Java - 集合框架完全解析
数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作。Java提供了几个能有效地组织和操作数据的数据结构,这些数据结构通常称为Java集合框架。在平常的学习开发中,灵活熟练地使用这些集合框架,可以很明显地提高我们的开发效率,当然仅仅会用还是不够的,理解其中的设计思想与原理才能更好地提高我们的开发水平。下面是自己对Java集合框架方面的学习总结。
一、概述
二、Collection接口
1.List
2.Set
3.Queue
三、Map接口
1.HashMap实现原理
2.其它Map实现类
四、其它集合类
五、总结
一、概述
在Java 2之前,Java是没有完整的集合框架的。它只有一些简单的可以自扩展的容器类,比如Vector,Stack,Hashtable等。这些容器类在使用的过程中由于效率问题饱受诟病,因此在Java 2中,Java设计者们进行了大刀阔斧的整改,重新设计,于是就有了现在的集合框架。需要注意的是,之前的那些容器类库并没有被弃用而是进行了保留,主要是为了向下兼容的目的,但我们在平时使用中还是应该尽量少用。
Java集合框架
从上面的集合框架图可以看到,Java集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection接口又有3种子类型,List、Set和Queue,再下面是一些抽象类,最后是具体实现类,常用的有ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap等等。
二、Collection接口
Collection接口是处理对象集合的根接口,其中定义了很多对元素进行操作的方法,AbstractCollection是提供Collection部分实现的抽象类。下图展示了Collection接口中的全部方法。
Collection接口结构
其中,有几个比较常用的方法,比如方法add()添加一个元素到集合中,addAll()将指定集合中的所有元素添加到集合中,contains()方法检测集合中是否包含指定的元素,toArray()方法返回一个表示集合的数组。Collection接口有三个子接口,下面详细介绍。
1.List
List接口扩展自Collection,它可以定义一个允许重复的有序集合,从List接口中的方法来看,List接口主要是增加了面向位置的操作,允许在指定位置上操作元素,同时增加了一个能够双向遍历线性表的新列表迭代器ListIterator。AbstractList类提供了List接口的部分实现,AbstractSequentialList扩展自AbstractList,主要是提供对链表的支持。下面介绍List接口的两个重要的具体实现类,也是我们可能最常用的类,ArrayList和LinkedList。
ArrayList
通过阅读ArrayList的源码,我们可以很清楚地看到里面的逻辑,它是用数组存储元素的,这个数组可以动态创建,如果元素个数超过了数组的容量,那么就创建一个更大的新数组,并将当前数组中的所有元素都复制到新数组中。假设第一次是集合没有任何元素,下面以插入一个元素为例看看源码的实现。
1、方法add(E e)向集合中添加指定元素。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2、此方法主要是确定将要创建的数组大小。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3、最后是创建数组,可以明显的看到先是确定了添加元素后的大小之后将元素复制到新数组中。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList
同样,我们打开LinkedList的源文件,不难看到LinkedList是在一个链表中存储元素。
在学习数据结构的时候,我们知道链表和数组的最大区别在于它们对元素的存储方式的不同导致它们在对数据进行不同操作时的效率不同,同样,ArrayList与LinkedList也是如此,实际使用中我们需要根据特定的需求选用合适的类,如果除了在末尾外不能在其他位置插入或者删除元素,那么ArrayList效率更高,如果需要经常插入或者删除元素,就选择LinkedList。
2.Set
Set接口扩展自Collection,它与List的不同之处在于,规定Set的实例不包含重复的元素。在一个规则集内,一定不存在两个相等的元素。AbstractSet是一个实现Set接口的抽象类,Set接口有三个具体实现类,分别是散列集HashSet、链式散列集LinkedHashSet和树形集TreeSet。
散列集HashSet
散列集HashSet是一个用于实现Set接口的具体类,可以使用它的无参构造方法来创建空的散列集,也可以由一个现有的集合创建散列集。在散列集中,有两个名词需要关注,初始容量和客座率。客座率是确定在增加规则集之前,该规则集的饱满程度,当元素个数超过了容量与客座率的乘积时,容量就会自动翻倍。
下面看一个HashSet的例子。
/**
* @author JackalTsc
*/
public class TestHashSet {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("11111");
set.add("22222");
set.add("33333");
set.add("44444");
set.add("22222");
System.out.println(set.size());
for (String e : set) {
System.out.println(e);
}
}
}
从输出结果我们可以看到,规则集里最后有4个元素,而且在输出时元素还是无序的。
链式散列集LinkedHashSet
LinkedHashSet是用一个链表实现来扩展HashSet类,它支持对规则集内的元素排序。HashSet中的元素是没有被排序的,而LinkedHashSet中的元素可以按照它们插入规则集的顺序提取。
树形集TreeSet
TreeSet扩展自AbstractSet,并实现了NavigableSet,AbstractSet扩展自AbstractCollection,树形集是一个有序的Set,其底层是一颗树,这样就能从Set里面提取一个有序序列了。在实例化TreeSet时,我们可以给TreeSet指定一个比较器Comparator来指定树形集中的元素顺序。树形集中提供了很多便捷的方法。
下面是一个TreeSet的例子。
/**
* @author JackalTsc
*/
public class TestSet {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
set.add(1111);
set.add(2222);
set.add(3333);
set.add(4444);
set.add(5555);
System.out.println(set.first()); // 输出第一个元素
System.out.println(set.lower(3333)); //小于3333的最大元素
System.out.println(set.higher(2222)); //大于2222的最大元素
System.out.println(set.floor(3333)); //不大于3333的最大元素
System.out.println(set.ceiling(3333)); //不小于3333的最大元素
System.out.println(set.pollFirst()); //删除第一个元素
System.out.println(set.pollLast()); //删除最后一个元素
System.out.println(set);
}
}
3.Queue
队列是一种先进先出的数据结构,元素在队列末尾添加,在队列头部删除。Queue接口扩展自Collection,并提供插入、提取、检验等操作。
Queue接口结构
上图中,方法offer表示向队列添加一个元素,poll()与remove()方法都是移除队列头部的元素,两者的区别在于如果队列为空,那么poll()返回的是null,而remove()会抛出一个异常。方法element()与peek()主要是获取头部元素,不删除。
接口Deque,是一个扩展自Queue的双端队列,它支持在两端插入和删除元素,因为LinkedList类实现了Deque接口,所以通常我们可以使用LinkedList来创建一个队列。PriorityQueue类实现了一个优先队列,优先队列中元素被赋予优先级,拥有高优先级的先被删除。
/**
* @author JackalTsc
*/
public class TestQueue {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("aaaa");
queue.offer("bbbb");
queue.offer("cccc");
queue.offer("dddd");
while (queue.size() > 0) {
System.out.println(queue.remove() + "");
}
}
}
三、Map接口
Map,图,是一种存储键值对映射的容器类,在Map中键可以是任意类型的对象,但不能有重复的键,每个键都对应一个值,真正存储在图中的是键值构成的条目。下面是接口Map的类结构。
接口Map的结构
从上面这张图中我们可以看到接口Map提供了很多查询、更新和获取存储的键值对的方法,更新包括方法clear()、put()、putAll()、remove()等等,查询方法包括containsKey、containsValue等等。Map接口常用的有三个具体实现类,分别是HashMap、LinkedHashMap、TreeMap。
1.HashMap
HashMap是基于哈希表的Map接口的非同步实现,继承自AbstractMap,AbstractMap是部分实现Map接口的抽象类。在平时的开发中,HashMap的使用还是比较多的。我们知道ArrayList主要是用数组来存储元素的,LinkedList是用链表来存储的,那么HashMap的实现原理是什么呢?先看下面这张图:
HashMap原理.jpg
在之前的版本中,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当链表中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
下面主要通过源码介绍一下它的实现原理。
HashMap存储元素的数组
transient Node<K,V>[] table;
数组的元素类型是Node<K,V>,Node<K,V>继承自Map.Entry<K,V>,表示键值对映射。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//构造函数 ( Hash值键值下一个节点 )
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
接下来我们看下HashMap的put操作。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //如果没有初始化则初始化table
if ((p = tab[i = (n - 1) & hash]) == null)
//这里 (n-1)&hash 是根据hash值得到这个元素在数组中的位置(即下标)
tab[i] = newNode(hash, key, value, null);
//如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上
else {
Node<K,V> e; K k;
//第一节节点hash值同,且key值与插入key相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//属于红黑树处理冲突
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
/链表处理冲突
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//新增节点后如果节点个数到达阈值,则将链表转换为红黑树
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//更新hash值和key值均相同的节点Value值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
接下来我们看下HashMap的get操作。
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
//如果第一个节点是TreeNode,说明采用的是数组+红黑树结构处理冲突
//遍历红黑树,得到节点值
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null &&
key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
到这里HashMap的大致实现原理应该很清楚了,有几个需要关注的重点是:HashMap存储元素的方式以及根据Hash值确定映射在数组中的位置还有JDK 1.8之后加入的红黑树的。
在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。对于任意给定的对象,只要它的hashCode()返回值相同,那么程序调用hash(int h)方法所计算得到的hash码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,在HashMap中,(n - 1) & hash 用于计算对象应该保存在table数组的哪个索引处。HashMap底层数组的长度总是2的n次方,当数组长度为2的n次幂的时候,(n - 1) & hash
2.LinkedHashMap
LinkedHashMap继承自HashMap,它主要是用链表实现来扩展HashMap类,HashMap中条目是没有顺序的,但是在LinkedHashMap中元素既可以按照它们插入图的顺序排序,也可以按它们最后一次被访问的顺序排序。
3.TreeMap
TreeMap基于红黑树数据结构的实现,键值可以使用Comparable或Comparator接口来排序。TreeMap继承自AbstractMap,同时实现了接口NavigableMap,而接口NavigableMap则继承自SortedMap。SortedMap是Map的子接口,使用它可以确保图中的条目是排好序的。
在实际使用中,如果更新图时不需要保持图中元素的顺序,就使用HashMap,如果需要保持图中元素的插入顺序或者访问顺序,就使用LinkedHashMap,如果需要使图按照键值排序,就使用TreeMap。
四、其它集合类
上面主要对Java集合框架作了详细的介绍,包括Collection和Map两个接口及它们的抽象类和常用的具体实现类,下面主要介绍一下其它几个特殊的集合类,Vector、Stack、HashTable、ConcurrentHashMap以及CopyOnWriteArrayList。
1.Vector
前面我们已经提到,Java设计者们在对之前的容器类进行重新设计时保留了一些数据结构,其中就有Vector。用法上,Vector与ArrayList基本一致,不同之处在于Vector使用了关键字synchronized将访问和修改向量的方法都变成同步的了,所以对于不需要同步的应用程序来说,类ArrayList比类Vector更高效。
2.Stack
Stack,栈类,是Java2之前引入的,继承自类Vector。
3.HashTable
HashTable和前面介绍的HashMap很类似,它也是一个散列表,存储的内容是键值对映射,不同之处在于,HashTable是继承自Dictionary的,HashTable中的函数都是同步的,这意味着它也是线程安全的,另外,HashTable中key和value都不可以为null。
上面的三个集合类都是在Java2之前推出的容器类,可以看到,尽管在使用中效率比较低,但是它们都是线程安全的。下面介绍两个特殊的集合类。
4.ConcurrentHashMap
Concurrent,并发,从名字就可以看出来ConcurrentHashMap是HashMap的线程安全版。同HashMap相比,ConcurrentHashMap不仅保证了访问的线程安全性,而且在效率上与HashTable相比,也有较大的提高。关于ConcurrentHashMap的设计,我将会在下一篇关于并发编程的博客中介绍,敬请关注。
5.CopyOnWriteArrayList
CopyOnWriteArrayList,是一个线程安全的List接口的实现,它使用了ReentrantLock锁来保证在并发情况下提供高性能的并发读取。
五、总结
到这里,对于Java集合框架的总结就结束了,还有很多集合类没有在这里提到,更多的还是需要大家自己去查去用。通过阅读源码,查阅资料,收获很大。
- Java集合框架主要包括Collection和Map两种类型。其中Collection又有3种子类型,分别是List、Set、Queue。Map中存储的主要是键值对映射。
- 规则集Set中存储的是不重复的元素,线性表中存储可以包括重复的元素,Queue队列描述的是先进先出的数据结构,可以用LinkedList来实现队列。
- 效率上,规则集比线性表更高效。
- ArrayList主要是用数组来存储元素,LinkedList主要是用链表来存储元素,HashMap的底层实现主要是借助数组+链表+红黑树来实现。
- Vector、HashTable等集合类效率比较低但都是线程安全的。包java.util.concurrent下包含了大量线程安全的集合类,效率上有较大提升。
来自:http://www.jianshu.com/p/63e76826e852
面试:
介绍 Collection 框架的结构
答:随意发挥题,天南海北谁便谈,只要让别觉得你知识渊博,理解透彻即可。
58、Collection 框架中实现比较要实现什么接口
comparable/comparator
59、ArrayList 和 Vector 的区别
答:
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集
合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,,
并且其中的数据是允许重复的,这是 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索
引号去检索其中的元素,也不允许有重复的元素(本来题目问的与 hashset 没有任何关系,但为了说清楚
ArrayList 与 Vector 的功能,我们使用对比方式,更有利于说明问题)。
接着才说 ArrayList 与 Vector 的区别,这主要包括两个方面:.
(1)同步性:
Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程序不安全的,它
的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线
程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考
虑和编写线程安全的代码。
备注:对于 Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住 Vector 与 Hashtable 是旧
的,是 java 一诞生就提供了的,它们是线程安全的,ArrayList 与 HashMap 是 java2 时才提供的,它们是
41
线程不安全的。所以,我们讲课时先讲老的。
(2)数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需
要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多
个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认
增长为原来两倍,而 ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的 1.5
倍)。 ArrayList 与 Vector 都可以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList
没有提供设置增长空间的方法。
总结:即 Vector 增长原来的一倍,ArrayList 增加原来的 0.5 倍。
60、HashMap 和 Hashtable 的区别
(条理上还需要整理,也是先说相同点,再说不同点)
HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了 Map 接口,主要区别在于
HashMap 允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于
Hashtable。 HashMap 允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey。因为 contains 方法容易
让人引起误解。
Hashtable 继承自 Dictionary 类,而 HashMap 是 Java1.2 引进的 Map interface 的一个实现。
最大的不同是,Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable 时,不
需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步。
Hashtable 和 HashMap 采用的 hash/rehash 算法都大概一样,所以性能不会有很大的差异。
就 HashMap 与 HashTable 主要从三方面来说。
一.历史原因:Hashtable 是基于陈旧的 Dictionary 类的,HashMap 是 Java 1.2 引进的 Map 接口的一个实现
二.同步性:Hashtable 是线程安全的,也就是说是同步的,而 HashMap 是线程序不安全的,不是同步的
三.值:只有 HashMap 可以让你将空值作为一个表的条目的 key 或 value
61、List 和 Map 区别?
一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数据是有顺序,
并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。
62、List, Set, Map 是否继承自 Collection 接口?
List,Set 是,Map 不是
63、List、Map、Set 三个接口,存取元素时,各有什么特点?
这样的题属于随意发挥题:这样的题比较考水平,两个方面的水平:一是要真正明白这些内容,二是要有
较强的总结和表述能力。如果你明白,但表述不清楚,在别人那里则等同于不明白。
42
首先,List 与 Set 具有相似性,它们都是单列元素的集合,所以,它们有一个功共同的父接口,叫
Collection。 Set 里面不允许有重复的元素,所谓重复,即不能有两个相等(注意,不是仅仅是相同)的对
象 ,即假设 Set 集合中有了一个 A 对象,现在我要向 Set 集合再存入一个 B 对象,但 B 对象与 A 对象
equals 相等,则 B 对象存储不进去,所以,Set 集合的 add 方法有一个 boolean 的返回值,当集合中没有某
个元素,此时 add 方法可成功加入该元素时,则返回 true,当集合含有与某个元素 equals 相等的元素时,
此时 add 方法无法加入该元素,返回结果为 false。Set 取元素时,没法说取第几个,只能以 Iterator 接口取
得所有的元素,再逐一遍历各个元素。
List 表示有先后顺序的集合, 注意,不是那种按年龄、按大小、按价格之类的排序。当我们多次调
用 add(Obj e)方法时,每次加入的对象就像火车站买票有排队顺序一样,按先来后到的顺序排序。有时
候,也可以插队,即调用 add(int index,Obj e)方法,就可以指定当前对象在集合中的存放位置。一个对象
可以被反复存储进 List 中,每调用一次 add 方法,这个对象就被插入进集合中一次,其实,并不是把这个
对象本身存储进了集合中,而是在集合中用一个索引变量指向这个对象,当这个对象被 add 多次时,即相
当于集合中有多个索引指向了这个对象,如图 x 所示。List 除了可以以 Iterator 接口取得所有的元素,再
逐一遍历各个元素之外,还可以调用 get(index i)来明确说明取第几个。
Map 与 List 和 Set 不同,它是双列的集合,其中有 put 方法,定义如下:put(obj key,obj value),每次
存储时,要存储一对 key/value,不能存储重复的 key,这个重复的规则也是按 equals 比较相等。取则可以
根据 key 获得相应的 value,即 get(Object key)返回值为 key 所对应的 value。另外,也可以获得所有的 key
的结合,还可以获得所有的 value 的结合,还可以获得 key 和 value 组合成的 Map.Entry 对象的集合。
List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存 key-value 值,
value 可多值。
HashSet 按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode 值的大小进行存储。例如,"abc"
---> 78,"def" ---> 62,"xyz" ---> 65 在 hashSet 中的存储顺序不是 62,65,78,这些问题感谢以前一个叫崔健的学员
提出,最后通过查看源代码给他解释清楚,看本次培训学员当中有多少能看懂源码。LinkedHashSet 按插入的顺
序存储,那被存储对象的 hashcode 方法还有什么作用呢?学员想想!hashset 集合比较两个对象
是否相等,首先看 hashcode 方法是否相等,然后看 equals 方法是否相等。new 两个 Student 插入到
HashSet 中,看 HashSet 的 size,实现 hashcode 和 equals 方法后再看 size。
同一个对象可以在 Vector 中加入多次。往集合里面加元素,相当于集合里用一根绳子连接到了目标对象。
往 HashSet 中却加不了多次的。
64、说出 ArrayList,Vector, LinkedList 的存储性能和特性
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元
素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而
插入数据慢,Vector 由于使用了 synchronized 方法(线程安全),通常性能上较 ArrayList 差,而
LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记
录本项的前后项即可,所以插入速度较快。
LinkedList 也是线程不安全的,LinkedList 提供了一些方法,使得 LinkedList 可以被当作堆栈和队列来使用。
43
65、去掉一个 Vector 集合中重复的元素
Vector newVector = new Vector();
For (int i=0;i<vector.size();i++)
{
Object obj = vector.get(i);
if(!newVector.contains(obj);
newVector.add(obj);
}
还有一种简单的方式,HashSet set = new HashSet(vector);
66、Collection 和 Collections 的区别。
Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List.
Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全
化等操作。
67、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是 equals()? 它们有何区别?
Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。
equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个分离的对象的内容
和类型相配的话,返回真值。
68、你所知道的集合类都有哪些?主要方法?
最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,
比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。
Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),其中每个
键映射到一个值。
ArrayList/VectorList
Collection
HashSet/TreeSetSet
PropetiesHashTable
Map
Treemap/HashMap
我记的不是方法名,而是思想,我知道它们都有增删改查的方法,但这些方法的具体名称,我记得不是很清楚,
对于 set,大概的方法是 add,remove, contains;对于 map,大概的方法就是 put,remove,contains 等,因为,我只
要在 eclispe 下按点操作符,很自然的这些方法就出来了。我记住的一些思想就是 List 类会有 get(int index)这样的
方法,因为它可以按顺序取元素,而 set 类中没有 get(int index)这样的方法。List 和 set 都可以
44
迭代出所有元素,迭代时先要得到一个 iterator 对象,所以,set 和 list 类都有一个 iterator 方法,用于返回
那个 iterator 对象。map 可以返回三个集合,一个是返回所有的 key 的集合,另外一个返回的是所有 value
的集合,再一个返回的 key 和 value 组合成的 EntrySet 对象的集合,map 也有 get 方法,参数是 key,返回
值是 key 对应的 value。
69、两个对象值相同(x.equals(y) == true),但却可有不同的 hash code,这句话对不对?
对。
如果对象要保存在 HashSet 或 HashMap 中,它们的 equals 相等,那么,它们的 hashcode 值就必须相等。
如果不是要保存在 HashSet 或 HashMap,则与 hashcode 没有什么关系了,这时候 hashcode 不等是可以
的,例如 arrayList 存储的对象就不用实现 hashcode,当然,我们没有理由不实现,通常都会去实现的。
70、TreeSet 里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的 compareTo 方
法,还是使用的子类的 compareTo 方法,还是抛异常!
(应该是没有针对问题的确切的答案,当前的 add 方法放入的是哪个对象,就调用哪个对象的 compareTo
方法,至于这个 compareTo 方法怎么做,就看当前这个对象的类中是如何编写这个方法的)
实验代码:
public class Parent implements Comparable
{ private int age = 0;
public Parent(int age){
this.age = age;
}
public int compareTo(Object o) {
// TODO Auto-generated method stub
System.out.println("method of parent");
Parent o1 = (Parent)o;
return age>o1.age?1:age<o1.age?-1:0;
}
}
public class Child extends Parent {
public Child(){
super(3);
}
public int compareTo(Object o) {
// TODO Auto-generated method stub
System.out.println("method of child");
// Child o1 = (Child)o;
45
return 1;
}
}
public class TreeSetTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TreeSet set = new TreeSet();
set.add(new Parent(3));
set.add(new Child()); set.add(new
Parent(4));
System.out.println(set.size());
}
}