Java Collections Framework
首先,看一下集合框架的最根基的接口Collection,看一下它的声明public interface Collection<E> extends Iterable<E>,可以看出它继承了Iterable(可迭代的)接口,就相当于说Collection的具体实现类均可以利用Iterator了,这也是集合均支持增强型For循环的原因。
这里面需要说明两个概念,即Ordered与Sorted,可以这么理解Sorted是Ordered的特例,也就是说没有既Sorted且Unordered的情况。
Ordered是说,访问该集合,该集合给定的顺序已经预定好了,例如对List的访问是按照下标,虽说HashMap之类的本质也是按照不同的Hash码的递增顺序访问,但还是很难预知。
Sorted是说,该集合内部的顺序是按照自然顺序(比如整数,字符串按字母表)或者按照对象之间具体的比较方式(实现Comparable接口,或者传入给集合Comparator的实现)。
看看这个接口:
它继承了Iterable接口,其中这些操作或行为,主要有几个方面:
(1)查询操作。
Size,isEmpty,contains,iterator,toArray均是属于查询操作。
(2)修改操作。
Add,Remove
(3)批量操作。
removeAll(后者的补集然后再取交集),retainAll(交集),containsAll,addAll,clear
一般来说会实现它的子接口List,Queue,Set,其中List是Ordered的,Queue是Sorted,而Set是不允许重复的,那么如果要求实现一个Bag集合(允许重复且Unordered)的话,那么才会直接实现Collection这个接口。
实现Collection的实现类一般均有两个构造函数,其中一个是无参的,另一个是以一个Collection为参数的构造函数。
针对这个接口Collection框架提供了一个抽象类,除了size与iterator,add方法以外基本给与了实现,因为上述方法与集合内部的具体实现的数据结构有关系。
其次,Ordered的List接口,可以使用下标来顺序访问集合内的元素。
看看这个接口:
加入的方法
addAll(int, Collection<? extends E>)
get(int)
set(int, E)
add(int, E)
remove(int)
indexOf(Object)
lastIndexOf(Object)
listIterator()
listIterator(int)
subList(int, int)
其中,listIterator返回的就是Iterator的子接口ListIterator,而listIterator(int)返回的迭代器是以int为开头的。
同样针对List这个接口也有一个抽象类AbstractList(继承自AbstractCollection)来完成具体通用实现。
其中,Collection的add已经实现,那么针对这个抽象类,主要有两个如下的内部类,其实就是迭代器,一个是完成Collection的顺序遍历,一个是完成有特殊功能的ListIterator的(诸如回退等)迭代器。
对于List接口的几个具体实现类。
第一个,大名鼎鼎的ArrayList,看看类图:
其本质是一个数组的实现,其自身的方法主要是trimToSize与ensureCapacity,前者是将数组本身的大小设置成实际数组大小而后者主要是将数组的大小最低设置成为参数要求的大小。还有一个关键,它是非线程安全的。
还有一个AbstractList的实现类,作为JAVA1.0的遗留产物,在JAVA2中实现了List接口,不过是线程安全的Vector。
方法够多。。。,不过很少维护了,例如ArrayList的lastIndexOf均是用listIterator来完成的,而Vector的实现就相对简单了,逆序遍历就完了,建议Sun很好的重写Collection Frameworks,因为这个很重要,可以大幅加速JAVA应用。
接着,Queue接口,该接口设计目的是以优先原则持有元素,主要添加了插入、删除和检查方法,其中每种方法均是有两个版本,一个返回一个异常,一个返回一个数。一般来说,该接口的实现类均不允许插入NULL,但是它的一个实现类LinkedList可以允许插入NULL。
Throws exception
Returns special value
Insert
Remove
Examine
一般是FIFO的,尾部插入,头部删除。
那么相对于Queue就有一个AbstractQueue,但是我们先来看看Queue的子接口Deque(double ended queue),其实就是一个双向队列,目的在于首尾均可进行修改。
First Element (Head)
Last Element (Tail)
Throws exception
Special value
Throws exception
Special value
Insert
Remove
Examine
好了,看一下关于FIFO的Queue的抽象类AbstractQueue,它提供了一个关于Queue的实现骨架,它也继承了AbstractCollection中的行为,主要是针对插入、删除和检查是否属于抛异常还是返回值的分别实现。
下面就是其具体实现类PriorityQueue,其本质是一个堆实现的优先队列,那么针对它的插入与删除时间复杂度均为O(log(n)),要求是插入的元素不能够是NULL,且元素需要实现Comparable接口,或者给定一个Comparator的实现,否则抛出一个ClassCastException,如果直插一个元素(没有Comparable与Comparator)的话,没问题,但是针对集合,谁直插一个元素呢。。。
PriorityQueue与它的迭代器
在介绍List与Queue接口的同时实现者LinkedList的时候,必须要看一下AbstractList的一个子抽象类AbstractSequentialList,主要也是提供了一个框架,利用ListIterator完成的Get、ADD、Set和Remove方法。
接着LinkedList,它继承了AbstractSequentialList但是实现了Deque与List接口,其实它本质就是List的一个链表实现,但是该链表是双向链表,且允许插入NULL值。
针对于双向队列的数组实现便是ArrayDeque,其实现是利用了数组,在同时实现栈和队列的情况下,性能要比LinkedList好一些。
下面是这两个实现了双向队列(Deque)接口的实现。
上述基本把List接口与Queue接口讲述完毕了,总结一下。
List接口主要是针对下标来随机访问,而Queue是针对一定的顺序来操作集合。
两者均用抽象类加以实现了框架,诸如AbstractList、AbstractQueue而两者的直接实现者就是ArrayList、Vector和PriorityQueue,而在Queue里有点特殊,Deque双向队列的加入使队列两头均可操作,在此基础上的实现就是以数组为实现的ArrayDeque,而本质上是有序List(AbstractList的子类AbstractSequentialList)并用双向队列加以实现的LinkedList就这样出现了。
接着是Map接口,因为Set的主要实现均是借用了一个只有Key的Map,所以将Map提前来看一下,不过事先说明Map不属于Collection的子类。
先看一下,Map的族谱。
Map被定义成为三种集合视图,第一种就是不被允许重复的Key值的Set视图,第二种就是Value的集合视图,第三种就是它本身,一种Key-Value对应的视图。一般来说,Map不允许把它本身的引用传给自身做Key,如果这样的话,就栈溢出了。。。
可以看到,其中keySet方法就是提供了Key的Set视图,而values就是集合视图了。
我们可以马上猜到肯定有一个AbstractMap来实现基础的Map框架。
其中,AbstractMap的维护键值对的关键就是对Map.Entry接口实现的SimpleEntry与SimpleImmutableEntry,顾名思义后者是不支持删除键值对的。
接下来继承自AbstractMap的大名鼎鼎的HashMap,HashMap允许NULL的Key或者Value,在HashMap的实现中,对其性能影响存在两点,第一点就是HashMap初始化时桶(桶里放着一堆HashCode一样的对象)的数量,第二个就是装填因子,也就是HashMap中的元素数量和容积的商数,如果该商数过大的话,HashMap的效率就大打折扣了,因为首先定位桶的位置(根据HashCode)后又需要经过一堆Equals来判断,就没有发挥Hash的优势,所以推荐的比例是0.75,如果超过该数后,HashMap会重新分配容积,也就是增加桶的数量。
其中它维护了一个Entry的数组,也就是table,而默认装填因子为0.75,桶的数量为16个,Hash算法主要又hash和indexFor来完成,如过超过了数量与装填因子的乘积,那么就将桶的数量变为2倍。
其中比较关键的方法如put和get是我们常用的。
Put方法其实就是将<Key,Value>中的Key首先利用Hash算法找到桶,然后随机访问桶中Entry链表,如果找到K(Key.equals(K))的话,就替换Value并返回oldValue,如果没有找到,就将<Key,Value>也就是Entry链入这个桶中返回NULL,可以看出该Hash表的实现是链地址法。
Get方法反之亦然,找桶号,然后再在桶中找。
HashMap是非线程安全的,并且也是Unordered与UnSorted的。
顺带着看一下HashMap的子类,LinkedHashMap,这个Map很有意思,它主要有两点要说:
第一点,它如果是默认构造的话,那么它就是(insert-order)的,也就是说,它会维护一个所谓插入的顺序,按照从开始到结束的顺序。
第二点,如果是这样构造的话,LinkedHashMap(16 , 0.75F , true)的话,它就是(Access-order),按照访问顺序来判断迭代的顺序,最新访问(访问,插入)在最后,这样它就可以用来实现一个LRU(最近最少使用)。
看完这个LinkedHashMap之后,我们见识一下不常用的IdentifyHashMap,这个类判定元素是否在Map中就指依靠引用来判断,而不是Equals,所以解序列化之后的对象便找不到了,其内部实现没有利用链地址法,而是开放地址法中的以步长为2的线性探测法。
AbstractMap还有一个比较有趣的子类,也就是HashMap的兄弟,WeakHashMap,它用一个例子来说明吧。
Map map = new WeakHashMap();
Object x = new Object();
Object y = new Object();
map.put(x , y);
x = null;
System.gc();
System.out.println(map);
map空了,也就是说当Key这个对象一旦被销毁,那么Key所对应的Entry也就被销毁了,如果是HashMap的话,当然不会这样,因为HashMap中的Key保有一个指向对象的引用,那么这个Key所指向的对象是不会被回收的。
在看完AbstractMap及其子类后,我们顺带看一下遗留的Hashtable,一般讲它主要是线程安全且不允许NULL的Key与Value的,其实它是继承子Dictionary的,但是Dictionary这个抽象类其实就是个接口,完全没有实现一些共性工作,所以当JAVA在1.0的时候还是很稚嫩的。
还有,它的默认容积是11,还有它的Hash算法是取余,这点是素数和取余的标准实现,但是HashMap不是,HashMap是求与。
Map应该是属于Unorder也就是UnSorted的,那么我们想Map中的Key按照一定顺序,诸如自然顺序或者实现了Comparable接口的数据,也就是想有一个SortedMap,那么作为Map的子接口SortedMap便加入了。
它要求它的子类要么被插入的Key实现了Comparable,要么传一个Comparator实现给它,否则就会抛异常了。。。
在
1.6后,SortedMap有了一个子接口,NavigableMap,顾名思义,就是可以导航的Map,也就是在Key上可以上下跑。
lowerKey/Entry或者higherKey/Entry就是上下遍历。
最后NavigableMap的实现,TreeMap,就不多说了,红黑树的实现,查询、插入、删除的时间复杂度均为O(log(n))。
好了完成了Map的介绍,开头说了Map提供了值的列表视图,另一个就是Key的集合视图(排他性、唯一性)。
首先看一下Set接口。
在Set接口中的方法定义基本同Collection一致,完全重写了,但是它要求其实现按照数学定义中的集合来约束其实现。
老规矩,肯定有一个AbstractSet来实现共性,但是事实上AbstractSet只是实现了Equals和hashCode,equals是利用了AbstractCollection的containsAll方法来判断是否相等,而hashCode则是将集合中所有元素的hashCode和返回。
接下来AbstractSet的实现HashSet。
可以看出来,HashSet内部聚合了一个HashMap,利用了Map的Key的Set视图来完成。
然后HashSet的子类LinkedHashSet,其内部依然利用了一个双向链表来维护对Set的插入顺序,但是它不像LinkedHashMap有所谓的插入顺序和访问顺序两种,其本身就是重载的构造函数,然后返回了一个LinkedHashMap插入顺序的实现。
看来LinkedHashSet是属于Ordered的Set了,但是对于Set,我们也希望有一个Sorted的方式,所以就有了Set的子接口,SortedSet。
SortedSet需要它的元素实现了Comparable接口,或者它的实现类拥有一个Comparator的实现,在1.6以后,它又想进行上下导航,所以NavigableSet被加入。
按照对NavigableMap的理解lower和higher就是向下和向上导航的方法,则针对这个接口有一个具体实现,便是TreeSet。
TreeSet使用了一个TreeMap的Key视图来实现自己的Set视图。