Collection集合概覽

Container taxonomy

Container taxonomy

上圖是Java容器分類圖,初看這張圖可能會有點龐大,但是實際上只有三個組件:Map, List, and Set。
我們解讀一下其中的組件:
1.黑色粗線框所代表的是我們常用的容器組件,包括HashMap、HashSet、ArrayList和LinkedList。
2.點框代表的是接口。
3.虛線框代表了抽象類。
4.實線框是常規的實現類。
5.空心箭頭代表實現類實現了一個接口或者繼承了一個抽象類。
6.實心箭頭表示這個類可以生成箭頭指向的那個類,例如,任何一個Collection對象可以生成一個Iterator對象,任何一個List對象可以生成一個ListIterator對象。

1.Collection接口

Collection接口是最基本的集合接口,它不提供直接的實現,JDK提供的實現類都是繼承自Collection的兩個”子接口”,即List和Set
在Java中所有實現了Collection接口的類(抽象類除外)都必須提供兩套標準的構造函數:一個是無參構造函數,用於創建一個空的Collection;另一個是帶有Collection參數的有參構造函數,用於創建一個新的Collection,這個新的Collection與傳入進來的Collection具備相同的元素。

1.1 List接口
List代表了有序的Collection,即它用某種特定的插入順序來維護元素順序。實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

1.1.1 ArrayList
(1) ArrayList是一個動態數組,它允許插入任何符合規則的元素甚至是null。
(2) 每一個ArrayList的初始容量是10,隨着容器中的元素不斷增加,容器的大小也會隨着增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作。擴容時會將容器大小設爲當前容量的1.5倍。所以,倘若我們明確知道所需插入元素的size,最好指定一個初始容量值,避免過多得進行擴容操作而浪費時間、效率。

int newCapacity = oldCapacity + (oldCapacity >> 1);

(3) add操作的複雜度是O(n),同時ArrayList是非同步的。

1.1.2 LinkedList
(1) LinkedList是一個雙向鏈表。它除了有ArrayList的基本操作方法外,還額外提供了獲取、刪除、插入到LinkedList首部或尾部的方法等。
(2) 由於實現方式不同,LinkedList不能隨機訪問,在列表中索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。但在List中進行插入和刪除操作的代價會比較低。
(3) LinkedList也是非同步的。如果多個線程同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在創建List時構造一個同步的List:

List list = Collections.synchronizedList(new LinkedList(...));

1.1.3 Vector
與ArrayList相似,它的操作與ArrayList幾乎一樣。但是Vector是同步的。
另外,Vector擴容是一倍增長,而ArrayList是0.5倍增長。

        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);

1.1.4 Stack
Stack繼承自Vector,實現一個後進先出的堆棧。Stack提供5個額外的方法使得Vector得以被當作堆棧使用。push()把元素壓入棧頂,pop()移除棧頂元素,peek()得到棧頂的元素,empty()測試堆棧是否爲空,search()返回一個元素在堆棧中的位置。

1.2 Set接口
Set是一種不包括重複元素的Collection。它維持它自己的內部排序,所以隨機訪問沒有任何意義。與List一樣,它同樣允許null的存在但是隻能有一個。
Set根據equals方法判斷兩個對象是否相同。也就是說,只要兩個對象用equals方法比較返回true,Set就不會接受這兩個對象。
有意思的是,幾乎所有的Set實現都是Map,例如TreeSet基於TreeMap實現,HashSet基於HashMap實現。
實現了Set接口的集合有:TreeSet、HashSet、LinkedHashSet。

1.2.1 TreeSet
TreeSet可以確保集合元素處於排序狀態,內部以TreeMap來實現。TreeSet支持兩種排序方式,自然排序和定製排序,其中自然排序爲默認的排序方式。向TreeSet中加入的應該是同一個類的對象。

    public TreeSet(Comparator<? super E> comparator) {
        // 定製排序應該使用Comparator接口,實現int compare(T o1,T o2)方法
        this(new TreeMap<>(comparator));
    }

1.2.2 HashSet
HashSet堪稱查詢速度最快的集合,因爲其內部是以HashCode來實現的。它內部元素的順序是由哈希碼來決定的,所以它不保證set的迭代順序,特別是它不保證該順序恆久不變。HashSet還提供了可以指定初始容量和負載因子的構造函數。

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

