Hashmap 實現方式 jdk1.7 和 1.8區別


hashmap 是很常用的一種集合框架,其底層實現方式在 jdk1.7和 jdk1.8中卻有很大區別,今天我們通過看源碼的方式來研究下它們之間的區別。

hashmap 是用來存儲數據的,它底層數據結構是數組,數組中元素是鏈表或紅黑樹,通過對 key 進行哈希計算等操作后得到數組下標,把 value 等信息放在鏈表或紅黑樹存在此位置。如果兩個不同的 key 運算后獲取的數組下標一致,就出現了哈希沖突。數組默認長度是16,如果實際數組長度超過一定的值,就會進行擴容。在我看來,1.7和1.8主要在處理哈希沖突和擴容問題上區別比較大。

 

首先看下 jdk1.7

存放數據的數組

 

 

 put 方法源碼,我都加了注釋

 public V put(K key, V value) {
     //數組為空就進行初始化
if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value);
     //key 進行哈希計算
int hash = hash(key);
     //獲取數組下標
int i = indexFor(hash, table.length);
     //如果此下標有值,遍歷鏈表上的元素,key 一致的話就替換 value 的值
for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++;
     //新增一個key addEntry(hash, key, value, i);
return null; }
addEntry源碼
 void addEntry(int hash, K key, V value, int bucketIndex) {
     //數組長度大於閾值且存在哈希沖突(即當前數組下標有元素),就將數組擴容至2倍
if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }

繼續看  createEntry 源碼

void createEntry(int hash, K key, V value, int bucketIndex) {
     //此位置有元素,就在鏈表頭部插入新元素(頭插法) Entry
<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }

這里可以看到 jdk 1.7擴容的條件是 數組長度大於閾值且存在哈希沖突,由此我們可以想象,默認長度為16的情況下,數組最多可以存27個元素后才擴容,原因是在一個下標存儲12個元素后(閾值為12),在剩下的15個下標各存一個元素,最多就可存27個元素,當然這種是很偶然的情況。不過也可以看到 jdk1.7 中,這個閾值的作用並不是特別的大,並不是超過閾值就一定會擴容。

 

下面來看看 jdk1.8 的源碼

存放數據的數組

 

 

 

這里 hash算法發生了變化,不過這不是重點,我們繼續看下 put 的源碼

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

putVal 源碼

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
     //數組為空就初始化
if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;
     //當前下標為空,就直接插入
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k;
       //key 相同就覆蓋原來的值
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;
       //樹節點插入數據
else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) {
            //鏈表,尾插法插入數據
if ((e = p.next) == null) { p.next = newNode(hash, key, value, null);
              //鏈表長度超過8,就把鏈表轉為紅黑樹
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; }
            //key相同就覆蓋原來的值
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount;
     //數組長度大於閾值,就擴容
if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }

繼續看下 treeifyBin 的源碼

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
     //鏈表轉為紅黑樹時,若此時數組長度小於64,擴容數組
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null;
       //鏈表轉為樹結構
do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }

由此可以看到1.8中,數組有兩種情況會發生擴容,一種是超過閾值,一種是鏈表轉為紅黑樹且數組元素小於64時,由此在jdk1.8中,默認長度為16情況下,要么元素一直放在同一下標,數組長度為9時就會擴容,要么超過閾值12時才會擴容。

 

通過上面的分析,我們可以看到jdk1.7和1.8情況下 hashmap實現方式的主要區別

1. 出現哈希沖突時,1.7把數據存放在鏈表,1.8是先放在鏈表,鏈表長度超過8就轉成紅黑樹

2. 1.7擴容條件是數組長度大於閾值且存在哈希沖突,1.8擴容條件是數組長度大於閾值或鏈表轉為紅黑樹且數組元素小於64時

這篇文章我只是大概分析下 hashmap 在兩個jdk版本中實現方式的差異,很多如鏈表怎么轉紅黑樹的,怎么擴容的細節沒有很清楚的說明,主要這部分也涉及到數據結構的內容,我對這方面了解的還不夠透徹。但之所以鏈表要轉成紅黑樹,還是為了解決存取效率的問題。鏈表過長,取數據的效率就很慢,紅黑樹插入比較慢,但取數據還是很快的。

使用 hashmap 時,一開始最好指定下長度,畢竟擴容時,需要重新根據 key 計算數組下標,還是很影響效率的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM