Java
集合框架涉及到的類的繼承關係,從集合類的角度來看,它分爲兩個大類:
Collection
和
Map
。
Collection
是List
和Set
抽象出來的接口,它包含了這些集合的基本操做。html
List
接口一般表示一個列表(數組、隊列、鏈表,棧等),其中的元素能夠重複,經常使用的實現類爲ArrayList
、LinkedList
和Vector
。java
Set
接口一般表示一個集合,集合中的元素不容許重複(經過hashCode
和equals
函數保證),經常使用的實現類有HashSet
和TreeSet
,HashSet
是經過Map
中的HashMap
來實現的,而TreeSet
則是經過Map
中的TreeMap
實現的,另外TreeSet
還實現了SortedSet
接口,所以是有序的集合。算法
Set
接口存儲的是無序的、不重複的數據List
接口存儲的是有序的、能夠重複的數據Set
檢索效率低,刪除和插入效率高,插入和刪除不會引發元素位置改變。List
查找元素效率高,刪除和插入效率低,List
和數組相似,能夠動態增加,根據實際存儲的長度自動增加List
的長度。抽象類AbstractCollection
、AbstractList
和AbstractSet
分別實現了Collection
、List
和Set
接口,這就是在Java
集合框架中用的不少的適配器設計模式,用這些抽象類去實現接口,在抽象類中實現接口中的若干或所有方法,這樣下面的一些類只需直接繼承該抽象類,並實現本身須要的方法便可,而不用實現接口中的所有抽象方法。數據庫
Map
是一個映射接口,其中的每一個元素都是一個Key-Value
鍵值對,一樣抽象類AbstractMap
經過適配器模式實現了Map
接口的大部分函數,TreeMap
、HashMap
和WeakHashMap
等實現類都經過繼承AbstractMap
來實現。設計模式
Iterator
是遍歷集合的迭代器,它能夠用來遍歷Collection
,可是不能用來遍歷Map
。Collection
的實現類都實現了iterator()
函數,它返回一個Iterator
對象,用來遍歷集合,ListIterator
則專門用來遍歷List
。而Enumeration
則是JDK 1.0
時引入的,做用與Iterator
相同,但它的功能比Iterator
要少,它只能在Hashtable
、Vector
和Stack
中使用。數組
Arrays
和Collections
是用來操做數組、集合的兩個工具類,例如在ArrayList
和Vector
中大量調用了Arrays.Copyof()
方法,而Collections
中有不少靜態方法能夠返回各集合類的synchronized
版本,即線程安全的版本,固然了,若是要用線程安全的集合類,首選concurrent
併發包下的對應的集合類。安全
ArrayList
是基於一個能動態增加的數組實現,ArrayList
並非線程安全的,在多線程的狀況下能夠考慮使用Collections.synchronizedList(List T)
函數返回一個線程安全的ArrayList
類,也可使用併發包下的CopyOnWriteArrayList
類。數據結構
ArrayList<T>
類繼承於AbstractList<T>
,並實現瞭如下四個接口:多線程
List<T>
RandomAccess
:支持快速隨機訪問Cloneable
:可以被克隆Serializable
:支持序列化因爲ArrayList
是基於數組實現的,所以當咱們經過addXX
方法向數組中添加元素以前,都要保證有足夠的空間容納新的元素,這一過程是經過ensureCapacityInternal
來實現的,傳入的參數爲所要求的數組容量:併發
10
,那麼將要求的容量設爲10
2.5
倍8
,那麼調用hugeCapacity
方法,將數組的容量設爲整型的最大值Arrays.copyOf
將原有數組中的元素複製到新的數組中。Arrays.copyOf
最終會調用到System.arraycopy()
方法。該Native
函數實際上最終調用了C
語言的memmove()
函數,所以它能夠保證同一個數組內元素的正確複製和移動,比通常的複製方法的實現效率要高不少,很適合用來批量處理數組,Java
強烈推薦在複製大量數組元素時用該方法,以取得更高的效率。
ArrayList
中提供了兩種轉換爲靜態數組的方法:
Object[] toArray()
該方法有可能會拋出java.lang.ClassCastException
異常,若是直接用向下轉型的方法,將整個ArrayList
集合轉變爲指定類型的Array
數組,便會拋出該異常,而若是轉化爲Array
數組時不向下轉型,而是將每一個元素向下轉型,則不會拋出該異常,顯然對數組中的元素一個個進行向下轉型,效率不高,且不太方便。T[] toArray(T[] a)
該方法能夠直接將ArrayList
轉換獲得的Array
進行總體向下轉型,且從該方法的源碼中能夠看出,參數a
的大小不足時,內部會調用Arrays.copyOf
方法,該方法內部建立一個新的數組返回,所以對該方法的經常使用形式以下:public static Integer[] vectorToArray2(ArrayList<Integer> v) {
Integer[] newText = (Integer[])v.toArray(new Integer[0]);
return newText;
}
複製代碼
ArrayList
基於數組實現,能夠經過下標索引直接查找到指定位置的元素,所以查找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。
在查找給定元素索引值等的方法中,源碼都將該元素的值分爲null
和不爲null
兩種狀況處理,ArrayList
中容許元素爲null
。
LinkedList
是基於雙向循環鏈表實現的,除了能夠看成鏈表來操做外,它還能夠看成棧,隊列和雙端隊列來使用。
LinkedList
一樣是非線程安全的,在多線程的狀況下能夠考慮使用Collections.synchronizedList(List T)
函數返回一個線程安全的LinkedList
類,LinkedList
繼承於AbstractSequentialList
類,同時實現瞭如下四個接口:
List<T>
Deque
和Queue
:雙端隊列Cloneable
:支持克隆操做Serializable
:支持序列化LinkedList的
實現是基於雙向循環鏈表的,且頭結點voidLink
中不存放數據,因此它也不存在擴容的方法,只需改變節點的指向便可,每一個鏈表節點包含該節點的數據,以及前驅和後繼節點的引用,其定義以下所示:
private static final class Link<ET> {
//該節點的數據。
ET data;
//前驅節點和後繼節點。
Link<ET> previous, next;
Link(ET o, Link<ET> p, Link<ET> n) {
data = o;
previous = p;
next = n;
}
}
複製代碼
當須要根據位置尋找對應節點的數據時,會先比較待查找位置和鏈表的大小,若是小於一半,那麼從頭節點的後繼節點開始向後尋找,反之則從頭結點的前驅節點開始往前尋找,所以對於查找操做來講,它的效率很低,可是向頭尾節點插入和刪除數據的效率較高。
Vector
也是基於數組實現的,其容量可以動態增加。它的許多實現方法都加入了同步語句,所以是 線程安全 的。
Vector
繼承於AbstractList
類,而且實現了下面四個接口:
List<E>
RandomAccess
:支持隨機訪問Cloneable, java.io.Serializable
:支持Clone
和序列化。Vector
的實現大致和ArrayList
相似,它有如下幾個特色:
Vector
有四個不一樣的構造方法,無參構造方法的容量爲默認值10
,僅包含容量的構造方法則將容量增加量置爲0
。Vector
的容量不足以容納新的元素時,將進行擴容操做。首先判斷容量增加值是否爲0
,若是爲0
,那麼就將新容量設爲舊容量的兩倍,不然就設置新容量爲舊容量加上容量增加值。假如新容量還不夠,那麼就直接設置新量容量爲傳入的參數。null
進行處理,也就是說,Vector
容許元素爲null
。HashSet
具備如下特色:
null
,但只能放入一個null
當向HashSet
集合中存入一個元素時,HashSet
會調用該對象的hashCode()
方法來獲得該對象的hashCode
值,而後根據hashCode
值來決定該對象在HashSet
中存儲位置。 簡單的說,HashSet
集合判斷兩個元素相等的標準是兩個對象經過equals
方法比較相等,而且兩個對象的hashCode()
方法返回值相等。
注意,若是要把一個對象放入HashSet
中,重寫該對象對應類的equals
方法,也應該重寫其hashCode()
方法。其規則是若是兩個對象經過equals
方法比較返回true
時,其hashCode
也應該相同。另外,對象中用做equals
比較標準的屬性,都應該用來計算hashCode
的值。
TreeSet
是SortedSet
接口的惟一實現類,TreeSet
能夠確保集合元素處於排序狀態。TreeSet
支持兩種排序方式,天然排序 和 定製排序,其中天然排序爲默認的排序方式。
向TreeSet
中加入的應該是同一個類的對象。TreeSet
判斷兩個對象不相等的方式是兩個對象經過equals
方法返回false
,或者經過CompareTo
方法比較沒有返回0
。
天然排序使用要排序元素的CompareTo(Object obj)
方法來比較元素之間大小關係,而後將元素按照升序排列。 Java
提供了一個Comparable
接口,該接口裏定義了一個compareTo(Object obj)
方法,該方法返回一個整數值,實現了該接口的對象就能夠比較大小。
obj1.compareTo(obj2)
方法若是返回0
,則說明被比較的兩個對象相等,若是返回一個正數,則代表obj1
大於obj2
,若是是負數,則代表obj1
小於obj2
。若是咱們將兩個對象的equals
方法老是返回true
,則這兩個對象的compareTo
方法返回應該返回0
.
天然排序是根據集合元素的大小,以升序排列,若是要定製排序,應該使用Comparator
接口,實現int compare(T o1,T o2)
方法。
TreeSet
是二叉樹實現的,Treeset
中的數據是自動排好序的,不容許放入null
值。HashSet
是哈希表實現的,HashSet中
的數據是無序的,能夠放入null
,但只能放入一個null
,二者中的值都不能重複,就如數據庫中惟一約束。HashSet
要求放入的對象必須實現hashCode()
方法,放入的對象,是以hashcode()
碼做爲標識的,而具備相同內容的String
對象,hashcode
是同樣,因此放入的內容不能重複。可是同一個類的對象能夠放入不一樣的實例 。HashMap
是基於哈希表實現的,每個元素都是一個key-value
對,其內部經過單鏈表解決衝突問題,容量不足時,一樣會自動增加。HashMap
是非線程安全的,只是用於單線程環境下,多線程環境下能夠採用併發包下的ConcurrentHashMap
。
HashMap
繼承於AbstractMap
,同時實現了Cloneable
和Serializable
接口,所以,它支持克隆和序列化。
HashMap
是基於數組和鏈表來實現的:
Key
的hashCode
方法,計算出在數組中的座標。//計算 Key 的 hash 值。
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
//根據 Key 的 hash 值和鏈表的長度來計算下標。
int i = indexFor(hash, table.length);
複製代碼
Key/Value
封裝成HashMapEntry
數據結構,並將其做爲數組在該位置上的元素。不然就先從頭節點開始遍歷該鏈表,若是 知足下面的兩個條件,那麼就替換鏈表該節點的Value
//Value 替換的條件
//條件1:hash 值徹底相同
//條件2:key 指向同一塊內存地址 或者 key 的 equals 方法返回爲 true
(e.hash == hash && ((k = e.key) == key || key.equals(k)))
複製代碼
HashMapEntry
做爲鏈表的頭節點,而且也是數組在該位置上的元素,原先的頭節點則做爲它的後繼節點。HashMapEntry
的定義以下:
static class HashMapEntry<K,V> implements Map.Entry<K,V> {
//Key
final K key;
//Value
V value;
//後繼節點。
HashMapEntry<K,V> next;
//若是 Key 不爲 null ,那麼就是它的哈希值,不然爲0。
int hash;
//....
}
複製代碼
在第一小節中,咱們簡要的計算了HashMap
的總體結構,由此咱們能夠推斷出在設計的時候應當儘量地使元素均勻分佈,使得數組每一個位置上的鏈表儘量地短,避免從鏈表頭結點開始遍歷的過程。
而元素是否分佈均勻就取決於根據Key
的Hash
值計算數組下標的過程,首先咱們看一下Hash
值的計算,這裏首先調用對象的hashCode
方法,再經過二次Hash
算法得到一個Hash
值:
public static int secondaryHash(Object key) {
return secondaryHash(key.hashCode());
}
private static int secondaryHash(int h) {
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
複製代碼
以後,再經過這個計算出來Hash
值 與上當前數組長度減一 進行取餘,得到對應的數組下標:
hash & (tab.length - 1)
複製代碼
因爲HashMap
在擴容的時候,保證了數組的長度適中爲2
的n
冪,所以length - 1
的二進制表示始終爲全1
,所以進行&
操做的結果既保證了最終的結果不會超過數組的長度範圍,同時也保證了兩個Hash
值相同的元素不會映射到數組的同一位置,再加上上面二次Hash
的過程加上了高位的計算優化,從而使得數據的分佈儘量地平均。
理解了上面存儲的過程,讀取天然也就很好理解了,其實經過Key
計算數組下標,遍歷該位置上數組元素的鏈表進行查找的過程。
當HashMap
中的元素愈來愈多的時候,hash
衝突的概率也就愈來愈高,由於數組的長度是固定的,因此爲了提升查詢的效率,就要對HashMap
的數組進行擴容。
當HashMap
中的元素個數超過數組大小 * loadFactor
時,loadFactor
的默認值爲0.75,就會進行數組擴容,擴容後的大小爲原先的2
倍,而後從新計算每一個元素在數組中的位置,原數組中的數據必須從新計算其在新數組中的位置,並放進去。
擴容是一個至關耗費性能的操做,所以若是咱們已經預知HashMap中元素的個數,那麼預設元素的個數可以有效的提升HashMap
的性能。
HashMap
並非線程安全的,所以若是在使用迭代器的過程當中有其餘線程修改了map
,那麼將拋出ConcurrentModificationException
,這就是所謂fail-fast
策略。
這一策略在源碼中的實現是經過modCount
域,modCount
顧名思義就是修改次數,對HashMap
內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的expectedModCount
。
在迭代過程當中,判斷modCount
跟expectedModCount
是否相等,若是不相等就表示已經有其餘線程修改了Map
,那麼就會經過下面的方法拋出異常:
HashMapEntry<K, V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
//省略...
}
複製代碼
modCount
聲明爲volatile
,保證了多線程狀況下的內存可見性。
在迭代器建立以後,若是從結構上對映射進行修改,除非經過迭代器自己的remove
方法,其餘任什麼時候間任何方式的修改,迭代器都將拋出ConcurrentModificationException
。所以,面對併發的修改,迭代器很快就會徹底失敗,而不保證在未來不肯定的時間發生任意不肯定行爲的風險
HashTable
常常用來和HashMap
進行比較,前者是線程安全的,然後者則不是,其實HashTable
要比HashMap
出現得要早,它實現線程安全的原理並無什麼高級的地方,只不過是在寫入和讀取時加上了synchronized
關鍵字用於同步,而且也不推薦使用了,由於在併發包中提供了更好的解決方案ConcurrentHashMap
,它內部的實現比較複雜,以後咱們再經過一篇文章進行分析。
這裏簡單地總結一下它和HashMap
之間的區別:
HashTable
基於Dictionary
類,而HashMap
是基於AbstractMap
。Dictionary
是任何可將鍵映射到相應值的類的抽象父類,而AbstractMap
基於 Map
接口的實現,它以最大限度地減小實現此接口所需的工做。HashMap
的key
和value
都容許爲null
,而Hashtable
的key
和value
都不容許爲null
。HashMap
遇到key
爲null
的時候,調用putForNullKey
方法進行處理,而對value
沒有處理,Hashtable
遇到null
,直接返回 NullPointerException
。Hashtable
方法是同步,而HashMap
則不是。咱們能夠看一下源碼,Hashtable
中的幾乎全部的public
的方法都是synchronized
的,而有些方法也是在內部經過synchronized
代碼塊來實現。因此有人通常都建議若是是涉及到多線程同步時採用HashTable
,沒有涉及就採用HashMap
,可是在 Collections
類中存在一個靜態方法:synchronizedMap()
,該方法建立了一個線程安全的Map
對象,並把它做爲一個封裝的對象來返回。TreeMap
是一個有序的key-value
集合,它是經過 紅黑樹 實現的。TreeMap
繼承於AbstractMap
,因此它是一個Map
,即一個key-value
集合。TreeMap
實現了NavigableMap
接口,意味着它支持一系列的導航方法,好比返回有序的key集合。TreeMap
實現了Cloneable
和Serializable
接口,意味着它能夠被Clone
和序列化。
TreeMap
基於紅黑樹實現,該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator
進行排序,具體取決於使用的構造方法。TreeMap
的基本操做containsKey
、get
、put
和remove
的時間複雜度是log(n)
,另外,TreeMap
是非同步的, 它的iterator
方法返回的迭代器是Fail-Fastl
的。
LinkedHashMap
是HashMap
的子類,與HashMap
有着一樣的存儲結構,但它加入了一個雙向鏈表的頭結點,將全部put
到LinkedHashmap
的節點一一串成了一個雙向循環鏈表,所以它保留了節點插入的順序,可使節點的輸出順序與輸入順序相同。LinkedHashMap
能夠用來實現LRU
算法。LinkedHashMap
一樣是非線程安全的,只在單線程環境下使用。LinkedHashSet
是具備可預知迭代順序的Set
接口的哈希表和連接列表實現。此實現與HashSet
的不一樣之處在於,後者維護着一個運行於全部條目的雙重連接列表。此連接列表定義了迭代順序,該迭代順序可爲插入順序或是訪問順序。
LinkedHashSet
的實現:對於LinkedHashSet
而言,它繼承與HashSet
、又基於LinkedHashMap
來實現的。