Collection
层次结构 中的根
接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此
接口的任何直接 实现:它提供更具体的
子接口(如 Set 和 List)实现。此
接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
包 (bag) 或多集合 (multiset)(可能包含重复元素的无序 collection)应该直接实现此
接口。
所有通用的 Collection 实现类(通常通过它的一个
子接口间接实现 Collection)应该提供两个“标准”构造方法:一个是 void(无参数)构造方法,用于创建空 collection;另一个是带有 Collection 类型单参数的构造方法,用于创建一个具有与其参数相同元素新的 collection。实际上,后者允许用户复制任何 collection,以生成所需实现类型的一个等效 collection。尽管无法强制执行此约定(因为
接口不能包含
构造方法),但是 Java 平台库中所有通用的 Collection 实现都遵从它。
此
接口中包含的“破坏性”方法,是指可修改其所操作的 collection 的那些方法,如果此 collection 不支持该操作,则指定这些方法抛出 UnsupportedOperationException。如果是这样,那么在调用对该 collection 无效时,这些方法可能,但并不一定抛出 UnsupportedOperationException。例如,如果要添加的 collection 为空且不可修改,则对该 collection 调用 addAll(Collection) 方法时,可能但并不一定
抛出异常。
一些 collection 实现对它们可能包含的元素有所限制。例如,某些实现禁止 null 元素,而某些实现则对元素的类型有限制。试图添加不合格的元素将抛出一个未经检查的异常,通常是 NullPointerException 或 ClassCastException。试图查询是否存在不合格的元素可能抛出一个异常,或者只是简单地返回 false;某些实现将表现出前一种行为,而某些实现则表现后一种。较为常见的是,试图对某个不合格的元素执行操作且该操作的完成不会导致将不合格的元素插入 collection 中,将可能抛出一个异常,也可能操作成功,这取决于实现本身。这样的异常在此
接口的规范中标记为“可选”。
由每个 collection 来确定其自身的同步策略。在没有实现的强烈保证的情况下,调用由另一进程正在更改的 collection 的方法可能会出现不确定行为;这包括直接调用,将 collection 传递给可能执行调用的方法,以及使用现有
迭代器检查 collection。
Collections Framework
接口中的很多方法是根据 equals 方法定义的。例如,contains(Object o) 方法的规范声明:“当且仅当此 collection 包含至少一个满足 (o==null e==null :o.equals(e)) 的元素 e 时,返回 true。”不 应将此规范理解为它暗指调用具有非空参数 o 的 Collection.contains 方法会导致为任意的 e 元素调用 o.equals(e) 方法。可随意对各种实现执行优化,只要避免调用 equals 即可,例如,通过首先比较两个元素的
哈希码。(Object.hashCode() 规范保证
哈希码不相等的两个对象不会相等)。较为常见的是,各种 Collections Framework
接口的实现可随意利用底层 Object 方法的指定行为,而不管实现程序认为它是否合适。
集合的常用操作
boolean add(E e)
确保此 collection 包含指定的元素(可选操作)。
boolean addAll(Collection<? extends E> c)
将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。
void clear()
移除此 collection 中的所有元素(可选操作)。
boolean contains(Object o)
如果此 collection 包含指定的元素,则返回 true。
boolean containsAll(Collection<?> c)
如果此 collection 包含指定 collection 中的所有元素,则返回 true。
boolean equals(Object o)
比较此 collection 与指定对象是否相等。
int hashCode()
boolean isEmpty()
如果此 collection 不包含元素,则返回 true。
Iterator<E> iterator()
返回在此 collection 的元素上进行迭代的
迭代器。
boolean remove(Object o)
从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
boolean removeAll(Collection<?> c)
移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。
boolean retainAll(Collection<?> c)
仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。
int size()
返回此 collection 中的元素数。
Object[] toArray()
返回包含此 collection 中所有元素的
数组。
<T> T[]toArray(T[] a)
返回包含此 collection 中所有元素的
数组;返回数组的运行时类型与指定数组的运行时类型相同。
编辑本段Collection接口的各子类的深入剖析
线性表,
链表,
哈希表是常用的
数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在
java.util包中。本段落试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
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是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
很明显,Set的
构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。
请注意,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时,
客户端代码不用改变。这就是针对
抽象编程。
同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是
线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是
线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要
线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用
数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部
数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
使用模式
在ArrayList和Vector中,从一个指定的位置(通过
索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的
索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在
索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的
数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用
数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。
层次结构 中的根
接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此
接口的任何直接 实现:它提供更具体的
子接口(如 Set 和 List)实现。此
接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
包 (bag) 或多集合 (multiset)(可能包含重复元素的无序 collection)应该直接实现此
接口。
所有通用的 Collection 实现类(通常通过它的一个
子接口间接实现 Collection)应该提供两个“标准”构造方法:一个是 void(无参数)构造方法,用于创建空 collection;另一个是带有 Collection 类型单参数的构造方法,用于创建一个具有与其参数相同元素新的 collection。实际上,后者允许用户复制任何 collection,以生成所需实现类型的一个等效 collection。尽管无法强制执行此约定(因为
接口不能包含
构造方法),但是 Java 平台库中所有通用的 Collection 实现都遵从它。
此
接口中包含的“破坏性”方法,是指可修改其所操作的 collection 的那些方法,如果此 collection 不支持该操作,则指定这些方法抛出 UnsupportedOperationException。如果是这样,那么在调用对该 collection 无效时,这些方法可能,但并不一定抛出 UnsupportedOperationException。例如,如果要添加的 collection 为空且不可修改,则对该 collection 调用 addAll(Collection) 方法时,可能但并不一定
抛出异常。
一些 collection 实现对它们可能包含的元素有所限制。例如,某些实现禁止 null 元素,而某些实现则对元素的类型有限制。试图添加不合格的元素将抛出一个未经检查的异常,通常是 NullPointerException 或 ClassCastException。试图查询是否存在不合格的元素可能抛出一个异常,或者只是简单地返回 false;某些实现将表现出前一种行为,而某些实现则表现后一种。较为常见的是,试图对某个不合格的元素执行操作且该操作的完成不会导致将不合格的元素插入 collection 中,将可能抛出一个异常,也可能操作成功,这取决于实现本身。这样的异常在此
接口的规范中标记为“可选”。
由每个 collection 来确定其自身的同步策略。在没有实现的强烈保证的情况下,调用由另一进程正在更改的 collection 的方法可能会出现不确定行为;这包括直接调用,将 collection 传递给可能执行调用的方法,以及使用现有
迭代器检查 collection。
Collections Framework
接口中的很多方法是根据 equals 方法定义的。例如,contains(Object o) 方法的规范声明:“当且仅当此 collection 包含至少一个满足 (o==null e==null :o.equals(e)) 的元素 e 时,返回 true。”不 应将此规范理解为它暗指调用具有非空参数 o 的 Collection.contains 方法会导致为任意的 e 元素调用 o.equals(e) 方法。可随意对各种实现执行优化,只要避免调用 equals 即可,例如,通过首先比较两个元素的
哈希码。(Object.hashCode() 规范保证
哈希码不相等的两个对象不会相等)。较为常见的是,各种 Collections Framework
接口的实现可随意利用底层 Object 方法的指定行为,而不管实现程序认为它是否合适。
集合的常用操作
boolean add(E e)
确保此 collection 包含指定的元素(可选操作)。
boolean addAll(Collection<? extends E> c)
将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。
void clear()
移除此 collection 中的所有元素(可选操作)。
boolean contains(Object o)
如果此 collection 包含指定的元素,则返回 true。
boolean containsAll(Collection<?> c)
如果此 collection 包含指定 collection 中的所有元素,则返回 true。
boolean equals(Object o)
比较此 collection 与指定对象是否相等。
int hashCode()
boolean isEmpty()
如果此 collection 不包含元素,则返回 true。
Iterator<E> iterator()
返回在此 collection 的元素上进行迭代的
迭代器。
boolean remove(Object o)
从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
boolean removeAll(Collection<?> c)
移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。
boolean retainAll(Collection<?> c)
仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。
int size()
返回此 collection 中的元素数。
Object[] toArray()
返回包含此 collection 中所有元素的
数组。
<T> T[]toArray(T[] a)
返回包含此 collection 中所有元素的
数组;返回数组的运行时类型与指定数组的运行时类型相同。
编辑本段Collection接口的各子类的深入剖析
线性表,
链表,
哈希表是常用的
数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在
java.util包中。本段落试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
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是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
很明显,Set的
构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。
请注意,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时,
客户端代码不用改变。这就是针对
抽象编程。
同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是
线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是
线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要
线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用
数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部
数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
使用模式
在ArrayList和Vector中,从一个指定的位置(通过
索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的
索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在
索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的
数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用
数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。
- Java集合Collection框架是什么?列出集合框架的一些好处?
在每一种编程语言都有集合的使用,最初的Java版本包含了几类集合:向量,堆栈,哈希表,数组。但在更大的范围使用是在Java 1.2中集合框架想出了该组的所有集合接口,实现和算法。 Java集合的线程安全操作和使用泛型和并发集合类等。它还包括阻塞的接口及其实现在Java并发包。
集合框架的好处是:
使用核心集合类,而不需要实现我们自己的集合类,减少了开发工作。
使用经过测试的集合框架类提高代码质量。
使用JDK附带的集合类减少代码维护的工作。
可重用性和互操作性
- 泛型集合框架的好处是什么呢?
Java 1.5中附带泛型和所有收集接口和接口实现的大量使用。泛型允许我们提供一个集合可以包含Object类型,所以如果你尝试添加任何其他类型的元素,它会引发编译时错误。这就避免了在运行时抛出,因为你会得到编译错误。泛型使代码更干净,因为我们并不需要使用溯型casting和instanceof检查。它也增加了运行时的好处,因为不生成的做类型检查字节码指令。
- Java集合框架的基本接口是什么?
Collection是集合层次的根。一个集合包含一组对象作为其元素。 Java平台不提供任何直接实现这个接口。
Set 是一个不能包含重复的元素的集合。此接口模型代表数学Set的抽象,用来代表一组Set,如一副扑克牌。
List是有序集合,可以包含重复的元素。您可以从它的索引访问任何元素。更像是动态长度的数组列表。
一个Map是一个键映射值的对象。一个Map不能包含重复键:每个key只能映射一个值。
其他一些接口Queue, Dequeue, Iterator, SortedSet,SortedMap的和listIterator。
- 为什么要集合不能继承Cloneable和Serializable接口?
Collection接口指定一组称为元素的对象。元素如何被组织取决于具体实现。例如,一些LIST实现允许重复的元素,而SET不允许。Collection是一种抽象表示,而克隆和序列化重在执行,应该是在Collection具体实现子类中根据具体元素组织情况来实现。因此,强制在所有实现都要有克隆和序列化是不够灵活的,具有限制性。
- 为什么MAP接口不实现Collection接口?
虽然Map接口和它的实现是集合框架的一部分,但是MAP不是集合,而且集合也不是地图。因此,它实现集合接口没有任何意义。
- Iterator是什么?
Iterator接口提供遍历集合的方法。从一个集合中使用迭代方法,我们可以得到迭代器实例。迭代器允许呼叫者在迭代过程中从集合中删除元素。
- 枚举Enumeration 和Iterator接口之间的差异是什么?
枚举是快迭代两倍,使用非常少的内存。枚举适合基本需求。但Iterator是更安全,因为它总是拒绝其他线程修改它正在迭代集合中的对象。
- 为什么没有方法像Iterator.add()将元素添加到集合?
这是语义不清,对于迭代一个集合,必须保证迭代的顺序。但是请注意,ListIterator确实提供了一个add的操作,而且它保证迭代的顺序。
- 为什么迭代器没有不用移动光标来直接获得下一个元素的方法?
它可以基于当前Iterator接口之上实现,但很少使用,没有意义。
- Iterator和listIterator之间有什么不同?
我们可以使用迭代器Iterator遍历Set和List集合,而ListIterator只可以使用List。
迭代器遍历只有向前的方向,而ListIterator可以用来在两个方向遍历。
ListIterator继承Iterator接口,并配备了额外的功能,如添加元素,更换一个元素,能获得上一个和下一个元素的索引位置。
Arrays
Array是Java特有的数组。在你知道所要处理数据元素个数的情况下非常好用。java.util.Arrays 包含了许多处理数据的实用方法:
- Arrays.asList:可以从 Array 转换成 List。可以作为其他集合类型构造器的参数。
- Arrays.binarySearch:在一个已排序的或者其中一段中快速查找。
- Arrays.copyOf:如果你想扩大数组容量又不想改变它的内容的时候可以使用这个方法。
- Arrays.copyOfRange:可以复制整个数组或其中的一部分。
- Arrays.deepEquals、Arrays.deepHashCode:Arrays.equals/hashCode的高级版本,支持子数组的操作。
- Arrays.equals:如果你想要比较两个数组是否相等,应该调用这个方法而不是数组对象中的 equals方法(数组对象中没有重写equals()方法,所以这个方法之比较引用而不比较内容)。这个方法集合了Java 5的自动装箱和无参变量的特性,来实现将一个变量快速地传给 equals() 方法——所以这个方法在比较了对象的类型之后是直接传值进去比较的。
- Arrays.fill:用一个给定的值填充整个数组或其中的一部分。
- Arrays.hashCode:用来根据数组的内容计算其哈希值(数组对象的hashCode()不可用)。这个方法集合了Java 5的自动装箱和无参变量的特性,来实现将一个变量快速地传给 Arrays.hashcode方法——只是传值进去,不是对象。
- Arrays.sort:对整个数组或者数组的一部分进行排序。也可以使用此方法用给定的比较器对对象数组进行排序。
- Arrays.toString:打印数组的内容。
如果想要复制整个数组或其中一部分到另一个数组,可以调用 System.arraycopy方法。此方法从源数组中指定的位置复制指定个数的元素到目标数组里。这无疑是一个简便的方法。(有时候用 ByteBuffer bulk复制会更快。可以参考这篇文章).
最后,所有的集合都可以用T[] Collection.toArray( T[] a ) 这个方法复制到数组中。通常会用这样的方式调用:
return coll.toArray( new T[ coll.size() ] );
这个方法会分配足够大的数组来储存所有的集合,这样 toArray 在返回值时就不必再分配空间了。
单线程集合
这一部分介绍的是不支持多线程的集合。这些集合都在java.util包里。其中一些在Java 1.o的时候就有了(现在已经弃用),其中大多数在Java 1.4中重新发布。枚举集合在Java 1.5中重新发布,并且从这个版本之后所有的集合都支持泛型。PriorityQueue也在Java 1.5中加入。非线程安全的集合架构的最后一个版本是ArrayDeque ,也在Java 1.6中重新发布了。
List
- ArrayList:最有用的List集合实现。由一个整形数字或数组存储了集合的大小(数组中第一个没有使用的元素)。像所有的List集合一样,ArrayList可以在必要的时候扩展它的大小。ArrayList访问元素的时间开销固定。在尾部添加元素成本低(为常数复杂度),而在头部添加元素成本很高(线性复杂度)。这是由ArrayList的实现原理——所有的元素的从角标为0开始一个接着一个排列造成的。也就是说,从要插入的元素位置往后,每个元素都要向后移动一个位置。CPU缓存友好的集合是基于数组的。(其实也不是很友好,因为有时数组会包含对象,这样存储的只是指向实际对象的指针)。
- LinkedList:Deque实现:每一个节点都保存着上一个节点和下一个节点的指针。这就意味着数据的存取和更新具有线性复杂度(这也是一个最佳化的实现,每次操作都不会遍历数组一半以上,操作成本最高的元素就是数组中间的那个)。如果想写出高效的LinkedList代码可以使用 ListIterators 。如果你想用一个Queue/Deque实现的话(你只需读取第一个和最后一个元素就行了)——考虑用ArrayDeque代替。
- Vector:一个带有线程同步方法的ArrayList版本。现在直接用ArrayList代替了。
Queues/deques
- ArrayDeque:Deque是基于有首尾指针的数组(环形缓冲区)实现的。和LinkedList不同,这个类没有实现List接口。因此,如果没有首尾元素的话就不能取出任何元素。这个类比LinkedList要好一些,因为它产生的垃圾数量较少(在扩展的时候旧的数组会被丢弃)。
- Stack:一种后进先出的队列。不要在生产代码中使用,使用别的Deque来代替(ArrayDeque比较好)。
- PriorityQueue:一个基于优先级的队列。使用自然顺序或者制定的比较器来排序。他的主要属性——poll/peek/remove/element会返回一个队列的最小值。不仅如此,PriorityQueue还实现了Iterable接口,队列迭代时不进行排序(或者其他顺序)。在需要排序的集合中,使用这个队列会比TreeSet等其他队列要方便。
Maps
- HashMap:最常用的Map实现。只是将一个键和值相对应,并没有其他的功能。对于复杂的hashCode method,get/put方法有固定的复杂度。
- EnumMap:枚举类型作为键值的Map。因为键的数量相对固定,所以在内部用一个数组储存对应值。通常来说,效率要高于HashMap。
- HashTable:旧HashMap的同步版本,新的代码中也使用了HashMap。
- IdentityHashMap:这是一个特殊的Map版本,它违背了一般Map的规则:它使用 “==” 来比较引用而不是调用Object.equals来判断相等。这个特性使得此集合在遍历图表的算法中非常实用——可以方便地在IdentityHashMap中存储处理过的节点以及相关的数据。
- LinkedHashMap :HashMap和LinkedList的结合,所有元素的插入顺序存储在LinkedList中。这就是为什么迭代LinkedHashMap的条目(entry)、键和值的时候总是遵循插入的顺序。在JDK中,这是每元素消耗内存最大的集合。
- TreeMap:一种基于已排序且带导向信息Map的红黑树。每次插入都会按照自然顺序或者给定的比较器排序。这个Map需要实现equals方法和Comparable/Comparator。compareTo需要前后一致。这个类实现了一个NavigableMap接口:可以带有与键数量不同的入口,可以得到键的上一个或者下一个入口,可以得到另一Map某一范围的键(大致和SQL的BETWEEN运算符相同),以及其他的一些方法。
- WeakHashMap:这种Map通常用在数据缓存中。它将键存储在WeakReference中,就是说,如果没有强引用指向键对象的话,这些键就可以被垃圾回收线程回收。值被保存在强引用中。因此,你要确保没有引用从值指向键或者将值也保存在弱引用中m.put(key, new WeakReference(value))。
Sets
- HashSet:一个基于HashMap的Set实现。其中,所有的值为“假值”(同一个Object对象具备和HashMap同样的性能。基于这个特性,这个数据结构会消耗更多不必要的内存。
- EnumSet:值为枚举类型的Set。Java的每一个enum都映射成一个不同的int。这就允许使用BitSet——一个类似的集合结构,其中每一比特都映射成不同的enum。EnumSet有两种实现,RegularEnumSet——由一个单独的long存储(能够存储64个枚举值,99.9%的情况下是够用的),JumboEnumSet——由long[]存储。
- BitSet:一个比特Set。需要时常考虑用BitSet处理一组密集的整数Set(比如从一个预先知道的数字开始的id集合)。这个类用 long[]来存储bit。
- LinkedHashMap:与HashSet一样,这个类基于LinkedHashMap实现。这是唯一一个保持了插入顺序的Set。
- TreeSet:与HashSet类似。这个类是基于一个TreeMap实例的。这是在单线程部分唯一一个排序的Set。
java.util.Collections
就像有专门的java.util.Arrays来处理数组,Java中对集合也有java.util.Collections来处理。
第一组方法主要返回集合的各种数据:
- Collections.checkedCollection / checkedList / checkedMap / checkedSet / checkedSortedMap / checkedSortedSet:检查要添加的元素的类型并返回结果。任何尝试添加非法类型的变量都会抛出一个ClassCastException异常。这个功能可以防止在运行的时候出错。//fixme
- Collections.emptyList / emptyMap / emptySet :返回一个固定的空集合,不能添加任何元素。
- Collections.singleton / singletonList / singletonMap:返回一个只有一个入口的 set/list/map 集合。
- Collections.synchronizedCollection / synchronizedList / synchronizedMap / synchronizedSet / synchronizedSortedMap / synchronizedSortedSet:获得集合的线程安全版本(多线程操作时开销低但不高效,而且不支持类似put或update这样的复合操作)
- Collections.unmodifiableCollection / unmodifiableList / unmodifiableMap / unmodifiableSet / unmodifiableSortedMap / unmodifiableSortedSet:返回一个不可变的集合。当一个不可变对象中包含集合的时候,可以使用此方法。
第二组方法中,其中有一些方法因为某些原因没有加入到集合中:
- Collections.addAll:添加一些元素或者一个数组的内容到集合中。
- Collections.binarySearch:和数组的Arrays.binarySearch功能相同。
- Collections.disjoint:检查两个集合是不是没有相同的元素。
- Collections.fill:用一个指定的值代替集合中的所有元素。
- Collections.frequency:集合中有多少元素是和给定元素相同的。
- Collections.indexOfSubList / lastIndexOfSubList:和String.indexOf(String) / lastIndexOf(String)方法类似——找出给定的List中第一个出现或者最后一个出现的子表。
- Collections.max / min:找出基于自然顺序或者比较器排序的集合中,最大的或者最小的元素。
- Collections.replaceAll:将集合中的某一元素替换成另一个元素。
- Collections.reverse:颠倒排列元素在集合中的顺序。如果你要在排序之后使用这个方法的话,在列表排序时,最好使用Collections.reverseOrder比较器。
- Collections.rotate:根据给定的距离旋转元素。
- Collections.shuffle:随机排放List集合中的节点,可以给定你自己的生成器——例如 java.util.Random / java.util.ThreadLocalRandom or java.security.SecureRandom。
- Collections.sort:将集合按照自然顺序或者给定的顺序排序。
- Collections.swap:交换集合中两个元素的位置(多数开发者都是自己实现这个操作的)。
并发集合
这一部分将介绍java.util.concurrent包中线程安全的集合。这些集合的主要属性是一个不可分割的必须执行的方法。因为并发的操作,例如add或update或者check再update,都有一次以上的调用,必须同步。因为第一步从集合中组合操作查询到的信息在开始第二步操作时可能变为无效数据。
多数的并发集合是在Java 1.5引入的。ConcurrentSkipListMap / ConcurrentSkipListSet 和 LinkedBlockingDeque是在Java 1.6新加入的。Java 1.7加入了最后的 ConcurrentLinkedDeque 和 LinkedTransferQueue
Lists
- CopyOnWriteArrayList:list的实现每一次更新都会产生一个新的隐含数组副本,所以这个操作成本很高。通常用在遍历操作比更新操作多的集合,比如listeners/observers集合。
Queues/deques
- ArrayBlockingQueue:基于数组实现的一个有界阻塞队,大小不能重新定义。所以当你试图向一个满的队列添加元素的时候,就会受到阻塞,直到另一个方法从队列中取出元素。
- ConcurrentLinkedDeque / ConcurrentLinkedQueue:基于链表实现的无界队列,添加元素不会堵塞。但是这就要求这个集合的消费者工作速度至少要和生产这一样快,不然内存就会耗尽。严重依赖于CAS(compare-and-set)操作。
- DelayQueue:无界的保存Delayed元素的集合。元素只有在延时已经过期的时候才能被取出。队列的第一个元素延期最小(包含负值——延时已经过期)。当你要实现一个延期任务的队列的时候使用(不要自己手动实现——使用ScheduledThreadPoolExecutor)。
- LinkedBlockingDeque / LinkedBlockingQueue:可选择有界或者无界基于链表的实现。在队列为空或者满的情况下使用ReentrantLock-s。
- LinkedTransferQueue:基于链表的无界队列。除了通常的队列操作,它还有一系列的transfer方法,可以让生产者直接给等待的消费者传递信息,这样就不用将元素存储到队列中了。这是一个基于CAS操作的无锁集合。
- PriorityBlockingQueue:PriorityQueue的无界的版本。
- SynchronousQueue:一个有界队列,其中没有任何内存容量。这就意味着任何插入操作必须等到响应的取出操作才能执行,反之亦反。如果不需要Queue接口的话,通过Exchanger类也能完成响应的功能。
Maps
- ConcurrentHashMap:get操作全并发访问,put操作可配置并发操作的哈希表。并发的级别可以通过构造函数中concurrencyLevel参数设置(默认级别16)。该参数会在Map内部划分一些分区。在put操作的时候只有只有更新的分区是锁住的。这种Map不是代替HashMap的线程安全版本——任何 get-then-put的操作都需要在外部进行同步。
- ConcurrentSkipListMap:基于跳跃列表(Skip List)的ConcurrentNavigableMap实现。本质上这种集合可以当做一种TreeMap的线程安全版本来使用。
Sets
- ConcurrentSkipListSet:使用 ConcurrentSkipListMap来存储的线程安全的Set。
- CopyOnWriteArraySet:使用CopyOnWriteArrayList来存储的线程安全的Set。
相关阅读
- Java 基本类型集合库:Trove:Trove库概述——存储Java基本类型数据的集合库(与大多数JDK中的Objects类不同)。
- Java常见数据类型内存占用(1):各种类的内存占用会所名,包括enums、EnumMap、EnumSet、BitSet、ArrayList、LinkedList和ArrayDeque。
- Java常见数据类型内存占用(2):HashMap、HashSet、LinkedHashMap、LinkedHashSet、TreeMap、TreeSet和JDK 7 PriorityQueue内存占用,以及对应的Trove替代类说明。
推荐阅读
如果想要了解更多关于Java集合的知识,推荐阅读以下书籍:
总结
| 单线程 | 并发 |
Lists |
- ArrayList——基于泛型数组
- LinkedList——不推荐使用
- Vector——已废弃(deprecated)
|
- CopyOnWriteArrayList——几乎不更新,常用来遍历
|
Queues / deques |
- ArrayDeque——基于泛型数组
- Stack——已废弃(deprecated)
- PriorityQueue——读取操作的内容已排序
|
- ArrayBlockingQueue——带边界的阻塞式队列
- ConcurrentLinkedDeque / ConcurrentLinkedQueue——无边界的链表队列(CAS)
- DelayQueue——元素带有延迟的队列
- LinkedBlockingDeque / LinkedBlockingQueue——链表队列(带锁),可设定是否带边界
- LinkedTransferQueue——可将元素`transfer`进行w/o存储
- PriorityBlockingQueue——并发PriorityQueue
- SynchronousQueue——使用Queue接口进行Exchanger
|
Maps |
- HashMap——通用Map
- EnumMap——键使用enum
- Hashtable——已废弃(deprecated)
- IdentityHashMap——键使用==进行比较
- LinkedHashMap——保持插入顺序
- TreeMap——键已排序
- WeakHashMap——适用于缓存(cache)
|
- ConcurrentHashMap——通用并发Map
- ConcurrentSkipListMap——已排序的并发Map
|
Sets |
- HashSet——通用set
- EnumSet——enum Set
- BitSet——比特或密集的整数Set
- LinkedHashSet——保持插入顺序
- TreeSet——排序Set
|
- ConcurrentSkipListSet——排序并发Set
- CopyOnWriteArraySet——几乎不更新,通常只做遍历
|