Hashmap
JDK1.7中
使用一個Entry數組來存儲數據,用key的hashcode取模來決定key會被放到數組里的位置,如果hashcode相同,或者hashcode取模后的結果相同,那么這些key會被定位到Entry數組的同一個格子里,這些key會形成一個鏈表;
在hash函數特別差的情況下,比如說所有key的hashcode都相同,這個鏈表可能會很長,那么put/get操作都可能需要遍歷這個鏈表,也就是最差情況下時間復雜度為O(n)。
JDK1.8中
使用一個Node數組來存儲數據,但是這個Node可能是鏈表結構,也可能是紅黑樹結構;如果插入的元素key的hashcode值相同,那么這些key也會被定位到Node數組的同一個格子里,如果不超過8個使用鏈表存儲,超過8個,會調用treeifyBin函數,將鏈表轉換為紅黑樹。那么即使所有key的hashcode完全相同,由於紅黑樹的特點,查找某個特定元素,也只需要O(logn)的開銷。
紅黑樹:
每個節點不是紅的就是黑的;
根節點是黑的;
葉節點都是黑色,葉子節點指的是為空的節點;
如果一個節點是紅色的,那么子節點必須為黑色;
從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
根節點是黑的;
葉節點都是黑色,葉子節點指的是為空的節點;
如果一個節點是紅色的,那么子節點必須為黑色;
從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
ConcurrentHashMap
JDK1.7中
使用segment+hashentry來實現。ConcurrentHashMap在初始化時,計算出segement數組的大小ssize和每個segment中HashEntry數組的大小cap,並初始化segement數組的第一個元素,其中ssize大小為2的冪次方,默認為16,cap大小也是2的冪次方,最小值為2。segement在實現上繼承了ReetrantLock,這樣就自帶了鎖的功能。
put實現:當執行put方法插入數據的時候,先通過hash值在segment中找到對應的位置,然后如果相應位置的segment還未初始化,則通過CAS進行賦值,接着執行segment對象的put方法通過加鎖機制插入數據。
size實現:因為concurrenthashmap是可以並發插入數據的,所以准確計算元素時有一定的難度,所以是先采用不加鎖的方式,連續計算元素的個數,最多計算3次,如果前后兩次計算結果相同,那么說明元素個數是准確的;如果前后兩次計算結果都不相同,則給每個segment加鎖,再計算一次元素的個數。
JDK1.8中
放棄了segment的設計,取而代之的是Node+CAS+Synchronized來保證並發安全。只有在執行第一次put方法時,才會調用initTable()初始化Node數組。
put實現:
如果Node還未初始化,那么通過CAS插入相應的數據;
如果Node不為空,且當前該節點不處於移動狀態,那么對該節點加synchronized鎖,如果該節點hash不小於0,則遍歷鏈表更新節點或者插入新節點;
如果該節點是TreeBin類型的節點,說明是紅黑樹結構,則通過putTreeVal方法往紅黑樹中插入節點;
如果binCount不為0,說明put操作對數據產生了影響,如果當前鏈表的個數達到8個,則通過treeifyBin方法轉化為紅黑樹,如果oldVal不為空,說明是一次更新操作,沒有對元素個數產生影響,則直接返回舊值;
如果插入的是一個新節點,則執行addCount()方法嘗試更新元素個數baseCount;
size實現:1.8中使用一個volatile類型的變量baseCount記錄元素的個數,當插入新數據或則刪除數據時,會通過addCount()方法更新baseCount。因為元素個數保存baseCount中,部分元素的變化個數保存在CounterCell數組中,通過累加baseCount和CounterCell數組中的數量,即可得到元素的總個數。
如果Node不為空,且當前該節點不處於移動狀態,那么對該節點加synchronized鎖,如果該節點hash不小於0,則遍歷鏈表更新節點或者插入新節點;
如果該節點是TreeBin類型的節點,說明是紅黑樹結構,則通過putTreeVal方法往紅黑樹中插入節點;
如果binCount不為0,說明put操作對數據產生了影響,如果當前鏈表的個數達到8個,則通過treeifyBin方法轉化為紅黑樹,如果oldVal不為空,說明是一次更新操作,沒有對元素個數產生影響,則直接返回舊值;
如果插入的是一個新節點,則執行addCount()方法嘗試更新元素個數baseCount;
size實現:1.8中使用一個volatile類型的變量baseCount記錄元素的個數,當插入新數據或則刪除數據時,會通過addCount()方法更新baseCount。因為元素個數保存baseCount中,部分元素的變化個數保存在CounterCell數組中,通過累加baseCount和CounterCell數組中的數量,即可得到元素的總個數。
PS.兩者在1.8之前都是頭插,1.8之后都是尾插。