HashMap的底層結構:
1.7之前 數組加鏈表,當兩個值進行插入的時候 采用頭插法進行插入,可能會造成死循環
1.8之后 數組加鏈表/紅黑樹,當兩個值進行插入的時候,采用尾插法進行插入,不會造成死循環
HashMap底層put源碼:
- 1.7之前 會先進行判斷當前插入的值的key是否為null,如果是null
檢查數組為0的索引下標位置是否為空,如果不是空,就會把原來的值取出來放入oldValue返回回去,并采用頭插法將新put的值放到原來的位置上,舊的值放到新值下面。
如果是空,就會直接把put的值放入索引下標的位置上。
如果key不為null會根據key調用方法indexFor計算出當前key所要存儲的數組的索引下標的值,在判斷當前下標下是否有元素,重復上面的操作,
如果有的話還要進行判斷當前的key是否存在,如果存在就直接覆蓋并返回之前的值。
之后將modCount加一,為了判斷集合在迭代的時候,值是否發生了修改
之后就會添加一個新的鏈表節點,添加之前先判斷當前的數組用了多少,
如果當前已用的數組位置達到了數組長度的0.75并且即將添加位置不為空就會發生擴容機制,沒有達到會頭插法添加元素,size++ - 1.8之后 會先進行hash運算取出hash值,再判斷當前hashMap的數組是否為空,如果為空初始化數組
判斷當前數組索引位置的值是否為空,如果為空,新建加入一個節點
如果不為空,判斷當前索引下標位置的元素是否與新put的值的key相同,如果相同,將值取出來放到e中
如果不相同,判斷當前索引下標位置的節點是否是樹節點,如果是,去調用樹方法去判斷 加入節點等
如果不是樹節點,表示是鏈表節點,進行循環遍歷,將p.next給e,如果e是空的話,直接將put進來的值插入到p.next位置,并判斷是否已經達到了樹化閾值
達到了樹化閾值了判斷數組長度是否達到了64,數組長度也達到了進行樹化,跳出循環
如果e不為空,判斷e的hash值是否和put進來的值的key的hash一樣,如果相同,直接跳出循環
將e的值放到一個oldValue中進行返回
修改modCount的值進行++,size進行++,判斷size的值是否達到了擴容情況,進行擴容。
HashMap在1.8的時候為什么要轉換為紅黑樹?
因為在1.7的時候如果出現hash沖突就會將沖突的數據加入到鏈表中,如果沖突的數據多了,鏈表的數據就會太長,這樣的話就會造成查詢效率下降
紅黑樹的查詢效率大于鏈表。紅黑樹的加入是為了解決hash沖突,提高查詢效率。
樹化閾值為什么是8?
這涉及到一個泊松分布,有人調查過,如果閾值達到8的時候,造成hash沖突的概率是千萬分之一級別,沖突的概率就已經很小了
反樹化閾值為什么是6?
因為如果反樹化閾值是8,這樣不太穩定,如果數組的同一節點在加上一個數就會導致進行樹化,而如果減少一個數就會進行鏈表化,這樣來回樹化和反樹化,會導致性能降低
而如果反樹化閾值太小,很少的數就會導致轉換成紅黑樹,完全沒必要
加載因子為什么是0.75?
這是對時間和空間之間取得一個折中值,如果太大了是1的話,會導致hash沖突增加,降低性能,浪費時間
如果太小,是0.5的話,會導致還有一半的空間沒有用就進行擴容,浪費空間
hashmap默認初始化大小是多少?為什么按照2的冪次方進行擴容?
初始化大小是16,因為2的冪次方-1是多個1的組合,就像1111,當獲取添加元素到數組的哪一個索引下標的時候用hash碼的值和2的冪次方-1進行取余運算,因為是取余運算
在相同的位置上,hash碼值是什么,結果就是什么,降低了得到同一索引下標的可能性,也就降低了hash沖突。
hashmap擴容的條件?
當數組中插入元素的長度達到數組的長度*加載因子的時候會進行擴容
當同一個索引下標位置鏈表的長度達到8并且數組的長度還沒有達到64的時候
hashmap是怎么進行擴容的?1.7
hashMap的初始化容量如果不是2的冪次方,實際初始化容量會是接近自定義初始化容量的最小的2的冪次方
按照原數組長度的2倍進行擴容,會將原來的數組的索引下標位置的部分元素轉移的原數組索引下標位置加上原數組的長度的位置上
先進行循環遍歷要進行轉移的數組的索引下標,里面在進行循環遍歷要轉移的數據
將e指向要進行轉移的元素,將next指向e的下一個元素,在將e.next指向要轉移的新數組的對應下標位置,再將e賦值給新數組的對應索引下標,相當于是往下移了一位,
再將next的值賦給e,再次執行同樣的操作。
為什么hashMap在1.7的時候采用頭插法,在1.8變成了尾插法?
因為在高并發情況下,在擴容進行數據轉移的時候,使用頭插法可能導致next指向已經轉移到新數組的數據,再次使用e.next指向新數組索引下標的時候,
的是鏈表上面的元素,導致成為一個環,造成了死循環。
而使用尾插法不會導致這樣的情況。
hashMap里面的key和value可以是空值嗎?
key和value都可以是空值,但是key只能有一個是空值
HashMap是線程安全的嗎?
不是線程安全的,但是可以通過使用Collections的synchronizedMap()變得線程安全,也可以使用copyOnWrite變得線程安全
HashMap和HashTable的區別是什么?
HashMap是線程不安全的,HashTable是線程安全的
HashMap允許Key和Value為空,HashTable不允許鍵或值為空,如果鍵為空會報空指針異常
HashMap遍歷的順序不確定,HashTable的遍歷順序是按照插入的順序進行遍歷的
HashMap的性能相對較高一點,HashTable的性能相對較低
HashMap和LinkedHashMap的區別?
HashMap是無序的,獲取的順序和插入順序不一致,LinkedHashMap是有序地,獲取元素的順序和插入順序是一致的
HashMap底層就是數組加鏈表/紅黑樹,LinkedHashMap的底層是在HashMap的基礎上維護了一個雙向鏈表
HashMap和ConcurrentHashMap的區別是什么?
HashMap是線程不安全的,ConcurrentHashMap是線程安全的
HashMap由于線程不安全,所以并發情況下可能導致數據不一致或拋出異常,ConcurretHashMap使用了分段鎖,所以不同的段可以被不同的線程訪問,提高了并發性
HashMap沒有鎖,要保證線程安全需要再外部加上同步機制,ConcurrentHashMap使用了分段式鎖, 鎖得到粒度小
HashMap:在擴容時需要重新計算哈希值,重新分配元素位置,可能會導致鏈表長度過長,影響性能。ConcurrentHashMap:在擴容時只需要對某個段進行擴容,不會影響其他段,減少了擴容帶來的性能損耗。