JDK集合類原始碼分析
本文總結一下JDK中集合類的實現。首先看下集合類的繼承圖:
可看出,介面主要有Collection和Map兩大主線,其中Collection又有List和Set兩個分支。List是一個有序的佇列,每一個元素都有它的索引;而Set是一個不允許有重複元素的集合。Map是一個key-value鍵值對對映,AbstractMap是個抽象類,它實現了Map介面中的大部分API。Hashtable繼承於Dictionary,也實現了Map介面。
下面逐個說明:
ArrayList
ArrayList 是一個數組佇列,相當於動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List,RandomAccess,Cloneable,java.io.Serializable介面。ArrayList不是執行緒安全的。
ArrayList包含兩個重要的物件:elementData和size。
size是動態陣列的實際大小,這裡主要說說elementData。它是Object[]型別的陣列,儲存了新增到ArrayList中的元素。elementData是個動態陣列,可通過建構函式ArrayList(int initialCapacity)來執行它的初始容量為initialCapacity;如果通過不含引數的建構函式ArrayList()來建立ArrayList,則elementData的容量預設是10。當ArrayList容量不足以容納全部元素時,ArrayList會重新設定容量=
(原始容量x3) / 2 + 1。
ArrayList轉物件陣列方式:Integer[] newText = (Integer[])v.toArray(new Integer[0]);
LinkedList
LinkedList是繼承於AbstractSequentialList的雙向連結串列,可被當作堆疊、佇列或雙端佇列進行操作。實現了List,Deque,Cloneable,java.io.Serializable介面。LinkedList不是執行緒安全的。
LinkedList包含兩個重要的成員:header和size。
size是雙向連結串列中節點的個數,而header是雙向連結串列的表頭,它是雙向連結串列節點所對應的類Entry的例項。Entry中包含成員變數: previous、next和element。其中,previous是該節點的上一個節點,next是該節點的下一個節點,element是該節點所包含的值。
LinkedList是通過雙向連結串列去實現的,因此它的順序訪問會非常高效,而隨機訪問效率比較低。它的索引是通過一個計數索引值來實現的。例如,當呼叫get(int location)時,首先會比較“location”和“雙向連結串列長度的1/2”;若前者大,則從連結串列頭開始往後查詢,直到location位置;否則,從連結串列末尾開始先前查詢,直到location位置。
Vector
Vector是向量佇列,繼承於AbstractList,實現了List、RandomAccess和Cloneable介面。Vector是執行緒安全的。
Vector包含了3個成員變數:elementData、elementCount和capacityIncrement。
elementData是Object[]型別的陣列,它儲存了新增到Vector中的元素。如果初始化Vector時,沒指定elementData的大小,則預設大小10。隨著Vector中元素的增加,Vector的容量也會動態增長。
elementCount是動態陣列的實際大小。
capacityIncrement是動態陣列的增長係數。如果在建立Vector時指定了capacityIncrement的大小,則每次當Vector中動態陣列容量增加的大小都是capacityIncrement。
Vector的執行緒安全,是通過基本在所有方法上都加了synchronized關鍵字實現的。
HashMap
HashMap是一個散列表,儲存的內容是key-value鍵值對對映。繼承於AbstractMap,實現了Map、Cloneable和java.io.Serializable介面。HashMap不是執行緒安全的。HashMap的key、value都可以為null,對映不是有序的。
HashMap包括幾個重要的成員變數:table、size、threshold、loadFactor和modCount。
table是一個Entry[]陣列型別,而Entry實際上就是一個單向連結串列。雜湊表的key-value鍵值對都是儲存在Entry陣列中的。
size是HashMap的大小,是HashMap儲存的鍵值對的數量。
threshold是HashMap的閾值,用於判斷是否需要調整HashMap的容量。threshold=容量 * 負載因子,當HashMap中儲存資料的數量達到threshold時,就需要將HashMap的容量加倍。
loadFactor即負載因子,雜湊表在其容量自動增加之前可以達到多滿的一種尺度。預設為0.75。
modCount是用來實現fail-fast機制的。
HashMap是先呼叫Object的hashCode方法(native實現),然後indexFor(與table陣列長度-1取模)得到對應陣列下標。
Hash衝突:當put()操作的key與entry陣列有衝突時,新的entry仍然會被安放在對應的索引下標內,替換原有的值。同時,為了保證舊值不丟失,會將新的entry的next指向舊值。這就實現了在一個數組索引空間記憶體放多個值。為了避免衝突,需要將hashCode()和hash()方法實現的足夠好,儘可能減少衝突的產生。
HashMap將key為null的元素都放在table的位置0處。
HashTable
HashTable也是一個散列表,儲存的內容是鍵值對(key-value)對映。繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable介面。Hashtable是執行緒安全的。Hashtable的key、value都不可以為null,對映不是有序的。
HashMap包括幾個重要的成員變數:table、size、threshold、loadFactor和modCount。基本同HashMap一致,不再贅述。
HashTable的執行緒安全,是通過基本在所有方法上都加了synchronized關鍵字實現的。
LinkedHashMap
LinkedHashMap是一個有序的key-value集合,繼承於HashMap,實現了Map介面。按插入的順序排序,不是執行緒安全的,不允許使用 null 值和 null 鍵。
LinkedHashMap底層使用雜湊表與雙向連結串列來儲存所有元素。重新定義了陣列中儲存的元素 Entry,該 Entry 除了儲存當前物件的引用外,還儲存了其上一個元素 before 和下一個元素 after 的引用,從而在雜湊表的基礎上又構成了雙向連結列表。
初始化時增加 accessOrder 引數,預設為 false,代表按照插入順序進行迭代;設定為 true時代表以訪問順序進行迭代。
在進行put等元素操作的時候,LinkedHashMap會對自己維護的雙向連結串列執行相同的操作。
TreeMap
TreeMap是一個有序的key-value集合,通過紅黑樹實現。繼承於AbstractMap,實現了NavigableMap(支援一系列的導航方法)、Cloneable和java.io.Serializable介面。排列順序根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法。TreeMap不是執行緒安全的。
TreeMap包含幾個重要的成員變數: root、size和comparator。
root是紅黑數的根節點。它是Entry型別,Entry是紅黑數的節點,它包含了紅黑數的6個基本組成成分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)。Entry節點根據key進行排序,Entry節點包含的內容為value。紅黑數排序時,根據Entry中的key進行排序;Entry中的key比較大小是根據比較器comparator來進行判斷的。
size是紅黑數中節點的個數。
comparator是鍵值排序的比較實現。
HashSet
HashSet是一個沒有重複元素的集合,由HashMap實現的,不保證元素的順序,而且HashSet允許使用 null 元素。HashSet不是執行緒安全的。
TreeSet
TreeSet是一個有序的集合,作用是提供有序的Set集合。繼承於AbstractSet抽象類,實現了NavigableSet<E>(支援一系列的導航方法)、Cloneable和java.io.Serializable介面。TreeSet是基於TreeMap實現的。TreeSet中的元素支援2種排序方式:自然排序 或者 根據建立TreeSet 時提供的 Comparator 進行排序。這取決於使用的構造方法。TreeSet不是執行緒安全的。