集合之HashMap最全面試題

2021-10-09 08:53:57 字數 3315 閱讀 7318

後期會持續更新的哦

hashmap底層實現資料結構為陣列+鍊錶的形式,jdk8及其以後的版本中使用了陣列+鍊錶+紅黑樹實現,解決了鍊錶太長導致的查詢速度變慢的問題。

簡單來說,hashmap由陣列+鍊錶組成的,陣列是hashmap的主體,鍊錶則是主要為了解決雜湊衝突而存在的。hashmap通過key的hashcode經過擾動函式處理過後得到hash值,然後通過位運算判斷當前元素存放的位置,如果當前位置存在元素的話,就判斷該元素與要存入的元素的hash值以及key是否相同,如果相同的話,直接覆蓋,不相同就通過拉鍊法解決衝突。當map中的元素總數超過entry陣列的0.75時,觸發擴容操作,為了減少鍊錶長度,元素分配更均勻。

2.1為什麼這裡把key的hashcode取出來,然後把它右移16位,然後取異或?

因為int是4個位元組,也就是32位,大概是有40億的空間,如果雜湊函式運用的比較鬆散,一般是很難出現雜湊碰撞的。但是現實中乙個長度為40億的陣列記憶體是放不下的並且hashmap在擴容前的陣列的預設初始值為16,因此直接拿hashcode值來用是不現實的。因此需要做一些運算。我們右移16位也即是把高位的資料右移到低位的16位,然後與自己做異或,那就是把高位和低位的資料進行混合,以此來加大低位的隨機性,同時混合後的低位摻雜了高位的特徵,這樣高位的資訊也被變相儲存了下來。這麼做主要是從速度,功效和質量來考慮的。

hash運算的過程其實就是對目標元素的key進行hashcode,再對map的容量進行取模,而jdk 的工程師為了提公升取模的效率,使用位運算代替了取模運算,這就要求map的容量一定得是2的冪。

hashmap的容量為什麼是2的n次冪,和這個(n - 1) & hash的計算方法有著千絲萬縷的關係,符號&是按位與的計算,這是位運算,計算機能直接運算,特別高效,按位與&的計算方法是,只有當對應位置的資料都為1時,運算結果也為1,當hashmap的容量是2的n次冪時,(n-1)的2進製也就是1111111***111這樣形式的,這樣與新增元素的hash值進行位運算時,能夠(充分的雜湊),使得新增的元素均勻分布在hashmap的每個位置上,減少hash碰撞。

default_initial_capacity:預設的初始化容量,1<<4位運算的結果是16,也就是預設的初始化容量為16。當然如果對要儲存的資料有乙個估計值,最好在初始化的時候顯示的指定容量大小,減少擴容時的資料搬移等帶來的效率消耗。同時,容量大小需要是2的整數倍。

maximum_capacity:容量的最大值,1 << 30位,2的30次冪。

default_load_factor:預設的載入因子,設計者認為這個數值是基於時間和空間消耗上最好的數值。這個值和容量的乘積是乙個很重要的數值,也就是閾值,當達到這個值時候會產生擴容,擴容的大小大約為原來的二倍。

treeify_threshold:因為jdk8以後,hashmap底層的儲存結構改為了陣列+鍊錶+紅黑樹的儲存結構(之前是陣列+鍊錶),剛開始儲存元素產生碰撞時會在碰撞的陣列後面掛上乙個鍊錶,當鍊表長度大於這個引數時,鍊錶就可能會轉化為紅黑樹,為什麼是可能後面還有乙個引數,需要他們兩個都滿足的時候才會轉化。

untreeify_threshold:介紹上面的引數時,我們知道當長度過大時可能會產生從鍊錶到紅黑樹的轉化,但是,元素不僅僅只能新增還可以刪除,或者另一種情況,擴容後該陣列槽位置上的元素資料不是很多了,還使用紅黑樹的結構就會很浪費,所以這時就可以把紅黑樹結構變回鍊錶結構,什麼時候變,就是元素數量等於這個值也就是6的時候變回來(元素數量指的是乙個陣列槽內的數量,不是hashmap中所有元素的數量)。

