本文是「最最最多見Java面試題總結」系列第三週的文章。 主要內容: 1. Arraylist 與 LinkedList 異同 2. ArrayList 與 Vector 區別 3. HashMap的底層實現 4. HashMap 和 Hashtable 的區別 5. HashMap 的長度爲何是2的冪次方 6. HashSet 和 HashMap 區別 7. ConcurrentHashMap 和 Hashtable 的區別 8. ConcurrentHashMap線程安全的具體實現方式/底層具體實現 9. 集合框架底層數據結構總結
本文會同步更新在我開源的Java學習指南倉庫 Java-Guide (一份涵蓋大部分Java程序員所須要掌握的核心知識,正在一步一步慢慢完善,期待您的參與)中,地址:https://github.com/Snailclimb/Java-Guide,歡迎star、issue、pr。php
add(E e)
方法的時候, ArrayList 會默認在將指定的元素追加到此列表的末尾,這種狀況時間複雜度就是O(1)。可是若是要在指定位置 i 插入和刪除元素的話(add(int index, E element)
)時間複雜度就爲 O(n-i)。由於在進行上述操做的時候集合中第 i 和第 i 個元素以後的(n-i)個元素都要執行向後位/向前移一位的操做。 ② LinkedList 採用鏈表存儲,因此插入,刪除元素時間複雜度不受元素位置的影響,都是近似 O(1)而數組爲近似 O(n)。get(int index)
方法)。雙向鏈表也叫雙鏈表,是鏈表的一種,它的每一個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。因此,從雙向鏈表中的任意一個結點開始,均可以很方便地訪問它的前驅結點和後繼結點。通常咱們都構造雙向循環鏈表,以下圖所示,同時下圖也是LinkedList 底層使用的是雙向循環鏈表數據結構。html
Vector類的全部方法都是同步的。能夠由兩個線程安全地訪問一個Vector對象、可是一個線程訪問Vector的話代碼要在同步操做上耗費大量的時間。java
Arraylist不是同步的,因此在不須要保證線程安全時時建議使用Arraylist。git
JDK1.8 以前 HashMap 由 數組+鏈表 組成的(「鏈表散列」 即數組和鏈表的結合體),數組是 HashMap 的主體,鏈表則是主要爲了解決哈希衝突而存在的(HashMap 採用 「拉鍊法也就是鏈地址法」 解決衝突),若是定位到的數組位置不含鏈表(當前 entry 的 next 指向 null ),那麼對於查找,添加等操做很快,僅需一次尋址便可;若是定位到的數組包含鏈表,對於添加操做,其時間複雜度依然爲 O(1),由於最新的 Entry 會插入鏈表頭部,急須要簡單改變引用鏈便可,而對於查找操做來說,此時就須要遍歷鏈表,而後經過 key 對象的 equals 方法逐一比對查找.程序員
所謂 「拉鍊法」 就是將鏈表和數組相結合。也就是說建立一個鏈表數組,數組中每一格就是一個鏈表。若遇到哈希衝突,則將衝突的值加到鏈表中便可。
相比於以前的版本, JDK1.8以後在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(默認爲8)時,將鏈表轉化爲紅黑樹,以減小搜索時間。github
TreeMap、TreeSet以及JDK1.8以後的HashMap底層都用到了紅黑樹。紅黑樹就是爲了解決二叉查找樹的缺陷,由於二叉查找樹在某些狀況下會退化成一個線性結構。
推薦閱讀:面試
synchronized
修飾。(若是你要保證線程安全的話就使用 ConcurrentHashMap 吧!);爲了能讓 HashMap 存取高效,儘可能較少碰撞,也就是要儘可能把數據分配均勻,每一個鏈表/紅黑樹長度大體相同。這個實現就是把數據存到哪一個鏈表/紅黑樹中的算法。算法
這個算法應該如何設計呢?數組
咱們首先可能會想到採用%取餘的操做來實現。可是,重點來了:「取餘(%)操做中若是除數是2的冪次則等價於與其除數減一的與(&)操做(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。」 而且 採用二進制位操做 &,相對於%可以提升運算效率,這就解釋了 HashMap 的長度爲何是2的冪次方。安全
ConcurrentHashMap 和 Hashtable 的區別主要體如今實現線程安全的方式上不一樣。
二者的對比圖:
圖片來源:http://www.cnblogs.com/chengxiao/p/6842045.html
HashTable:
JDK1.7的ConcurrentHashMap:
JDK1.8的ConcurrentHashMap(TreeBin: 紅黑二叉樹節點 Node: 鏈表節點):
首先將數據分爲一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據時,其餘段的數據也能被其餘線程訪問。
ConcurrentHashMap 是由 Segment 數組結構和 HahEntry 數組結構組成。
Segment 實現了 ReentrantLock,因此 Segment 是一種可重入鎖,扮演鎖的角色。HashEntry 用於存儲鍵值對數據。
static class Segment<K,V> extends ReentrantLock implements Serializable { }
一個 ConcurrentHashMap 裏包含一個 Segment 數組。Segment 的結構和HashMap相似,是一種數組和鏈表結構,一個 Segment 包含一個 HashEntry 數組,每一個 HashEntry 是一個鏈表結構的元素,每一個 Segment 守護着一個HashEntry數組裏的元素,當對 HashEntry 數組的數據進行修改時,必須首先得到對應的 Segment的鎖。
ConcurrentHashMap取消了Segment分段鎖,採用CAS和synchronized來保證併發安全。數據結構跟HashMap1.8的結構相似,數組+鏈表/紅黑二叉樹。
synchronized只鎖定當前鏈表或紅黑二叉樹的首節點,這樣只要hash不衝突,就不會產生併發,效率又提高N倍。