HashMap JDK8 知識彙總

2021-09-12 17:49:05 字數 4114 閱讀 8414

關於hashmap的面試題這兩篇講的不錯:

關於hahmap中的變數解釋這篇講的不錯:

關於hashmap為什麼不是執行緒安全這篇講的不錯:

關於hash演算法講解:

解決hash演算法衝突的四種方法:

注意:hashmap和hashset是採用拉鍊法解決衝突的.

concurrenthashmap在jdk1.7和1.8的區別

concurrenthashmap的1.8版本,取消了segment分段鎖的結構,而是採用cas演算法以及synchronized的方式,陣列結構和hashmap一致,鎖的顆粒度調整為每個table元素,節點的value和next採用 volatile修飾

簡單總結下hashmap:

hashmap底層資料結構:陣列+鍊錶+紅黑樹,可以接受null的key和value值.陣列預設初始化大小為16,當鏈結長度大於8的時候就會轉換為紅黑樹,實現logn的複雜度查詢,如果小於6就會再次轉換為鍊錶.

其中的結構單體是entry,包括四個元素:key,value,next,hash值.

還有一些全域性變數:

hashmap中size表示當前共有多少個kv對,capacity表示當前hashmap的容量是多少,預設值是16,每次擴容都是成倍的。loadfactor是裝載因子,當map中元素個數超過loadfactor* capacity的值時,會觸發擴容。loadfactor* capacity可以用threshold表示。

何時擴容?

陣列超過閥值,且發生了hash碰撞

樹形化的時候

參考:

注意:put的時候先判斷一下陣列是否為空,空的話先擴容

如何擴容:

預設陣列大小為16,擴容每次都是兩倍,擴容因子是0.75,即當陣列容量達到12的時候就擴容,擴容的時候會建立乙個新的陣列,然後將原來陣列的entry們複製到新陣列中,原陣列需要null,這樣才會被gc**.

注意之前的entry的位置只有兩種情況,一種是原來位置,一種是原來位置+原容量大小.這個位置的判斷是hash值&初始容量即16,如果高位為0則原來位置,如果高位為1則位置變為原來位置+擴容大小.

如何查詢:

首先根據key的hashcode()找到陣列的下標,然後根據key的equals()找到陣列鍊錶上的對應的entry.

注意這裡首先獲取hash值有個函式是將高16位和低16位做異或運算,得到hash值.根據hash值&(容量-1)找到陣列下標,如果碰撞了就使用equals.

問題:為什麼高低位要異或?

因為如果不異或操作,那得到的hash值只保留了低位的特性,和(容量-1)&操作的時候如果陣列容量小,容易增加碰撞概率,因為只和低位的&操作,所以需要將hash保留高位特性,這樣得到的hash值可以盡量讓node落點分布均勻,減少碰撞概率.

問題:為什麼陣列容量要2的冪次方?

因為尋找陣列下標是依靠這個**:陣列[hash值&(容量-1)]

容量-1是為了讓得到的下標結果控制在陣列大小範圍內,不越界.

二進位制位運算的時候2冪次方-1的結果肯定是111… 可以合理分配陣列的位置,如果是其他數-1的話,二進位制肯定會有0 如 1101 這樣子陣列下標判斷的時候只會考慮到第1,2,4位, 而第三位就會被忽略,如1011等無法訪問,導致陣列下標分布不均勻.

put過程

1.判斷鍵值對陣列 table 是否為空或為 null,否則執行 resize()進行擴容;

2.根據鍵值 key 計算 hash 值得到插入的陣列索引 i,如果 table[i]==null,直接新建節點新增,轉向6,如果 table[i]不為空,轉向3;

3.判斷 table[i]的首個元素是否和 key 一樣,如果相同直接覆蓋 value,否則轉向4,這裡的相同指的是 hashcode 以及 equals;

4.判斷 table[i] 是否為 treenode,即 table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向5;

5.遍歷 table[i],判斷鍊錶長度是否大於 8,大於 8 的話把鍊錶轉換為紅黑樹,在紅黑樹中執行插入操作,否則進行鍊錶的插入操作;遍歷過程中若發現 key 已經存在直接覆蓋 value 即可;