min_treeify_capacity:鍊錶樹化的乙個標準,前面說過當陣列槽內的元素數量大於8時可能會轉化為紅黑樹,之所以說是可能就是因為這個值,當陣列的長度小於這個值的時候,會先去進行擴容,擴容之後就有很大的可能讓陣列槽內的資料可以更分散一些了,也就不用轉化陣列槽後的儲存結構了。當然,長度大於這個值並且槽內資料大於8時,那就轉化為紅黑樹吧。

如果兩個不同物件的hashcode相同,這種現象稱為hash衝突。有以下的方式可以解決雜湊衝突:

鏈位址法鏈位址法將雜湊表的每個單元作為鍊錶的頭結點,所有雜湊位址為i的元素構成乙個同義詞鍊錶。即發生衝突時就把該關鍵字鏈在以該單元為頭結點的鍊錶的尾部。

再雜湊法當雜湊位址發生衝突用其他的函式計算另乙個雜湊函式位址,直到衝突不再產生為止。

建立公共溢位區將雜湊表分為基本表和溢位錶兩部分,發生衝突的元素都放入溢位表中。

擾動函式:促使元素位置分布均勻,減少碰撞機率

使用final物件,並採用合適的equals()和hashcode()方法

hashmap可以通過下面的語句進行同步:

map m = collections.synchronizemap(hashmap);

hashmap中value的查詢是通過 key 的 hashcode 來查詢,所以對自己的物件必須重寫 hashcode 方法通過 hashcode 找到物件位址後會用 equals 比較你傳入的物件和 hashmap 中的 key 物件是否相同,因此還要重寫 equals。

hashmap進行擴容取決於以下兩個元素:

capacity:hashmap當前長度。

loadfactor:負載因子,預設值0.75f。

當現有容量⼤於總容量 * 負載因⼦時,hashmap 擴容規則為當前容量翻倍.

當map中的元素個數(包括陣列,鍊錶和紅黑樹中)超過了16*0.75=12之後開始擴容。

具體怎麼進行擴容呢?將會建立原來hashmap大小的兩倍的bucket陣列,來重新調整map的大小,並將原來的物件放入新的bucket陣列中。這個過程叫作rehashing ,因為它將會呼叫hash方法找到新的bucket位置。

是因為長度擴大以後,hash的規則也隨之改變。比如原來長度(length)是8,你位運算出來的值是2 ,新的長度是16你位運算出來的值明顯不一樣了。

hashmap在put的時候,插入的元素超過了容量(由capacity當前長度和loadfactor負載因子決定)的範圍就會觸發擴容操作,就是rehash,這個會重新將原陣列的內容重新hash到新的擴容陣列中,在多執行緒的環境下,存在同時其他的元素也在進行put操作,如果hash值相同,可能出現同時在同一陣列下用鍊錶表示,造成閉環,導致在get時會出現死迴圈,所以hashmap是執行緒不安全的。

1.使⽤collections.

synchronizedmap

(map)建立執行緒安全的map集合;

2.hashtable

3.concurrenthashmap

參考:

面試題之Java 集合

方法 size 返回集合中的項數。isempty 判斷集合中是否為空 contains object 判斷集合中是否包含某項 clear add e 從集合中新增某項 remove object 從集合中刪除某項 iterator 遍歷集合 2.1.實現iterable介面的的類可以擁有增強for迴...

面試題 HashMap詳解

先上hashcode和equals原始碼 jni,呼叫底層其它語言實現 public native inthashcode 預設同 直接比較物件 public boolean equals object obj equals方法 string類中重寫了equals方法,比較的是字串值,看一下原始碼實...

集合面試題

list,set集合與collection有直接的關係而map屬於間接的關係 list的特點 可以儲存重複的資料,有順序 set的特點 不能儲存重複的資料,沒有順序 map的特點 根據鍵值對保持資料,鍵不可以重複,值可以重複沒有順序 arraylist 優點 查詢速度較快,使用新增和刪除功能較慢 l...