首先,看一下集合架構的最根基的介面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
add(e)
offer(e)
Remove
remove()
poll()
Examine
element()
peek()
一般是FIFO的,尾部插入,頭部刪除。
那麼相對於Queue就有一個AbstractQueue,但是我們先來看看Queue的子介面Deque(double ended queue),其實就是一個雙向隊列,目的在於首尾均可進行修改。
First Element (Head)
Last Element (Tail)
Throws exception
Special value
Throws exception
Special value
Insert
addFirst(e)
offerFirst(e)
addLast(e)
offerLast(e)
Remove
removeFirst()
pollFirst()
removeLast()
pollLast()
Examine
getFirst()
peekFirst()
getLast()
peekLast()
好了,看一下關於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視圖。
(from:http://weipeng2k.javaeye.com/blog/)