HashMap的結構無疑是Java面試中出現頻率最高的一道題,這個題是如此之常見,應該每個人都會信手拈來。可是就在我經歷過的無數【允許我誇張一下】面試當中,能完整回答我提出的HashMap問題的人卻是寥寥無幾,如今這道題y也問的有點厭煩了。
HashMap的結構是怎樣的?
二維結構,第一維是陣列,第二維是連結串列
Get方法的流程是怎樣的?
先呼叫Key的hashcode方法拿到物件的hash值,然後用hash值對第一維陣列的長度進行取模,得到陣列的下標。這個陣列下標所在的元素就是第二維連結串列的表頭。然後遍歷這個連結串列,使用Key的equals同連結串列元素進行比較,匹配成功即返回連結串列元素裡存放的值。
Get方法的時間複雜度是多少?
小夥伴們在回答這道題是有很多人會開始懷疑人生,他們的腦細胞這個時候會出現短路現象。明明是O(1)啊,平時都記得牢牢的,可是剛才Get方法的流程裡需要遍歷連結串列,遍歷的時間複雜度難道不是O(n)麼?此刻觀察這些孩子們的表情是非常卡哇伊呢的。當然還有些甚至是科班的小夥伴居然沒聽過時間複雜度,想到這裡我也開始懷疑人生了。當他們卡殼的時候,我會稍微提醒一下,問下面的這一道題。
假如HashMap裡的元素有100w個,請問第二維連結串列的長度大概是多少?
嗦嘎!連結串列的長度很短,相比總元素的個數可以忽略不計。這個時候小夥伴們的眼睛通常會開始發光,很童貞。作為面試官是很喜歡看到這種眼神的。我使用反射統計過HashMap裡面連結串列的長度,在HashMap裡放了100w個隨機字串鍵值對,發現連結串列的長度幾乎從來沒有超過7這個數字,當我增大loadFactor的時候,才會偶爾冒出幾個長度為8的連結串列來。於是問題又來了。
既然連結串列如此短,為啥Java8要對HashMap的連結串列進行改造,使用紅黑樹來代替連結串列呢?
有很多同學都沒具體去深入關注Java8的新特性,如果他們回答不上來,我也不會感到失望。因為到這個問題的時候,已經只剩下15%的同學不到了,如果再打擊他們,看著他們落寞的身影走出了大門,我都要對自己感到失望了,怎麼招個人就如此困難?
這道題的關鍵在於如果Key的hashcode不是隨機的,而是人為特殊構造的話,那麼第二維連結串列可能會無比的長,而且分佈極為不均勻,這個時候就會出現效能問題。比如我們把物件的hashcode都統一返回一個常量,最終的結果就是hashmap會退化為一維連結串列,Get方法的效能巨降為O(n),使用紅黑樹可以將效能提升到O(log(n))。
請解釋一下HashMap的引數loadFactor,它的作用是什麼?
loadFactor表示HashMap的擁擠程度,影響hash操作到同一個陣列位置的概率。預設loadFactor等於0.75,當HashMap裡面容納的元素已經達到HashMap陣列長度的75%時,表示HashMap太擠了,需要擴容,在HashMap的構造器中可以定製loadFactor。
請說明一下HashMap擴容的過程
擴容需要重新分配一個新陣列,新陣列是老陣列的2倍長,然後遍歷整個老結構,把所有的元素挨個重新hash分配到新結構中去。這個rehash的過程是很耗時的,特別是HashMap很大的時候,會導致程式卡頓,而2倍記憶體的關係還會導致記憶體瞬間溢位,實際上是3倍記憶體,因為老結構的記憶體在rehash結束之前還不能立即回收。那為什麼不能在HashMap比較大的時候擴容擴少一點呢,關於這個問題我也沒有非常滿意的答案,我只知道hash的取模操作使用的是按位操作,按位操作需要限制陣列的長度必須是2的指數。另外就是Java堆記憶體底層用的是tcmalloc這類library,它們在記憶體管理的分配單位就是以2的指數的單位,2倍記憶體的遞增有助於減少記憶體碎片,減少記憶體管理的負擔。
HashMap是執行緒安全的麼?
當然不是,執行緒安全的HashMap是ConcurrentHashMap。關於ConcurrentHashMap還可以問很多問題,這就需要另一篇文章來具體講解了。
你瞭解Redis麼,你知道Redis裡面的字典是如何擴容的麼?
好,如果這道題你也回答正確了,恭喜你,毫無無疑,你是一位很有錢途的高階程式設計師。
你瞭解Python麼,你知道Python裡面字典的結構是怎樣的麼?
這個就屬於附加題了,如果這道題你也回答對了,那我的眼睛就要發射紫外線了。
閱讀相關文章,關注公眾號【碼洞】