6.插入成功後,判斷實際存在的鍵值對數量 size 是否超多了最大容量 threshold,如果超過,進行擴容。

hashmap和hashtable區別?

hashmap是非執行緒安全的,hashtable是執行緒安全的,其實就是在操作方法即get()和put()上加上synchronized.

hashmap可以新增key和value為null的值,而hashtable不行.

如何將hashmap變為執行緒安全呢?

可以使用collections.synchronizedmap(map)方法.

什麼是concurenthashmap?

現在用concurrenthashmap代替hashtable.

concurrenthashmap採用分段鎖,內部分為很多段,每段都相當於乙個小的hashtable,他們有自己的鎖,只要多個修改操作發生在不同的段上,就可以併發進行.

為了效率一般都會初始化hashmap容量,在這裡jdk1.8是建構函式的時候就會實現擴容,而jdk1.7需要在第一次put的時候才會擴容.

jdk1.8建構函式傳入的數字並不是hashmap的容量,而是 第乙個大於傳入數字的2次冪數,如

傳入1–>容量2

傳入3–>容量4

傳入7–>容量8

面試題:初始構造器設定大小為25,hashmap實際大小是多少?

注意:hashmap賦值初始值容量的時候,一定是大於等於賦值數的2^n的值.

實際是64,首先,找到比25大的2^n,是32,負載因子為0.75,則能裝24個,25>24,觸發擴容,為64.

面試中關於hashmap的時間複雜度o(1)的思考

只有理想狀態下是o(n)

參考這篇文章:

題外話1:

常考arraylist和vector區別?

vector是執行緒安全的,它在add方法的時候使用了同步函式,方法上加了synchronized關鍵字,而arraylist的add方法沒有新增這個關鍵字.

當儲存空間不足的時候,arraylist預設大小變為原來的1.5倍,而vector預設大小變為原來的2倍.

題外話2:

hashset底層是hashmap,這個打死也沒想到.實現set介面,由雜湊表支援,不保證set的迭代順序,允許null元素.

預設建構函式是構建乙個初始容量為16,負載因子為0.75的hashmap.封裝了乙個hashmap物件來儲存所有的集合元素,所有放入hashset中的集合元素實際由hashmap的key來儲存,而hashmap的value則儲存了乙個present,他是乙個靜態的object物件.

將乙個物件放入hashset中儲存,重寫該類的equals和hashcode方法很重要,而且這兩個方法的返回值必須保持一致:當該類的兩個hashcode返回值相同時,他們通過equals方法比較.

hashset的其他操作都是基於hashmap的

題外話3:

關於hash值如果採用求餘演算法的話,那除數要為不大於雜湊表長度的最大素數或者不包含質因數小於20的合數.

原因:除數取合數的話,一旦資料是以合數的某個因子為間隔增長的,那麼雜湊值只會是幾個值不停的重複,衝突很嚴重,而素數就不會發生這種情況

參考:

HashMap jdk8 內部結構重點解析

static class node implements map.entry final node resize elseif newcap oldcap 1 maximum capacity oldcap default initial capacity newthr oldthr 1 雙倍擴大老...

Hadoop知識彙總

hadoop的兩大功能 海量資料儲存和海量資料分析 1 hdfs 分布式檔案系統海量資料儲存 3 yarn 資源排程管理集群 hdfs工作機制 基於namenode和datanode 1 namenode 響應客戶端的請求 負責維護整個hdfs檔案系統的目錄樹,以及每乙個路徑 檔案 所對應的bloc...

博弈知識彙總

顯然,如果n m 1,那麼由於一次最多只能取m個,所以,無論先取者拿走多少個,後取者都能夠一次拿走剩餘的物品,後者取勝。因此我們發現了如何取勝的法則 如果n m 1 r s,r為任意自然數,s m 那麼先取者要拿走s個物品,如果後取者拿走k m 個,那麼先取者再拿走m 1 k個,結果剩下 m 1 r...