Java 容器集合框架概览
Java Collections Framework
集合的概念
集合collection,有时叫做容器container,把多个元素组成一个单元。
早期的Java (pre-1.2) 中包含了Vector, Hashtable, 和array,但是没有包含一个统一的集合框架。
Java Collections Framework是一个统一的框架,为了表现和操纵集合。
所有的collections frameworks包括:接口(Interfaces)、实现(Implementations)和算法(Algorithms)。
泛型和类型安全的容器
使用Java SE5之前的容器的一个主要问题就是编译器允许你向容器中插入不正确的类型。
比如ArrayList,用add()插入对象,因为ArrayList保存的是Object,所以可以添加各种类型的对象,在编译期和运行时都不会产生问题。
当你用get()方法取出对象时,得到的只是Object类的引用,必须使用强制类型转换将其转换为特定的类型。在运行时,当你试图将一个类的对象转换为另一个类的对象时,就会得到一个异常。
可以在容器的尖括号中包含类型参数,指定容器实例可以保存的类型。
通过使用泛型,就可以在编译期防止将错误类型的对象放置到容器中。
这样的另外一个好处是,在将元素取出的时候,类型转换也不再是必须的了。因为容器知道它保存的是什么类型,因此它会在调用get()时替你执行转型。
接口
核心的集合接口封装了不同类型的集合。如下图所示:
核心集合接口
所有的核心接口都是泛型的,比如Collection接口:
public interface Collection<E>
<E>说明声明实例的时候需要指明对象类型,这样编译器就可以在编译期进行一个类型检查,减少运行时错误。
Collection
一个Collection代表了一组对象,这些对象被称为其元素(elements)。
这个接口提供了一些方法,告诉你有多少元素(size, isEmpty),检查一个对象是否包含在集合里(contains),为集合增加或者减少元素(add, remove),或者为集合提供迭代器(iterator)。
Set
一个不包含重复元素的集合。这个接口对数学中的集合的抽象概念进行了建模。
List
List定义一个有顺序的集合,有时也被称作一个序列(sequence)。List可以包含重复的元素。
Queue
通常,Queue中的元素都是按照FIFO (first-in-first-out)的方式,但是这不是必须的。一些优先级队列元素,按照它们的值排列。但是不管使用了什么排序方式,调用remove或poll方法时,移除的都是队列头元素。在FIFO队列中,新元素总是被插在队尾,但是其他类型的队列可能会使用不同的排列方式。
Map
Map中存储的是键值对的映射。一个Map不能包含重复的key,一个key可以对应至多一个值。
SortedSet和SortedMap
还有两个核心的接口是排序版本的Set和Map:SortedSet和SortedMap。SortedSet的元素是按照升序排列的,SortedMap是按照键的升序排列键值对的。
实现 (Implementations)
通用目的的实现:
Interfaces |
Set |
List |
Map |
Hash table Implementations |
HashSet |
|
HashMap |
Resizable array Implementations |
|
ArrayList |
|
Tree Implementations |
TreeSet |
|
TreeMap |
Linked list Implementations |
|
LinkedList |
|
Hash table + Linked list Implementations |
LinkedHashSet |
|
LinkedHashMap |
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1.Collection
一个独立元素的序列,这些元素都服从一条或者多条规则。
List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
有两种类型的List:
ArrayList,它长于随机访问元素,但是在List的中间插入和删除元素时较慢。
LinkedList,它在随机访问方面相对比较慢,但是它进行插入和删除代价较低。
Set:
HashSet、TreeSet、和LinkedHashSet都是Set,每个相同的项只保存一次。
HashSet是最快的获取元素方式,存储的顺序看起来并无实际意义;TreeSet按照比较的结果升序保存对象;LinkedHashSet按照被添加的顺序保存对象。
2.Map
一组成对的“键值对”对象,允许你使用键来查找值。
映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在了一起,或者被称为“字典”。
HashMap、TreeMap、和LinkedHashMap都是Map。
HashMap提供了最快的查找技术,也没有按照任何明显的顺序来保存其元素。
TreeMap按照比较结果的升序保存键。
LinkedHashMap则按照插入顺序保存键,同时还保留了HashMap的查询速度。
略去一些类和接口,一个简化版的类图关系如下:
算法
Collections
类提供了很多可复用的算法,它们都是静态方法的形式。
有代表性的几个算法:
排序Sorting
排序算法将集合重新排序,按照升序排列。
有两种形式:
sort(List<T> list)和sort(List<T> list, Comparator<? super T> c),前者按照元素的自然顺序排列,后者按照参数提供的具体的比较器进行排列。
关于排序顺序可见:
http://docs.oracle.com/javase/tutorial/collections/interfaces/order.html
排序算法使用的是归并算法(merge sort),因为它快速并且稳定(不会交换相等元素的位置),快速排序不稳定,并且也不能保证log(n)的效率。
洗牌Shuffling
shuffle算法打乱元素的顺序,制造一种随机无序的结果。
shuffle算法也有两种形式,一种使用默认的随机源,另一种使用用户传入的随机源(a source of randomness)。
常规数据操作Routine Data Manipulation
reverse:将List中的元素顺序反转。
fill:将每一个元素都用指定的值填充。
copy:拷贝源数据到目标数据List,目标List必须更长,这样目标List中前半段元素将被覆写,剩下的元素保持原状。
swap:交换List中两个指定位置的元素。
addAll:把指定的元素全都加入到指定的集合中去。
查找Searching
binarySearch算法在一个已经有序的List中用二分法查找一个特定的元素。
这个算法有两种形式,第一种传入一个List和一个指定的元素,这种形式假定这个List是按照其自然顺序的升序排列的。第二种形式还需要多传入一个比较器Comparator,并且假定List已经按照该比较器进行过排序,为升序。可以在binarySearch方法调用前先调用sort算法。
如果查找成功,返回索引,如果不成功,返回值是(-(insertion point) - 1),插入点(insertion point)是这个值应该插入的地方。这样就保证了如果查找成功,那么返回值是大于等于0的。
下面这个形式可以将查找失败的元素插入集合:
int pos = Collections.binarySearch(list, key); if (pos < 0) l.add(-pos-1);
组成Composition
对于集合的构成查询也存在一些方法:
frequency:计算某个特定元素在集合中出现的次数。
disjoint:判断两个集合是否分离,即它们是不是不含公共元素。
找极值Finding Extreme Values
min和max方法分别返回集合中的最小和最大元素。
它们都有两种形式,一种形式仅传入集合,然后根据元素的自然顺序返回极值;另一种形式需要再传入一个比较器Comparator,根据比较器设定的规则来返回极值。
参考资料
Java SE Documentation: The Collections Framework
http://docs.oracle.com/javase/7/docs/technotes/guides/collections/index.html
The Java Tutorials: Lesson: Interfaces
http://docs.oracle.com/javase/tutorial/collections/interfaces/index.html
The Java Tutorials: Lesson: Implementations
http://docs.oracle.com/javase/tutorial/collections/implementations/index.html
The Java Tutorials: Lesson: Algorithms
http://docs.oracle.com/javase/tutorial/collections/algorithms/index.html