JAVA 的容器---List,Map,Set
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection 接口
Collection 是最基本的集合接口,一个Collection 代表一组Object ,即Collection 的元素(Elements )。一些 Collection 允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK 不提供直接继承自Collection 的类,Java SDK 提供的类都是继承自Collection 的“ 子接口” 如List 和Set 。
所有实现Collection 接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection ,有一个 Collection 参数的构造函数用于创建一个新的Collection ,这个新的Collection 与传入的Collection 有相同的元素。后一个构造函数允许用户复制一个Collection 。
如何遍历Collection 中的每一个元素?不论Collection 的实际类型如何,它都支持一个iterator() 的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection 中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
由Collection 接口派生的两个接口是List 和Set 。
List 接口
List 是有序的Collection ,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List 中的位置,类似于数组下标)来访问List 中的元素,这类似于Java 的数组。
和下面要提到的Set 不同,List 允许有相同的元素。
除了具有Collection 接口必备的iterator() 方法外,List 还提供一个listIterator() 方法,返回一个 ListIterator 接口,和标准的Iterator 接口相比,ListIterator 多了一些add() 之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
实现List 接口的常用类有LinkedList ,ArrayList ,Vector 和Stack 。
LinkedList 类
LinkedList 实现了List 接口,允许null 元素。此外LinkedList 提供额外的get ,remove ,insert 方法在 LinkedList 的首部或尾部。这些操作使LinkedList 可被用作堆栈(stack ),队列(queue )或双向队列(deque )。
注意LinkedList 没有同步方法。如果多个线程同时访问一个List ,则必须自己实现访问同步。一种解决方法是在创建List 时构造一个同步的List :
List list = Collections.synchronizedList(new LinkedList(...));
ArrayList 类
ArrayList 实现了可变大小的数组。它允许所有元素,包括null 。ArrayList 没有同步。
size ,isEmpty ,get ,set 方法运行时间为常数。但是add 方法开销为分摊的常数,添加n 个元素需要O(n) 的时间。其他的方法运行时间为线性。
每个ArrayList 实例都有一个容量(Capacity ),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity 方法来增加ArrayList 的容量以提高插入效率。
和LinkedList 一样,ArrayList 也是非同步的(unsynchronized )。
Vector 类
Vector 非常类似ArrayList ,但是Vector 是同步的。由Vector 创建的Iterator ,虽然和ArrayList 创建的 Iterator 是同一接口,但是,因为Vector 是同步的,当一个Iterator 被创建而且正在被使用,另一个线程改变了Vector 的状态(例如,添加或删除了一些元素),这时调用Iterator 的方法时将抛出ConcurrentModificationException ,因此必须捕获该异常。
Stack 类
Stack 继承自Vector ,实现一个后进先出的堆栈。Stack 提供5 个额外的方法使得Vector 得以被当作堆栈使用。基本的push 和pop 方法,还有peek 方法得到栈顶的元素,empty 方法测试堆栈是否为空,search 方法检测一个元素在堆栈中的位置。Stack 刚创建后是空栈。
Set 接口
Set 是一种不包含重复的元素的Collection ,即任意的两个元素e1 和e2 都有e1.equals(e2)=false ,Set 最多有一个null 元素。
很明显,Set 的构造函数有一个约束条件,传入的Collection 参数不能包含重复的元素。
请注意:必须小心操作可变对象(Mutable Object )。如果一个Set 中的可变元素改变了自身状态导致Object.equals(Object)=true 将导致一些问题。
Map 接口
请注意,Map 没有继承Collection 接口,Map 提供key 到value 的映射。一个Map 中不能包含相同的key ,每个key 只能映射一个 value 。Map 接口提供3 种集合的视图,Map 的内容可以被当作一组key 集合,一组value 集合,或者一组key-value 映射。
Hashtable 类
Hashtable 继承Map 接口,实现一个key-value 映射的哈希表。任何非空(non-null )的对象都可作为key 或者value 。
添加数据使用put(key, value) ,取出数据使用get(key) ,这两个基本操作的时间开销为常数。
Hashtable 通过initial capacity 和load factor 两个参数调整性能。通常缺省的load factor 0.75 较好地实现了时间和空间的均衡。增大load factor 可以节省空间但相应的查找时间将增大,这会影响像get 和put 这样的操作。
使用Hashtable 的简单示例如下,将1 ,2 ,3 放到Hashtable 中,他们的key 分别是”one” ,”two” ,”three” :
Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));
要取出一个数,比如2 ,用相应的key :
Integer n = (Integer)numbers.get(“two”);
System.out.println(“two = ” + n);
由于作为key 的对象将通过计算其散列函数来确定与之对应的value 的位置,因此任何作为key 的对象都必须实现hashCode 和equals 方法。hashCode 和equals 方法继承自根类Object ,如果你用自定义的类当作key 的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true ,则它们的hashCode 必须相同,但如果两个对象不同,则它们的hashCode 不一定不同,如果两个不同对象的hashCode 相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode() 方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode ,对哈希表的操作会出现意想不到的结果(期待的get 方法返回null ),要避免这种问题,只需要牢记一条:要同时复写equals 方法和hashCode 方法,而不要只写其中一个。
Hashtable 是同步的。
HashMap 类
HashMap 和Hashtable 类似,不同之处在于HashMap 是非同步的,并且允许null ,即null value 和null key 。,但是将HashMap 视为Collection 时(values() 方法可返回Collection ),其迭代子操作时间开销和HashMap 的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap 的初始化容量设得过高,或者load factor 过低。
WeakHashMap 类
WeakHashMap 是一种改进的HashMap ,它对key 实行“ 弱引用” ,如果一个key 不再被外部所引用,那么该key 可以被GC 回收。
总结
如果涉及到堆栈,队列等操作,应该考虑用List ,对于需要快速插入,删除元素,应该使用LinkedList ,如果需要快速随机访问元素,应该使用ArrayList 。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
要特别注意对哈希表的操作,作为key 的对象要正确复写equals 和hashCode 方法。
尽量返回接口而非实际的类型,如返回List 而非ArrayList ,这样如果以后需要将ArrayList 换成LinkedList 时,客户端代码不用改变。这就是针对抽象编程。