1、廣泛問法
問題形如:“你用過HashMap嗎?” “什么是HashMap?你為什么用到它?”
回答:是的。容我列舉HashMap的一些特性,譬如HashMap可以接受null鍵值和值,而Hashtable則不能;HashMap是非synchronized;HashMap很快;以及HashMap儲(chǔ)存的是鍵值對(duì)等等。
Tip:(這顯示出你已經(jīng)用過HashMap,而且對(duì)它相當(dāng)?shù)氖煜?。但是面試官來個(gè)急轉(zhuǎn)直下,從此刻開始問出一些刁鉆的問題,關(guān)于HashMap的更多基礎(chǔ)的細(xì)節(jié)。)
2、基礎(chǔ)問法
問題形如:“你知道HashMap的工作原理嗎?” “你知道HashMap的get()方法的工作原理嗎?”
回答:HashMap是基于hashing的原理,我們使用put(key, value)存儲(chǔ)對(duì)象到HashMap中,使用get(key)從HashMap中獲取對(duì)象。當(dāng)我們給put()方法傳遞鍵和值時(shí),我們先對(duì)鍵調(diào)用hashCode()方法,返回的hashCode用于找到bucket位置來儲(chǔ)存Entry對(duì)象。
Tip:(這里關(guān)鍵點(diǎn)在于指出,HashMap是在bucket中儲(chǔ)存鍵對(duì)象和值對(duì)象,作為Map.Entry。這一點(diǎn)有助于理解獲取對(duì)象的邏輯。如果你沒有意識(shí)到這一點(diǎn),或者錯(cuò)誤的認(rèn)為僅僅只在bucket中存儲(chǔ)值的話,你將不會(huì)回答如何從HashMap中獲取對(duì)象的邏輯。這個(gè)答案相當(dāng)?shù)恼_,也顯示出面試者確實(shí)知道hashing以及HashMap的工作原理。)下個(gè)問題可能是關(guān)于HashMap中的碰撞探測(cè)(collision detection)以及碰撞的解決方法
3、困惑問法
問題形如:“當(dāng)兩個(gè)對(duì)象的hashcode相同會(huì)發(fā)生什么?” (面試官可能會(huì)提醒他們有equals()和hashCode()兩個(gè)方法,并告你兩個(gè)對(duì)象就算hashcode相同,但是它們可能并不相等)
回答:因?yàn)閔ashcode相同,所以它們的bucket位置相同,‘碰撞’會(huì)發(fā)生。因?yàn)镠ashMap使用鏈表存儲(chǔ)對(duì)象,這個(gè)Entry(包含有鍵值對(duì)的Map.Entry對(duì)象)會(huì)存儲(chǔ)在鏈表中。
Tip:(這個(gè)答案非常的合理,雖然有很多種處理碰撞的方法,這種方法是最簡單的,也正是HashMap的處理方法。)
4、疑惑重重問法
問題形如:“如果兩個(gè)鍵的hashcode相同,你如何獲取值對(duì)象?”,“如何提高獲取值對(duì)象的速度?”
回答:(1)當(dāng)我們調(diào)用get()方法,HashMap會(huì)使用鍵對(duì)象的hashcode找到bucket位置,找到bucket位置之后,會(huì)調(diào)用keys.equals()方法去找到鏈表中正確的節(jié)點(diǎn),最終找到要找的值對(duì)象。(HashMap在鏈表中存儲(chǔ)的是鍵值對(duì))
??(2)equals()方法僅僅在獲取值對(duì)象的時(shí)候才出現(xiàn)。使用不可變的、聲明作final的對(duì)象,并且采用合適的equals()和hashCode()方法的話,將會(huì)減少碰撞的發(fā)生,提高效率。不可變性使得能夠緩存不同鍵的hashcode,這將提高整個(gè)獲取對(duì)象的速度,使用String,Interger這樣的wrapper類作為鍵是非常好的選擇。
5、史詩級(jí)問法
問題形如:“如果HashMap的大小超過了負(fù)載因子(load factor)定義的容量,怎么辦?”
回答:默認(rèn)的負(fù)載因子大小為0.75,也就是說,當(dāng)一個(gè)map填滿了75%的bucket時(shí)候,和其它集合類(如ArrayList等)一樣,將會(huì)創(chuàng)建原來HashMap大小的兩倍的bucket數(shù)組,來重新調(diào)整map的大小,并將原來的對(duì)象放入新的bucket數(shù)組中。這個(gè)過程叫作rehashing,因?yàn)樗{(diào)用hash方法找到新的bucket位置。
6、地獄級(jí)問法
問題形如:“你了解重新調(diào)整HashMap大小存在什么問題嗎?”(當(dāng)多線程的情況下,可能產(chǎn)生條件競爭(race condition))
回答:當(dāng)重新調(diào)整HashMap大小的時(shí)候,確實(shí)存在條件競爭,因?yàn)槿绻麅蓚€(gè)線程都發(fā)現(xiàn)HashMap需要重新調(diào)整大小了,它們會(huì)同時(shí)試著調(diào)整大小。在調(diào)整大小的過程中,存儲(chǔ)在鏈表中的元素的次序會(huì)反過來,因?yàn)橐苿?dòng)到新的bucket位置的時(shí)候,HashMap并不會(huì)將元素放在鏈表的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing)。如果條件競爭發(fā)生了,那么就死循環(huán)了。這個(gè)時(shí)候,你可以質(zhì)問面試官,為什么這么奇怪,要在多線程的環(huán)境下使用HashMap呢?
7、究極細(xì)節(jié)問法
問題形如:“為什么String, Interger這樣的wrapper類適合作為鍵?”
回答: String, Interger這樣的wrapper類作為HashMap的鍵是再適合不過了,而且String最為常用。因?yàn)镾tring是不可變的,也是final的,而且已經(jīng)重寫了equals()和hashCode()方法了。其他的wrapper類也有這個(gè)特點(diǎn)。不可變性是必要的,因?yàn)闉榱艘?jì)算hashCode(),就要防止鍵值改變,如果鍵值在放入時(shí)和獲取時(shí)返回不同的hashcode的話,那么就不能從HashMap中找到你想要的對(duì)象。不可變性還有其他的優(yōu)點(diǎn)如線程安全。如果你可以僅僅通過將某個(gè)field聲明成final就能保證hashCode是不變的,那么請(qǐng)這么做吧。因?yàn)楂@取對(duì)象的時(shí)候要用到equals()和hashCode()方法,那么鍵對(duì)象正確的重寫這兩個(gè)方法是非常重要的。如果兩個(gè)不相等的對(duì)象返回不同的hashcode的話,那么碰撞的幾率就會(huì)小些,這樣就能提高HashMap的性能。
繼續(xù)追問:“那我們可以使用自定義的對(duì)象作為鍵嗎?”
回答: 這是前一個(gè)問題的延伸。當(dāng)然你可能使用任何對(duì)象作為鍵,只要它遵守了equals()和hashCode()方法的定義規(guī)則,并且當(dāng)對(duì)象插入到Map中之后將不會(huì)再改變了。如果這個(gè)自定義對(duì)象時(shí)不可變的,那么它已經(jīng)滿足了作為鍵的條件,因?yàn)楫?dāng)它創(chuàng)建之后就已經(jīng)不能改變了。
8、超究極問法
問題形如:“我們可以使用CocurrentHashMap來代替Hashtable嗎?”(熱門面試題)
回答:Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因?yàn)樗鼉H僅根據(jù)同步級(jí)別對(duì)map的一部分進(jìn)行上鎖。ConcurrentHashMap當(dāng)然可以代替HashTable,但是HashTable提供更強(qiáng)的線程安全性。
??換言之,它們都可以用于多線程的環(huán)境,但是當(dāng)Hashtable的大小增加到一定的時(shí)候,性能會(huì)急劇下降,因?yàn)榈鷷r(shí)需要被鎖定很長的時(shí)間。因?yàn)镃oncurrentHashMap引入了分割(segmentation),不論它變得多么大,僅僅需要鎖定map的某個(gè)部分,而其它的線程不需要等到迭代完成才能訪問map。簡而言之,在迭代的過程中,ConcurrentHashMap僅僅鎖定map的某個(gè)部分,而Hashtable則會(huì)鎖定整個(gè)map。