1.2.3 LinkedHashSet
LinkedHashSet同樣根據元素的HashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。LinkedHashSet在迭代訪問Set中的全部元素時,性能比HashSet好,但是插入時性能稍微遜色於HashSet。

2.Map接口

Map是由一系列鍵值對組成的集合,提供了key到Value的映射,它保證了key與value之間的一一對應關係。實現Map接口的有:HashMap、TreeMap、HashTable、LinkedHashMap、WeakHashMap。

2.1 HashMap
(1) HashMap底層是哈希表數據結構,查找對象時通過哈希函數計算其位置。它是爲快速查詢而設計的,其內部定義了一個hash表數組(table),元素會通過哈希轉換函數將元素的哈希地址轉換成數組中存放的索引,如果有衝突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來。通過查看HashMap$Entry的源碼它是一個單鏈表結構。

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

(2) HashMap是非同步的,並且允許null value和null key。由於作爲key的對象將通過計算其散列函數來確定與之對應的value的位置,因此任何作爲key的對象都必須實現HashCode和equals()方法。
(3) 將HashMap視爲Collection時(values()方法可返回Collection),其迭代子操作時間開銷和HashMap的容量成比例。因此,如果迭代操作的性能相當重要的話,不要將HashMap的初始化容量設得過高,或者load factor過低。

2.2 TreeMap
TreeMap底層是red-black Tree(紅黑樹),它可用於給Map集合中的鍵進行排序。同時它不是線程安全的。

    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

2.3 HashTable
與HashMap的不同在於它是線程安全的。但是,HashMap可以實現訪問同步,HashMap的功能比Hashtable的功能更多,而且Hashtable基於一個陳舊的類Dictionary的,所以Hashtable基本棄用了。

2.4 LinkedHashMap
(1) 與HashMap相比,LinkedHashMap維護的是一個具有雙重鏈表的HashMap。LinkedHashMap支持2種排序,一種是插入排序,一種是使用排序。
(2) LinkedHashMap內部含有一個header,來記錄元素插入的順序或者是元素被訪問的順序。利用這個線性結構的對象,可以幫助記錄entry加入的前後順序或者記錄entry被訪問的頻率(最少被訪問的entry靠前,最近訪問的entry靠後)。

private transient Entry<K,V> header;

2.5 WeakHashMap
WeakHashMap是一種改進的HashMap,它對key實行”弱引用”,如果一個key不再被外部所引用,那麼該key可以被GC回收。
例如:聲明瞭兩個Map對象,一個是HashMap,一個是WeakHashMap,同時向兩個map中放入a、b兩個對象,當HashMap中的a對象被remove掉並且將a、b都指向null時,WeakHashMap中的a將自動被回收掉。出現這個狀況的原因是,對於a對象而言,當HashMap中的a對象被remove掉並且將a指向null後,除了WeakHashMap中還保存a外已經沒有指向a的指針了,所以WeakHashMap會自動捨棄掉a,而對於b對象雖然指向了null,但HashMap中還有指向b的指針,所以WeakHashMap將會保留。

3.異同比較

3.1 HashMap vs HashTable
(1) HashTable繼承了Dictionary類,而HashMap繼承了AbstractMap。Dictionary是任何可將鍵映射到相應值的類的抽象父類,而AbstractMap是基於Map接口的骨幹實現,它以最大限度地減少實現此接口所需的工作。

(2) HashMap可以允許存在一個爲null的key和任意個爲null的value,但是HashTable中的key和value都不允許爲null。

    // 當HashMap遇到爲null的key時,它會調用putForNullKey方法來進行處理。對於value沒有進行任何處理,只要是對象都可以。
    if (key == null)
        return putForNullKey(value);

    // 當HashTable遇到null時,他會直接拋出NullPointerException異常信息。
    if (value == null) {
        throw new NullPointerException();
    }

(3) Hashtable的方法是同步的,而HashMap的方法不是。有人建議,如果涉及到多線程同步問題時採用Hashtable,沒有涉及就採用HashMap,但Collections類提供了一個靜態方法:synchronizedMap(),該方法創建了一個線程安全的Map對象,並把它作爲一個封裝的對象來返回,所以,通過Collections類的synchronizedMap方法是可以同步訪問潛在的HashMap的。

    // HashMap的put方法
    public V put(K key, V value) {

    // Hashtable的put方法
    public synchronized V put(K key, V value) {

(4) Hashtable默認初始容量11,HashMap默認初始容量是16。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章