jdk8對hashmap進行了一些修改,最大的不同就是利用了紅黑樹,所以其由**陣列+(鍊錶或紅黑樹)**組成。
在jdk7及之前的hashmap中,根據key查詢value時,根據hash值我們能夠快速定位到陣列具體下標位置處的鍊錶,但是之後的話,需要順著鍊錶乙個個比較下去才能找到我們需要的,時間複雜度取決於鍊錶的長度,為o(n)。
為了降低這部分的開銷,在jdk8中,當鍊表中的元素超過了8個以後,會將鍊錶轉換為紅黑樹,在這些位置進行查詢的時候可以降低時間複雜度為 o(logn)。
jdk8中hashmap裡面還是乙個陣列,幾個關鍵屬性:
static
final
int default_initial_capacity =16;
// 預設的初始容量是16,必須是2的冪。
static
final
int maximum_capacity =
1<<30;
// 最大容量(必須是2的冪且小於2的30次方,傳入容量過大將被這個值替換)
static
final
float default_load_factor =
0.75f
;// 預設的負載因子
static
final
int treeify_threshold =8;
// 鍊錶轉紅黑樹的閾值
static
final
int untreeify_threshold =6;
// 紅黑樹轉鍊錶的閾值
static
final
int min_treeify_capacity =64;
// 轉紅黑樹的最小的容量大小
如果陣列中儲存的是鍊錶,那麼對應的型別為node:
static
class
node
implements
map.entry
public v put
(k key, v value)
final v putval
(int hash, k key, v value,
boolean onlyifabsent,
boolean evict)
// 如果在該鍊錶中找到了"相等"的 key(== 或 equals)
if(e.hash == hash &&
((k = e.key)
== key ||
(key != null && key.
equals
(k))))
// 此時 break,那麼 e 為鍊錶中[與要插入的新值的 key "相等"]的 node
break
; p = e;}}
// e!=null 說明存在舊值的key與要插入的key"相等"
// 對於我們分析的put操作,下面這個 if 其實就是進行 "值覆蓋",然後返回舊值
if(e != null)
}++modcount;
// 如果 hashmap 由於新插入這個值導致 size 已經超過了閾值,需要進行擴容if(
++size > threshold)
resize()
;afternodeinsertion
(evict)
;return null;
}
和jdk7稍微有點不一樣的地方就是,jdk7是先擴容後插入新值的,jdk8先插值再擴容,不過這個不重要。
final
void
treeifybin
(node
tab,
int hash)
tl = p;
}while
((e = e.next)
!= null);if
((tab[index]
= hd)
!= null)
hd.treeify
(tab);}
}
鍊錶轉紅黑樹要同時滿足兩個條件:
原來單個鍊錶的長度為8,新插入第9個元素。
陣列的長度大於64,陣列長度小於64只會進行擴容,不會轉紅黑樹。
resize() 方法用於初始化陣列或陣列擴容,每次擴容後,容量為原來的 2 倍,並進行資料遷移。
final node
resize()
// 將陣列大小擴大一倍
elseif(
(newcap = oldcap <<1)
< maximum_capacity &&
oldcap >= default_initial_capacity)
// 將閾值擴大一倍
newthr = oldthr <<1;
// double threshold
}else
if(oldthr >0)
// 對應使用 new hashmap(int initialcapacity) 初始化後,第一次 put 的時候
newcap = oldthr;
else
if(newthr ==0)
threshold = newthr;
// 用新的陣列大小初始化新的陣列
node
newtab =
(node
)new
node
[newcap]
; table = newtab;
// 如果是初始化陣列,到這裡就結束了,返回 newtab 即可
if(oldtab != null)
else
}while
((e = next)
!= null);if
(lotail != null)
if(hitail != null)}}
}}return newtab;
}
resize()方法被呼叫的幾種情況:
public v get
(object key)
final node
getnode
(int hash, object key)
while
((e = e.next)
!= null);}
}return null;
}
陣列長度為什麼要是2的冪次方?
在陣列大小是2的n次方情況下,h&length-1
相當於h%length
,位運算速度更快。
什麼時候擴容?
下面兩種情況會擴容:
怎麼擴容?
擴容為原陣列長度的兩倍。
什麼時候鍊錶轉紅黑樹?
同時滿足兩個條件:
1. 原來單個鍊錶的長度為8,新插入第9個元素。
2. 陣列的長度大於64,陣列長度小於64只會進行擴容,不會轉紅黑樹。
什麼時候紅黑樹轉鍊錶?
鍊錶長度小於6。
hashmap vs hashtable ?
hashtable執行緒安全,所有的方法都加上了synchronized註解,而hashmap執行緒不安全。hashmap會觸發fast-fail,而hashtable不會。
jdk1.8與jdk1.7中hashmap差異?
hashmap原始碼分析jdk8
最近看了下jdk8的hashmap原始碼,相比於7,在儲存結構上有了些改變。1.在jdk8之前,hashmap的儲存結構是陣列 鍊錶的形式,那麼這種方式隨著鍊錶的長度增加,效率也凸顯出來。所以在jdk8中這塊做了優化,當鍊表超過一定長度時轉化為紅黑樹來解決這個問題,下面用流程圖畫出hashmap 的...
JDK8原始碼解析 HashMap(二)
1.hashmap容量大小求值方法 返回2的冪次 static final int tablesizefor int cap 1 為什麼這裡需要 int n cap 1這樣呢?首先我們要明白這個方法的作用是獲取輸入容量大小最近的2的冪次值。假設你傳過來的引數cap是16的話,經過下面的運算得出來的值...
jdk8下HashMap原始碼分析實現原理
1.hashmap 繼承 抽象類abstractmap 實現 介面map cloneable serializable 2.hashmap的初始化 四個建構函式 先看看無參建構函式 構建乙個hashmap例項,並初始化loadfactor default load factor 來看看loadfac...