HashMap必知必會
問題列表
1、HashMap的初始容量為什麼為2^n ?
目的:減少hash衝突
tab[(n - 1) & hash]
jdk1.8使用該程式碼來獲取陣列元素,不僅效率高,而且可以保證陣列不越界,讓元素分佈儘可能均勻。
為了保證這些優勢,所以hashmap的容量要為2^n。
-
陣列的長度為2^m,n-1的形式為0111…111,所以與hash進行&運算,結果值一定比n小;
-
n-1的形式為0111…111,進行&運算能更好地區分開數,減少雜湊衝突。
2、載入因子LoadFactor為什麼是0.75 ?
負載因子loadFactor是和擴容機制有關的,是一個擴容的閾值,意思是如果當前容器的使用度,達到了我們設定的最大值,就要開始執行擴容操作。
- 比如預設的容器容量為16,我們現在使用了的有16*0.75=12,此時就應該擴容。HashMap的資料結構為陣列+連結串列/紅黑樹(連結串列過長時轉換,預設為8,可以提高查詢效率),下面根據其底層的資料結構,從時間效率和空間利用率兩個方面來說明一下負載因子取值的問題:
- 1.0:loadFactor為1.0時,根據localFactoroldCapcity計算閾值,表明我們會在陣列容量用盡之後才開始擴容,此時插入資料會產生大量的hash衝突,導致底層的連結串列或紅黑樹的結構變得複雜,查詢效率變低,這是用時間效率來換得空間的利用率的提高。結論是負載因子取值過大。
- 0.5:loadFactor為0.5時,根據localFactor*oldCapcity計算閾值,表明當陣列容量使用一半就開始擴容,此時hash衝突少,連結串列的長度或者紅黑樹的高度就會降低,查詢效率就明顯變高了,但是空間利用率會變的很低,每次會有一半空間用不到,這是用空間利用率去換得時間效率的提高。結論是負載因子取值過小。
- 0.75:根據大量資料測試得到,此時的空間利用率比較高,而且避免了相當多的Hash衝突,使得底層的連結串列或者是紅黑樹的高度比較低,提升了時空效率,所以一般使用預設值就行。
3、JDK1.8做了哪些優化 ?
1、底層資料結構的變化
2、插入元素的方式
3、雜湊表為空時的操作
4、hash函式的變化
5、擴容策略
-
在 JDK 1.7 中 HashMap 底層資料結構是陣列加連結串列,JDK 1.8 之後新增了紅黑樹的組成結構,當連結串列大於 8 並且容量大於 64 時,連結串列結構會轉換成紅黑樹結構,在極端情況下,hashcode 完全衝突(一般不會發生),連結串列查詢時間複雜度為O(n),而如果是紅黑樹,查詢某個特定元素,也只需要O(log n)的開銷,查詢效率明顯提升了。不過樹化是需要一定的成本的,這裡暫時不考慮。
-
jdk1.7之前是使用頭插法,因為考慮到了一個所謂的熱點資料,即新插入的資料可能會更早用到,但問題是jdk1.7中rehash的時候,舊連結串列遷移到新連結串列,如果在新表的陣列索引位置相同,則連結串列元素會倒置, 所以最後的結果還是打亂了插入的順序。而且頭插法還會造成死連結串列:連結串列太長就需要擴充陣列長度進行rehash減少連結串列長度,如果多執行緒同時觸發擴容,在移動節點時會導致一個連結串列中的2個節點相互引用,從而生成環連結串列。jdk1.8之後使用的尾插法,把待插入元素掛在鏈尾,解決了死迴圈問題。
-
jdk1.7中當雜湊表為空時,會先呼叫inflateTable()初始化一個陣列;而1.8則是直接呼叫resize()擴容。
-
jdk1.7中的hash函式對雜湊值的計算直接使用key的hashCode值,而1.8中則是採用key的hashCode異或上key的hashCode進行無符號右移16位的結果,避免了只靠低位資料來計算雜湊時導致的衝突,計算結果由高低位結合決定,使元素分佈更均勻。
-
jdk1.7中是隻要不小於閾值就直接擴容2倍,而1.8的擴容策略更優化。當陣列容量未達到64時,以2倍進行擴容,超過64之後若桶中元素個數不小於7就將連結串列轉換為紅黑樹,但如果紅黑樹中的元素個數小於6就會還原為連結串列,當紅黑樹中元素不小於32的時候才會再次擴容。
4、HashMap獲取結點的步驟
1、根據hash得出陣列下標索引。
2、根據索引獲得索引位置所對應的鍵值對連結串列/紅黑樹。
3、遍歷鍵值對連結串列/紅黑樹,根據key找到對應的Entry結點。
4、返回該結點。
final Node<K,V> getNode(int hash, Object key) {
//通過hash查詢Entry所在的桶,然後通過key匹配物件返回
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//雜湊表存在 && 陣列長為2^m && 能找到對應的陣列下標結點first
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//檢視第一個結點是否符合
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
//紅黑樹結構遍歷查詢
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
//單連結串列遍歷查詢
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
5、HashMap是執行緒安全的 ?
hashmap顯然不是執行緒安全的。主要是由於其在put過程中的擴容導致的,當然如果只是單純的取數在jdk1.8是執行緒安全的。
在多執行緒情況下,比如有兩個執行緒A和B,首先A希望插入一個key-value對到HashMap中,首先需要計算記錄所要落到的桶的索引座標,然後獲取到該桶裡面的連結串列頭結點,此時執行緒A的時間片用完了,而此時執行緒B被排程得以執行,和執行緒A一樣執行,只不過執行緒B成功將記錄插到了桶裡面,假設執行緒A插入的記錄計算出來的桶索引和執行緒B要插入的記錄計算出來的桶索引是一樣的,那麼當執行緒B成功插入之後,執行緒A再次被排程執行時,它依然持有過期的連結串列頭但是它對此一無所知,以至於它認為它應該這樣做,如此一來就覆蓋了執行緒B插入的記錄,這樣執行緒B插入的記錄就憑空消失了,造成了資料不一致的行為。
6、HashMap與ConcurrentHashMap的區別 ?
HashMap執行緒不安全但效率高,ConcurrentHashMap執行緒安全但是效率比HashMap要低,ConcurrentHashMap會有更多的變數來支援執行緒安全所需要的樂觀鎖,在put方法中也加入了synchronized程式碼塊鎖住了當前的節點頭使其只能一個執行緒訪問,在擴容時其他執行緒新增元素會被擱置並且協助進行擴容,並宣告自己正在處理哪個位置。
7、ConcurrentHashMap如何實現執行緒安全 ?
通過樂觀鎖與synchronized來實現執行緒安全,在初始化陣列與空節點新增元素的時候使用樂觀鎖來實現執行緒安全,在給非空節點新增元素的時候會使用synchronized程式碼塊鎖住當前節點頭從而實現執行緒安全,而在擴容的時候會將轉移後的節點單獨儲存起來以便於在擴容中的put與get操作,如果put需要操作的元素還沒有轉移完成則會被擱置等待轉移完成,被擱置的執行緒也會被分配到轉移資料的任務從而協助擴容。
Thank you.
相關文章
- ThreadLocal必知必會thread
- MySQL 必知必會MySql
- Linux必會必知Linux
- git必會必知Git
- Redis 必知必會Redis
- Activity 必知必會
- JSON 必知必會JSON
- notion database 必知必會Database
- Linux shell必知必會Linux
- Linux 程式必知必會Linux
- 常用技術必知必會
- 必知必會Java命令-jpsJava
- 01-mysql必知必會MySql
- Redis 必知必會之 APIRedisAPI
- mysql必知必會筆記MySql筆記
- Mysql必知必會練習MySql
- HTTP 必知必會的那些HTTP
- 【必知必會的MySQL知識】①初探MySQLMySql
- 【必知必會的MySQL知識】②使用MySQLMySql
- ElasticSearch必知必會-進階篇Elasticsearch
- Redis 必知必會之持久化Redis持久化
- SQL必知必會筆記(上)SQL筆記
- SQL必知必會筆記(下)SQL筆記
- MYSQL中的鎖必知必會MySql
- 必知必會之Lambda表示式
- Java必知必會之註解Java
- Flutter 外掛使用必知必會Flutter
- 【必知必會的MySQL知識】④DCL語言MySql
- 【必知必會的MySQL知識】⑤DQL語言MySql
- 【必知必會的MySQL知識】③DML語言MySql
- Docker 必知必會1----初識Docker
- SQL 必知必會 50 題(1 - 5)SQL
- 面試:Redis必知必會20問面試Redis
- Android 中高階面試必知必會Android面試
- 《MySQL必知必會》萬用字元 ( like , % , _ ,)MySql字元
- MySQL必知必會》正規表示式MySql
- MySQL必知必會(1-12章)MySql
- Kafka叢集搭建及必知必會Kafka