HashMap,一個大小廠都會問的知識點墓捻。下面結(jié)合網(wǎng)上搜羅的一些信息抖仅,整理出一份比較全面的Hashmap相關(guān)面試資料:
- HashMap與HashTable的區(qū)別?
主要區(qū)別有三點:線程安全性毙替,同步岸售,以及速度。
HashTable是線程安全的厂画,而HashMap不是凸丸;
HashMap中允許存在null鍵和null值,而HashTable中不允許
單線程環(huán)境下HashMap的速度快袱院。多線程環(huán)境下屎慢,java 5提供了ConcurrentHashmap,它是HashTable的替代忽洛,比HashTable的擴展性更好腻惠。
2.HashMap的工作原理是什么?or HashMap的get()方法工作原理欲虚?
HashMap基于hashing原理集灌,我們通過put()和get()方法儲存和獲取對象。當我們將鍵值對傳遞給put()方法時复哆,它調(diào)用鍵對象的hashCode()方法來計算hashcode欣喧,讓后找到bucket位置來儲存值對象。當獲取對象時梯找,通過鍵對象的equals()方法找到正確的鍵值對唆阿,然后返回值對象。HashMap使用LinkedList來解決碰撞問題锈锤,當發(fā)生碰撞了驯鳖,對象將會儲存在LinkedList的下一個節(jié)點中。 HashMap在每個LinkedList節(jié)點中儲存鍵值對對象久免。
3.兩個對象的hashcode相同時會發(fā)生什么浅辙?
因為hashcode相同,所以它們的bucket位置相同妄壶,‘碰撞’會發(fā)生摔握。它們會儲存在同一個bucket位置的LinkedList中。鍵對象的equals()方法用來找到鍵值對丁寄。
4.兩個鍵的hashcode相同時氨淌,如何獲取值對象泊愧?
當我們調(diào)用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置盛正,然后獲取值對象删咱。如果有兩個值對象儲存在同一個bucket,會調(diào)用keys.equals()方法遍歷LinkedList豪筝,直到找到正確的節(jié)點痰滋,最終找到要找的值對象。
5.如果HashMap的大小超過了負載因子定義的容量怎么辦续崖?
默認的負載因子大小為0.75敲街,也就是說,當一個map填滿了75%的bucket時候严望,和其它集合類(如ArrayList等)一樣多艇,將會創(chuàng)建原來HashMap大小的兩倍的bucket數(shù)組,來重新調(diào)整map的大小像吻,并將原來的對象放入新的bucket數(shù)組中峻黍。這個過程叫作rehashing,因為它調(diào)用hash方法找到新的bucket位置拨匆。
6.多線程環(huán)境下姆涩,重新調(diào)整HashMap大小存在什么問題?
多線程環(huán)境下惭每,當重新調(diào)整HashMap大小的時候骨饿,存在條件競爭,因為如果兩個線程都發(fā)現(xiàn)HashMap需要重新調(diào)整大小了台腥,它們會同時試著調(diào)整大小样刷。在調(diào)整大小的過程中,存儲在LinkedList中的元素的次序會反過來览爵,因為移動到新的bucket位置的時候,HashMap并不會將元素放在LinkedList的尾部镇饮,而是放在頭部蜓竹,這是為了避免尾部遍歷(tail traversing)。如果條件競爭發(fā)生了储藐,那么就死循環(huán)了俱济。
所在,不能在多線程環(huán)境下使用Hashmap钙勃。
7.為什么String, Interger這樣的wrapper類適合作為鍵蛛碌?
如果兩個不相等的對象返回不同的hashcode的話,那么碰撞的幾率就會小些辖源,這樣就能提高HashMap的性能蔚携,也就適合做Hashmap的鍵希太。因為獲取對象的時候要用到equals()和hashCode()方法,鍵對象正確的重寫這兩個方法是非常重要的酝蜒。
因此誊辉,String,Interger這樣的wrapper類作為HashMap的鍵是再適合不過了亡脑,而且String最為常用堕澄。因為String是不可變的,也是final的霉咨,而且已經(jīng)重寫了equals()和hashCode()方法了蛙紫。其他的wrapper類也有這個特點。不可變性是必要的途戒,因為為了要計算hashCode()坑傅,就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話棺滞,那么就不能從HashMap中找到你想要的對象裁蚁。
8.可以用自定義對象作為鍵嗎?
這是前一個問題的延伸继准。當然你可能使用任何對象作為鍵枉证,只要它遵守了equals()和hashCode()方法的定義規(guī)則,并且當對象插入到Map中之后將不會再改變了移必。如果這個自定義對象時不可變的室谚,那么它已經(jīng)滿足了作為鍵的條件,因為當它創(chuàng)建之后就已經(jīng)不能改變了崔泵。
9.可以使用CocurrentHashMap來代替HashTable嗎秒赤?
可以。我們知道HashTable是synchronized的憎瘸,但是ConcurrentHashMap同步性能更好入篮,因為它僅僅根據(jù)同步級別對map的一部分進行上鎖。ConcurrentHashMap當然可以代替HashTable幌甘,但是HashTable提供更強的線程安全性潮售,效率要低。
10.能否讓HashMap同步锅风?
Hashmap可以通過下面的語句進行同步:
Map map = Collections.synchronizedMap(hashmap);
Collections 內(nèi)部有一個實現(xiàn)了 Map 接口的 SynchronizedMap 內(nèi)部類酥诽,這是一個實現(xiàn)線程同步的 map 類,具體線程同步就是在所有的方法實現(xiàn)中都使用 synhronized 塊達到線程同步皱埠,不過具體的方法實現(xiàn)統(tǒng)統(tǒng)使用 synchronizedMap 方法傳遞進去 map 來完成肮帐。里面的實現(xiàn)是典型的裝飾模式。
11.ConcurrentHashMap的并發(fā)機制边器?
ConcurrentHashMap 引入了分段鎖的機制训枢,該機制對并發(fā)控制做了優(yōu)化 托修。
ConcurrentHashMap是支持并發(fā)讀寫的HashMap,它的特點是讀取數(shù)據(jù)時無需加鎖肮砾,寫數(shù)據(jù)時可以保證加鎖粒度盡可能的小诀黍。由于其內(nèi)部采用“分段存儲”,只需對要進行寫操作的數(shù)據(jù)所在的“段”進行加鎖仗处。
ConcurrentHashMap 類中包含兩個靜態(tài)內(nèi)部類 HashEntry 和 Segment眯勾。HashEntry 用來封裝映射表的鍵 / 值對;Segment 用來充當鎖的角色婆誓,每個 Segment 對象守護整個散列映射表的若干個桶吃环。每個桶是由若干個 HashEntry 對象鏈接起來的鏈表。一個 ConcurrentHashMap 實例中包含由若干個 Segment 對象組成的數(shù)組洋幻。
通過減小請求同一個鎖的頻率和盡量減少持有鎖的時間 郁轻,使得 ConcurrentHashMap 的并發(fā)性相對于 HashTable 和用同步包裝器包裝的 HashMap有了質(zhì)的提高。
===========================更正============================
1.上面的HashMap是基于JDK1.8之前的文留,1.8的下次分析后單獨補上好唯。
2.ArrayList擴容原理。翻看了下源碼燥翅,當調(diào)用add()方法之后骑篙,會先判斷是否存滿了,如果滿了森书,最終會調(diào)用如下方法進行擴容:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
由上面可知靶端,newCapacity = oldCapacity + (oldCapacity >> 1);這個是新的容量是原來的1.5倍,即初始是10凛膏,經(jīng)過一次擴容后變?yōu)?5杨名。
本文原創(chuàng)發(fā)布于公眾號《Android面試專欄》,歡迎搜索關(guān)注猖毫!也歡迎大家留言投稿台谍!
動動手指吧 _