hashmap存多少条数据_干货 | 面试官想问的HashMap,都在这一篇里面了!
生活随笔
收集整理的這篇文章主要介紹了
hashmap存多少条数据_干货 | 面试官想问的HashMap,都在这一篇里面了!
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
來源公眾號:非科班的科班
int?hash = hash(key);
//獲取數組下標
int?i = indexFor(hash, table.length);通過計算后的下標,從而得到數組的對應下標的位置,最后把k,v值存進去,同樣的當再次第二次存值的時候,同樣把k,v傳進來,當k再次進行計算出數組下標index,有可能和第一次計算的index的值相同。為什么有可能相同呢?這個是hash函數的原因,看完上面推薦的那篇hash函數詳細介紹你就懂了。當兩次的計算index相同,這就是hash沖突。但是,兩次的需要存進去的value值是不同的,這就出現了同一個數組后面有一條鏈表,會比較鏈表上的每一個value值與當前的value是否相同,若是不相同,通過頭插法,將數值插入鏈表中。如下圖所示:接下來通通過源碼進行分析,在jdk 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 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;
????}put方法中主要做了以下幾件事:判斷table數組是否為空,若為空進行初始化table數組。 判斷key值是否為null,將null是作為key存進去。 若key不為空,通過key計算出數組下標,判斷table[i]是否為空。 若是不為空通過鏈表循環,判斷在鏈表中是否存在與該key相等,若是存在,直接將value替換成新的value。若是table[i]為空或者鏈表中不存在與之相同的key,就addEntry(hash, key, value, i)新增一個節點。 接下來看看addEntry(hash, key, value, i)新增節點的源碼如下: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);
????}這個方法很簡單,直接就是判斷當前數組的大小是否>=threshold并且table[bucketIndex]是否為null。若成立擴容,然后rehash,重新得到新數組的下標值,最后 createEntry(hash, key, value, bucketIndex)創建新節點。最后來看一下createEntry(hash, key, value, bucketIndex)創建新節點的源碼如下:void?createEntry(int?hash, K key, V value, int?bucketIndex) {
??//此位置有元素,就在鏈表頭部插入新元素(頭插法)
????????Entry e = table[bucketIndex];
????????table[bucketIndex] = new?Entry<>(hash, key, value, e);
????????size++;
????}該方法就是通過頭插法加入新節點,方法非常簡單,相信都能看懂。經過上面對put方法的源碼分析,在jdk 7 中put操作的原理圖如下所示:在JDK 7中,鏈表存儲有一個缺點,就是當數據很多的時候,鏈表就會很長,每次查詢都會遍歷很長的鏈表。因此在JDK 8中為了優化HashMap的查詢效率,將內部的結構改為數組+鏈表+和紅黑樹,當一個哈希桶后面的鏈表長度>8的時候,就會將鏈表轉化為紅黑樹,紅黑樹是二分查找,提高了查詢的效率。接下來通過JDK 8的put源碼分析如下:public?V put(K key, V value) {
????????return?putVal(hash(key), key, value, false, true);
}
final V putVal(int?hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
????????Node[] tab; Node 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 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)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;
????}通過分析源碼,上面的方法主要做了以下幾件事:判斷當前桶是否為空,空的就需要初始化(resize 中會判斷是否進行初始化)。 根據當前 key 的 hashcode 定位到具體的桶中并判斷是否為空,為空表明沒有 Hash 沖突就直接在當前位置創建一個新桶即可。 如果當前桶有值( Hash 沖突),那么就要比較當前桶中的 key、key 的 hashcode 與寫入的 key是否相等,相等就賦值給 e。 如果當前桶為紅黑樹,那就要按照紅黑樹的方式寫入數據。 如果是個鏈表,就需要將當前的 key、value 封裝成一個新節點寫入到當前桶的后面(形成鏈表)。 接著判斷當前鏈表的大小是否大于預設的閾值,大于時就要轉換為紅黑樹。 如果在遍歷過程中找到 key 相同時直接退出遍歷。 如果 e != null 就相當于存在相同的 key,那就需要將值覆蓋。 最后判斷是否需要進行擴容。 繼續看下 treeifyBin 的源碼:final?void?treeifyBin(Node[] tab, int?hash)?{
????????int?n, index; Node e;//鏈表轉為紅黑樹時,若此時數組長度小于64,擴容數組if?(tab == null?|| (n = tab.length) < MIN_TREEIFY_CAPACITY)
????????????resize();else?if?((e = tab[index = (n - 1) & hash]) != null) {
????????????TreeNode hd = null, tl = null;//鏈表轉為樹結構do?{
????????????????TreeNode 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情況下,要么元素一直放在同一下標,鏈表轉為紅黑樹且數組元素小于64時就會擴容,要么超過閾值12時才會擴容。依據上面的源碼分析,在JDK 1.8中put方法的執行的原理圖如下:通過上面的分析,我們可以看到jdk1.7和1.8情況下 hashmap實現方式區別是非常大的。在源碼的分析中,也可以找到下面問題的答案。問點三:HashMap的擴容機制是怎么樣的?JDK7與JDK8有什么不同嗎?JDK 1.7的擴容條件是數組長度大于閾值且存在哈希沖突,在JDK 7中的擴容的源碼如下: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);
????}而JDK 1.8擴容條件是數組長度大于閾值或鏈表轉為紅黑樹且數組元素小于64時,源碼中的體現如下所示://數組長度大于閾值,就擴容
if?(++size > threshold)
??????resize();
//鏈表轉為紅黑樹時,若此時數組長度小于64,擴容數組
if?(tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
??????resize();問點四:HashMap中的鍵值可以為Null嗎?能簡單說一下原理嗎?在JDK7中是允許null存進去的,通過 putForNullKey(value)方法來存儲key為null值,具體的實現的源代碼如下:if?(key == null)
????return?putForNullKey(value);而在JDK 8中當傳進key為null值的時候,就直接將hash值取0,進行計算存入值的位置。public?V put(K key, V value) {
??return?putVal(hash(key), key, value, false, true);
}
static?final int?hash(Object key) {
??int?h;
??return?(key == null) ? 0?: (h = key.hashCode()) ^ (h >>> 16);
}問點五:HashMap中能put兩個相同的Key嗎?為什么能或為什么不能?這個問題比較簡單,在JDK7和JDK8中的做法是一樣的,若是存入的key值一樣,就會將原來的key所對應的value值直接替換掉,可以從源碼中看出:// JDK1.7
if?(e.hash == hash && ((k = e.key) == key || key.equals(k))) {
???????V oldValue = e.value;
???????// 直接替換原來的value值
???????e.value?= value;
???????e.recordAccess(this);
???????return?oldValue;
?}
// JDK 1.8
else?{
????for?(int?binCount = 0; ; ++binCount) {
????????if?((e = p.next) == null) {
????????????p.next = newNode(hash, key, value, null);
????????????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)
????????// 替換掉原來value值
????????e.value?= value;
????afterNodeAccess(e);
????return?oldValue;
}問點六:聊一聊JDK 7的HashMap中的“死鎖”是怎么回事?HashMap是線程不安全的,在HashMap的源碼中并未對其操作進行同步執行,所以在并發訪問的時候就會出現線程安全的問題。由于上一篇的ConcurrentHashMap篇中講到了死鎖,也畫了圖,但是很多讀者說看不懂,這里我的鍋,在這里詳細的進行圖解。假設:有線程A和線程B,并發訪問HashMap中的數據。假設HashMap的長度為2(這里只是為了講解方便假設長度為2),鏈表的結構圖如下所示:4和8都位于同一條鏈表上,其中的threshold為1,現在線程A和線程B都要進行put操作,首先線程A進行插入值。此時,線程A執行到transfer函數中(transfer函數是resize擴容方法中調用的另一個方法),當執行(1)位置的時候,如下所示:/**
* Transfers all entries from current table to newTable.
*/
void?transfer(Entry[] newTable, boolean rehash) {
????int?newCapacity = newTable.length;
????for?(Entry e : table) {while(null?!= e) {
????????Entry next = e.next; ---------------------(1)if?(rehash) {
????????????e.hash = null?== e.key ? 0?: hash(e.key);
????????}int?i = indexFor(e.hash, newCapacity);
????????e.next = newTable[i];
????????newTable[i] = e;
????????e = next;
????} // while
????}
}此時線程A掛起,在此時在線程A的棧中就會存在如下值:e?=?4
next?=?8此時線程B執行put的操作,并發現在進行put操作的時候需要擴容,當線程B執行 transfer函數中的while循環,即會把原來的table變成新一table(線程B自己的棧中),再寫入到內存中。執行的過程如下圖所示(假設兩個元素在新的hash函數下也會映射到同一個位置):此時線程A有獲取到cpu的執行時間,接著執行(但是纖層A中的數據仍是舊表數據),即從transfer代碼(1)處接著執行,當前的 e = 4, next = 8, 上面已經描述,執行的的過程若下圖所示:當操作完成,執行查找時,會陷入死循環!問點七:HashMap是線程安全的嗎?為什么安全或者不安全?從上圖JDK8的put操作原理圖中可以看出為HashMap的put方法的詳細過程,其中造成線程不安全的方法主要是resize(擴容)方法。假設,現在有線程A 和線程B 共同對同一個HashMap進行put操作,假設A和B插入的Key-Value中key的hashcode是相同的,這說明該鍵值對將會插入到Table的同一個下標的,也就是會發生哈希碰撞。此時HashMap按照平時的做法是形成一個鏈表(若超過八個節點則是紅黑樹),現在我們插入的下標為null(Table[i]==null)則進行正常的插入。此時線程A進行到了這一步正準備插入,這時候線程A堵塞,線程B獲得運行時間,進行同樣操作,也是Table[i]==null 。此時它直接運行完整個put方法,成功將元素插入。隨后,線程A獲得運行時間接上上面的判斷繼續運行,進行了Table[i]==null的插入(此時其實應該是Table[i]!=null的操作,因為前面線程B已經插入了一個元素了),這樣就會直接把原來線程B插入的數據直接覆蓋了,如此一來就造成了線程不安全問題。-End-
本文思維導圖
HashMap簡介
HashMap 是很常用的一種集合框架,其底層實現方式在 JDK 1.7和 JDK 1.8中卻有很大區別。HashMap 是用來存儲數據的,它底層在JDK 1.7是數組+鏈表實現的,而JDK 1.8是使用數組+鏈表+紅黑樹實現,通過對 key 進行哈希計算等操作后得到數組下標,把 value 等信息存放在鏈表或紅黑樹存在此位置。如果兩個不同的 key 運算后獲取的數組下標一致,就出現了哈希沖突。數組默認長度是16,如果實際數組長度超過一定的值,就會進行擴容。HashMap的面試不管小廠還是大廠都是高頻問點,特別是大廠一定會深究底層,采用持續的追問,知道你懷疑人生,在Java7和Java8中對HashMap的數據結構進行了很大的優化。今天這篇文章就以HashMap的高頻問點為主,層層的剖析HasMap的底層實現,話不多說,直接進入正題。問點一:你了解HashMap的底層數據結構嗎?對于HashMap的底層數據結構在Java7和Java8中的實現是不同的,在Java7中是采用數組+鏈表的數據結構進行實現,而在Java8中是采用數組+鏈表+紅黑樹的數據結構實現的。說時遲那時快,剛話說完,從兜里拿出筆和紙,啪地一聲放在桌子上畫了起來,許久之后,出現了兩幅jdk7和jdk8的HashMap的內部結構圖:上圖是jdk7內部結構圖,以Entry[]數組作為哈希桶,每個哈希桶的后面又可以連著一條單向鏈表,在鏈表中以k,v的形式存儲數據,并且每一個節點有指向下一節點的指針。上圖是jdk8的HashMap的內部結構圖,此時在源碼源碼中就不再使用Entry[]作為數組,而是使用Node[]數組作為哈希桶,每個哈希桶的后面也可能連著一條單向鏈表或者紅黑樹。當單向鏈表的值>8的時候,鏈表就會轉換為紅黑樹進行存儲數據,在面試大廠的時候,其實答到這里,還是不完整的,為什么呢?因為你想你說的上面的實際由jdk7和jdk8轉變的一個結果,但是重要的為什么要這樣做?你還沒有回答。如果你聰明點的話,就不會等著面試官拋出接下來的問題?而是自己去回答這個為什么?不是等著面試官繼續拋出這個為什么?一個會聊天的人他會去猜測對方想知道什么?問點二:哈希沖突是怎么回事?HashMap又是怎么解決的?哈希沖突是怎么回事呢?當的數據將要存進HashMap中的時候,會先,把k值經過hash函數進行計算得到hash值,再通過hash值進行計算得到數據在數組的下標,在jdk7中的源碼如下://key 進行哈希計算int?hash = hash(key);
//獲取數組下標
int?i = indexFor(hash, table.length);通過計算后的下標,從而得到數組的對應下標的位置,最后把k,v值存進去,同樣的當再次第二次存值的時候,同樣把k,v傳進來,當k再次進行計算出數組下標index,有可能和第一次計算的index的值相同。為什么有可能相同呢?這個是hash函數的原因,看完上面推薦的那篇hash函數詳細介紹你就懂了。當兩次的計算index相同,這就是hash沖突。但是,兩次的需要存進去的value值是不同的,這就出現了同一個數組后面有一條鏈表,會比較鏈表上的每一個value值與當前的value是否相同,若是不相同,通過頭插法,將數值插入鏈表中。如下圖所示:接下來通通過源碼進行分析,在jdk 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 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;
????}put方法中主要做了以下幾件事:
//數組長度大于閾值且存在哈希沖突(即當前數組下標有元素),就將數組擴容至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);
????}這個方法很簡單,直接就是判斷當前數組的大小是否>=threshold并且table[bucketIndex]是否為null。若成立擴容,然后rehash,重新得到新數組的下標值,最后 createEntry(hash, key, value, bucketIndex)創建新節點。最后來看一下createEntry(hash, key, value, bucketIndex)創建新節點的源碼如下:void?createEntry(int?hash, K key, V value, int?bucketIndex) {
??//此位置有元素,就在鏈表頭部插入新元素(頭插法)
????????Entry e = table[bucketIndex];
????????table[bucketIndex] = new?Entry<>(hash, key, value, e);
????????size++;
????}該方法就是通過頭插法加入新節點,方法非常簡單,相信都能看懂。經過上面對put方法的源碼分析,在jdk 7 中put操作的原理圖如下所示:在JDK 7中,鏈表存儲有一個缺點,就是當數據很多的時候,鏈表就會很長,每次查詢都會遍歷很長的鏈表。因此在JDK 8中為了優化HashMap的查詢效率,將內部的結構改為數組+鏈表+和紅黑樹,當一個哈希桶后面的鏈表長度>8的時候,就會將鏈表轉化為紅黑樹,紅黑樹是二分查找,提高了查詢的效率。接下來通過JDK 8的put源碼分析如下:public?V put(K key, V value) {
????????return?putVal(hash(key), key, value, false, true);
}
final V putVal(int?hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
????????Node[] tab; Node 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 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)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;
????}通過分析源碼,上面的方法主要做了以下幾件事:
????????int?n, index; Node e;//鏈表轉為紅黑樹時,若此時數組長度小于64,擴容數組if?(tab == null?|| (n = tab.length) < MIN_TREEIFY_CAPACITY)
????????????resize();else?if?((e = tab[index = (n - 1) & hash]) != null) {
????????????TreeNode hd = null, tl = null;//鏈表轉為樹結構do?{
????????????????TreeNode 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中,數組有兩種情況會發生擴容:
?????//數組長度大于閾值且存在哈希沖突(即當前數組下標有元素),就將數組擴容至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);
????}而JDK 1.8擴容條件是數組長度大于閾值或鏈表轉為紅黑樹且數組元素小于64時,源碼中的體現如下所示://數組長度大于閾值,就擴容
if?(++size > threshold)
??????resize();
//鏈表轉為紅黑樹時,若此時數組長度小于64,擴容數組
if?(tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
??????resize();問點四:HashMap中的鍵值可以為Null嗎?能簡單說一下原理嗎?在JDK7中是允許null存進去的,通過 putForNullKey(value)方法來存儲key為null值,具體的實現的源代碼如下:if?(key == null)
????return?putForNullKey(value);而在JDK 8中當傳進key為null值的時候,就直接將hash值取0,進行計算存入值的位置。public?V put(K key, V value) {
??return?putVal(hash(key), key, value, false, true);
}
static?final int?hash(Object key) {
??int?h;
??return?(key == null) ? 0?: (h = key.hashCode()) ^ (h >>> 16);
}問點五:HashMap中能put兩個相同的Key嗎?為什么能或為什么不能?這個問題比較簡單,在JDK7和JDK8中的做法是一樣的,若是存入的key值一樣,就會將原來的key所對應的value值直接替換掉,可以從源碼中看出:// JDK1.7
if?(e.hash == hash && ((k = e.key) == key || key.equals(k))) {
???????V oldValue = e.value;
???????// 直接替換原來的value值
???????e.value?= value;
???????e.recordAccess(this);
???????return?oldValue;
?}
// JDK 1.8
else?{
????for?(int?binCount = 0; ; ++binCount) {
????????if?((e = p.next) == null) {
????????????p.next = newNode(hash, key, value, null);
????????????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)
????????// 替換掉原來value值
????????e.value?= value;
????afterNodeAccess(e);
????return?oldValue;
}問點六:聊一聊JDK 7的HashMap中的“死鎖”是怎么回事?HashMap是線程不安全的,在HashMap的源碼中并未對其操作進行同步執行,所以在并發訪問的時候就會出現線程安全的問題。由于上一篇的ConcurrentHashMap篇中講到了死鎖,也畫了圖,但是很多讀者說看不懂,這里我的鍋,在這里詳細的進行圖解。假設:有線程A和線程B,并發訪問HashMap中的數據。假設HashMap的長度為2(這里只是為了講解方便假設長度為2),鏈表的結構圖如下所示:4和8都位于同一條鏈表上,其中的threshold為1,現在線程A和線程B都要進行put操作,首先線程A進行插入值。此時,線程A執行到transfer函數中(transfer函數是resize擴容方法中調用的另一個方法),當執行(1)位置的時候,如下所示:/**
* Transfers all entries from current table to newTable.
*/
void?transfer(Entry[] newTable, boolean rehash) {
????int?newCapacity = newTable.length;
????for?(Entry e : table) {while(null?!= e) {
????????Entry next = e.next; ---------------------(1)if?(rehash) {
????????????e.hash = null?== e.key ? 0?: hash(e.key);
????????}int?i = indexFor(e.hash, newCapacity);
????????e.next = newTable[i];
????????newTable[i] = e;
????????e = next;
????} // while
????}
}此時線程A掛起,在此時在線程A的棧中就會存在如下值:e?=?4
next?=?8此時線程B執行put的操作,并發現在進行put操作的時候需要擴容,當線程B執行 transfer函數中的while循環,即會把原來的table變成新一table(線程B自己的棧中),再寫入到內存中。執行的過程如下圖所示(假設兩個元素在新的hash函數下也會映射到同一個位置):此時線程A有獲取到cpu的執行時間,接著執行(但是纖層A中的數據仍是舊表數據),即從transfer代碼(1)處接著執行,當前的 e = 4, next = 8, 上面已經描述,執行的的過程若下圖所示:當操作完成,執行查找時,會陷入死循環!問點七:HashMap是線程安全的嗎?為什么安全或者不安全?從上圖JDK8的put操作原理圖中可以看出為HashMap的put方法的詳細過程,其中造成線程不安全的方法主要是resize(擴容)方法。假設,現在有線程A 和線程B 共同對同一個HashMap進行put操作,假設A和B插入的Key-Value中key的hashcode是相同的,這說明該鍵值對將會插入到Table的同一個下標的,也就是會發生哈希碰撞。此時HashMap按照平時的做法是形成一個鏈表(若超過八個節點則是紅黑樹),現在我們插入的下標為null(Table[i]==null)則進行正常的插入。此時線程A進行到了這一步正準備插入,這時候線程A堵塞,線程B獲得運行時間,進行同樣操作,也是Table[i]==null 。此時它直接運行完整個put方法,成功將元素插入。隨后,線程A獲得運行時間接上上面的判斷繼續運行,進行了Table[i]==null的插入(此時其實應該是Table[i]!=null的操作,因為前面線程B已經插入了一個元素了),這樣就會直接把原來線程B插入的數據直接覆蓋了,如此一來就造成了線程不安全問題。-End-
90后中年人の爽點大賞
拼多多的廁所上了熱搜,996的大廠員工沒有如廁自由
為什么鬼不會攻擊被子里的人?
?可樂記得加冰,愛我就要置頂?素質三連biubiubiu~
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的hashmap存多少条数据_干货 | 面试官想问的HashMap,都在这一篇里面了!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 特斯拉第三季度全球交付超43.5万辆 全
- 下一篇: 三星 S Pen 创想版触控笔上架官方商