1.hashmap新增乙個鍵值對的過程是怎麼樣的?
2.為什麼說hashmap不是執行緒安全的?
3.為什麼要一起重寫hashcode()和equal()方法?
這是網上找的一張流程圖,可以結合著步驟來看這個流程圖,了解新增鍵值對的過程。
判斷table是否為空或為null,否則執行resize()方法(resize方法一般是擴容時呼叫,也可以呼叫來初始化table)。
根據鍵值key計算hash值。(因為hashcode是乙個int型別的變數,是4位元組,32位,所以這裡會將hashcode的低16位與高16位進行乙個異或運算,來保留高位的特徵,以便於得到的hash值更加均勻分布)
static final int hash(object key)
根據(n - 1) & hash計算得到插入的陣列下標i,然後進行判斷
那麼說明當前陣列下標下,沒有hash衝突的元素,直接新建節點新增。
判斷table[i]的首個元素是否和key一樣,如果相同直接更新value。
判斷table[i] 是否為treenode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對。
上面的判斷條件都不滿足,說明table[i]儲存的是乙個鍊錶,那麼遍歷鍊錶,判斷是否存在已有元素的key與插入鍵值對的key相等,如果是,那麼更新value,如果沒有,那麼在鍊錶末尾插入乙個新節點。插入之後判斷鍊錶長度是否大於8,大於8的話把鍊錶轉換為紅黑樹。
插入成功後,判斷實際存在的鍵值對數量size是否超多了最大容量threshold(一般是陣列長度*負載因子0.75),如果超過,進行擴容。
源**如下:
其實通過學習hashmap新增鍵值對的方法,我們可以看到整個方法內都沒有使用到鎖,所以一旦多線併發訪問,就有可能造成資料不一致的問題,
例如:如果有兩個新增鍵值對的執行緒都執行到if ((tab = table) == null || (n = tab.length) == 0)
這行語句,都對table變數進行陣列初始化,就會造成已經初始化好的陣列table被覆蓋,然後前面初始化的執行緒會將鍵值對新增到之前初始化的陣列中去,造成鍵值對丟失。
final v putval(int hash, k key, v value, boolean onlyifabsent,
boolean evict)
當我們的物件一旦作為hashmap中的key,或者是hashset中的元素使用時,就必須同時重寫hashcode()和equal()方法
可以看到obejct類中的原始碼如下,可以看到equals()方法的預設實現是判斷兩個物件的記憶體位址是否相同來決定返回結果。
public native int hashcode();
public boolean equals(object obj)
網上很多部落格說hashcode的預設實現是返回記憶體位址,其實不對,以openjdk為例,hashcode的預設計算方法有5種,有返回隨機數的,有返回記憶體位址,具體採用哪一種計算方法取決於執行時庫和jvm的具體實現。
感興趣的朋友可以看看這篇部落格
static final int hash(object key)
public v put(k key, v value)
為了將一組鍵值對均勻得儲存在乙個陣列中,hashmap對key的hashcode進行計算得到乙個hash值,用hash對陣列長度取模,得到陣列下標,將鍵值對儲存在陣列下標對應的鍊錶下(假設鍊錶長度小於8,沒有達到轉換為紅黑樹的閥值)。
下面是新增鍵值對的putval()方法,當陣列下標對應的是乙個鍊錶時執行的**
//遍歷鍊錶
for (int bincount = 0; ; ++bincount)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
可以清楚地看到判斷新增的key與鍊錶中已存在的key是否相等的方法主要是e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))),
也就是:
1.先判斷hash值是否相等,不相等直接結束判斷,因為hash值不相等,key肯定不相等。
2.判斷兩個key物件的記憶體位址是否相等(相等指向記憶體中同乙個物件)。
3.key不為null,呼叫key的equal()方法判斷是否相等,因為有可能兩個key在記憶體中儲存的位址不一樣,但是是相等的。
就像是
string a = new string("test");
string b = new string("test");
system.out.println("a==b is "+a==b);//列印為false
system.out.println("a.equals(b) is "+a.equals(b));//列印為true
假設我們有乙個keyobject類,假設我們認為兩個keyobject的屬性a相等,那麼keyobject就是相等的相等的,我們將keyobject作為hashmap的key,以keyobject是否相等作為去重標準,不能重複新增keyobject相等,value不等的值到hashmap中去
public static class keyobject
}
執行以下**:
public static void main(string args)
}
如果keyobject的hashcode()方法和equals()方法都不重寫,那麼即便keyobject的屬性a都是1,key1和key2的hashcode都是不相同的,key1和key2呼叫equals()方法也不相等,這樣hashmap中就可以同時存在key1和key2了。
列印結果:
key1的hashcode為728890494
key2的hashcode為1558600329
key1.equals(key2)的結果為false
keyobject.a=1 : value1
keyobject.a=1 : value2
執行以下**:
public static class keyobject
@override
public int hashcode()
public static void main(string args) }}
此時equal()方法的實現是預設實現,也就是當兩個物件的記憶體位址相等時,equal()方法才返回true,雖然key1和key2的a屬性是相同的,但是他們在記憶體中是不同的物件,所以key1==key2結果會是false,keyobject的equals()方法預設實現是判斷兩個物件的記憶體位址,所以 key1.equals(key2)也會是false,所以這兩個鍵值對可以重複地新增到hashmap中去。
輸出結果:
key1的hashcode為1
key2的hashcode為1
key1.equals(key2)的結果為false
testobject.a=1 : value1
testobject.a=1 : value2
public static class keyobject
@override
public boolean equals(object o)
public static void main(string args) }}
假設只equals()方法,hashcode方法會是預設實現,具體的計算方法取決於jvm,(測試時發現是記憶體位址不同但是相等的物件,它們的hashcode不相同),所以計算得到的陣列下標不相同,會儲存到hashmap中不同陣列下標下的鍊錶中,也會導致hashmap中存在重複元素。
輸出結果如下:
key1的hashcode為1289479439
key2的hashcode為6738746
key1.equals(key2)的結果為true
testobject.a=1 : value1
testobject.a=1 : value2
所以當我們的物件一旦作為hashmap中的key,或者是hashset中的元素使用時,就必須同時重寫hashcode()和equal()方法,因為hashcode會影響key儲存的陣列下標及與鍊錶元素的初步判斷,equal()是作為判斷key與鍊錶中的key是否相等的最後標準。 2023年一線大廠面試題精選
題目 mysql索引相關,使用 mysql 索引都有哪些原則?索引什麼資料結構?b tree 和 b tree 什麼區別?mysql中有哪些搜尋引擎?高併發系統資料庫層面應該如何設計?資料庫鎖有哪些型別,應該如何實現?資料庫事務有哪些?oracle常用函式有哪些?sql中有哪些情況不會走索引?說說對...
《一線大廠如何面試前端工程師》聽後感
這是由三個人聯合主講的面試經驗非技術帖。康康的分享 從平台的角度 康康陳述得挺流暢,音色也不錯。很書面化,像個正式的主持人。簡歷硬條件 面試要點 面試流程 winter的分享 從面試者的角度 小吐槽 溫大神的ppt做得真的很一般。草根如何準備簡歷 案例 沒成就的寫案例 心得 案例實在也沒有寫心得 面...
第1次做面試官的隨談 一
這首先應該得從國慶節後的第1周說起 有天部門老大給我說,下週我們部門可能需要招人,主要招asp.net sqlservr方向的,需要我去面試,如果我通過了,那邊就可以讓他複試就ok了。又說了些面試時我能有的一些許可權範圍。聽得這個訊息,其實還是有點緊張,自己畢竟還是第1次哇,抽空還是提前做些面試的工...