1. 程式人生 > >求求大廠給個Offer:Map面試題

求求大廠給個Offer:Map面試題

## 前言 > **文字已收錄至我的GitHub**:[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y),有300多篇原創文章,最近在**連載面試**系列! 我,三歪,最近開始寫面試系列。我給這個面試系列取了一個名字,叫做《**求求大廠給個Offer**》 所以這篇文章叫做《**求求大廠給個Offer:Map面試題**》 接下來就開始吧。 ## 面試現場 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoMTB4dGt2ajMwb2swYm1kaTIuanBn?x-oss-process=image/format,png) 三歪:“我叫三歪,這幾年寫了300+原創技術文章,近1000頁的**原創**電子書和多個知識點的思維導圖。我的願景是:**只要關注我並三連的同學都可以拿到大廠offer**。我的....” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoMG1mNm40ajMwdGswYm1kaTUuanBn?x-oss-process=image/format,png) ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoNGdkczZyajMxOTYwYm13aDguanBn?x-oss-process=image/format,png) 三歪:“Map在Java裡邊是一個介面,常見的實現類有HashMap、LinkedHashMap、TreeMap和ConcurrentHashMap” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoNHRvZHU0ajMwcmswYm1nbnouanBn?x-oss-process=image/format,png) 三歪:“首先要明確的是:在Java裡邊,雜湊表的結構是`陣列+連結串列`的方式。HashMap底層資料機構是`陣列+連結串列/紅黑樹`、LinkedHashMap底層資料結構是`陣列+連結串列+雙向連結串列`、TreeMap底層資料結構是紅黑樹,而ConcurrentHashMap底層資料結構也是`陣列+連結串列/紅黑樹`” 面試官:“我們先以HashMap開始吧,你能講講當你`new`一個HashMap的時候,會發生什麼嗎?” 三歪:“HashMap有幾個構造方法,但最主要的就是指定初始值大小和負載因子的大小。如果我們不指定,預設HashMap的大小為`16`,負載因子的大小為`0.75`” 三歪:“HashMap的大小隻能是**2次冪**的,假設你傳一個10進去,實際上最終HashMap的大小是16,你傳一個7進去,HashMap最終的大小是8,具體的實現在`tableSizeFor`可以看到。我們把元素放進HashMap的時候,需要算出這個元素所在的位置(hash)。在HashMap裡用的是**位運算**來代替取模,能夠更加**高效**地算出該元素所在的位置。為什麼HashMap的大小隻能是2次冪,因為只有大小為2次冪時,**才能合理用位運算替代取模**。” 三歪:“而負載因子的大小決定著雜湊表的**擴容**和**雜湊衝突**。比如現在我預設的HashMap大小為16,負載因子為0.75,這意味著陣列最多隻能放12個元素,一旦超過12個元素,則雜湊表需要擴容。怎麼算出是12呢?很簡單,就是`16*0.75`。每次`put`元素進去的時候,都會檢查HashMap的大小有沒有超過這個閾值,如果有,則需要擴容。” 三歪:“鑑於上面的說法(HashMap的大小隻能是2次冪),所以擴容的時候時候預設是擴原來的2倍” 三歪:“顯然擴容這個操作肯定是耗時的,那我能不能把**負載因子調高**一點,比如我要調至為1,那我的HashMap就等到16個元素的時候才擴容呢。顯然是可以的,但是不推薦。負載因子調高了,這意味著**雜湊衝突的概率**會增高,雜湊衝突概率增高,同樣會耗時(因為查詢的速度變慢了)” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoNXJhMDAxajMxNGUwYm1tenEuanBn?x-oss-process=image/format,png) 三歪:“實現就在`hash`方法上,可以發現的是,它是先算出正常的雜湊值,然後與**高16位**做異或運算,產生最終的雜湊值。這樣做的好處可以**增加了隨機性**,減少了碰撞衝突的可能性。” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoNmIxZnV3ajMxMWUwYm0wdjguanBn?x-oss-process=image/format,png) 三歪:”在`put`的時候,首先對key做hash運算,計算出該key所在的index。如果沒碰撞,直接放到陣列中,如果碰撞了,需要判斷目前資料結構是連結串列還是紅黑樹,根據不同的情況來進行插入。假設key是相同的,則替換到原來的值。最後判斷雜湊表是否滿了(當前雜湊表大小`*`負載因子),如果滿了,則擴容“ 三歪:”在`get`的時候,還是對key做hash運算,計算出該key所在的index,然後判斷是否有hash衝突,假設沒有直接返回,假設有則判斷當前資料結構是連結串列還是紅黑樹,分別從不同的資料結構中取出。“ ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoNnAzOW5zajMxMnEwYm0wdjguanBn?x-oss-process=image/format,png) 三歪:”首先會比較hash值,隨後會用`==`運算子和`equals()`來判斷該元素是否相同。說白了就是:如果只有hash值相同,那說明該元素雜湊衝突了,如果hash值和`equals() || ==` 都相同,那說明該元素是同一個。“ ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoNzZhcWJnajMxYnkwYm1hY3cuanBn?x-oss-process=image/format,png) 三歪:”當陣列的大小大於**64**且連結串列的大小大於**8**的時候才會將連結串列改為紅黑樹,當紅黑樹大小為**6**時,會退化為連結串列。這裡轉紅黑樹退化為連結串列的操作主要出於**查詢和插入時對效能的考量**。連結串列查詢時間複雜度O(N),插入時間複雜度O(1),紅黑樹查詢和插入時間複雜度O(logN)“ ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoN3IxMmgwajMxMWUwYm1hY24uanBn?x-oss-process=image/format,png) 三歪:“其實在日常開發中LinkedHashMap用得不多。在前面也提到了,LinkedHashMap底層結構是`陣列+連結串列+雙向連結串列`”,實際上它**繼承**了HashMap,在HashMap的基礎上維護了一個**雙向連結串列**。有了這個雙向連結串列,我們的插入可以是“有序”的,這裡的有序不是指大小有序,而是**插入有序**。 三歪:“LinkedHashMap在遍歷的時候實際用的是雙向連結串列來遍歷的,所以LinkedHashMap的大小不會影響到遍歷的效能” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoODNqdGNoajMwc3EwYm1nbnkuanBn?x-oss-process=image/format,png) 三歪:“TreeMap在現實開發中用得也不多,TreeMap的底層資料結構是紅黑樹,TreeMap的key不能為null(如果為null,那還怎麼排序呢),TreeMap有序是通過Comparator來進行比較的,**如果comparator為null,那麼就使用自然順序**” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoOGg4ODFtajMxMmswYm0wdmEuanBn?x-oss-process=image/format,png) 三歪:“HashMap不是執行緒安全的,在多執行緒環境下,HashMap有可能會有資料丟失和獲取不了最新資料的問題,比如說:執行緒A`put`進去了,執行緒B`get`不出來。我們想要執行緒安全,可以使用ConcurrentHashMap” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoOHVtYmY2ajMwdnMwYm1hY2cuanBn?x-oss-process=image/format,png) 三歪:“ConcurrentHashMap是執行緒安全的Map實現類,它在`juc`包下的。執行緒安全的Map實現類除了ConcurrentHashMap還有一個叫做Hashtable。當然了,也可以使用Collections來包裝出一個執行緒安全的Map。但無論是Hashtable還是Collections包裝出來的都比較低效(因為是直接在外層套synchronize),所以我們一般有執行緒安全問題考量的,都使用ConcurrentHashMap” 三歪:“ConcurrentHashMap的底層資料結構是`陣列+連結串列/紅黑樹`,它能支援高併發的訪問和更新,是執行緒安全的。ConcurrentHashMap通過在**部分加鎖**和**利用CAS演算法**來實現同步,在`get`的時候沒有加鎖,Node都用了`volatile`給修飾。在擴容時,會給每個執行緒分配對應的**區間**,並且為了防止`putVal`導致資料不一致,會給執行緒的所負責的區間加鎖” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoOTVsbm1vajMxZHUwYm1xNXYuanBn?x-oss-process=image/format,png) 三歪:“不能,我不會” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTRoOWpoNmpiajMwZ2swYm03NjYuanBn?x-oss-process=image/format,png) 三歪:“我在學習的時候也看過JDK7的HashMap和ConcurrentHashMap,其實還是有很多不一樣的地方,比如JDK 7 的HashMap在擴容時是頭插法,在JDK8就變成了尾插法,在JDK7 的HashMap還沒有引入紅黑樹....ConcurrentHashMap 在JDK7 還是使用分段鎖的方式來實現,而JDK 8 就又不一樣了。但JDK 7細節我大多數都忘了。” 三歪:“我就沒用過JDK 7的API,我想著現在最低應該也是用JDK8了吧?所以我就沒去仔細看了。**要不我給你講講多執行緒相關的知識唄?**” ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly90dmExLnNpbmFpbWcuY24vbGFyZ2UvMDA3UzhaSWxseTFnaTUzeXZvc2l0ajMxa3cwY3c0MjcuanBn?x-oss-process=image/format,png) 三歪:“哦” ## 題外話 針對這次的面試可能你想了解更多Map的細節,比如說`Map基礎知識/HashMap/LinkedHashMap/TreeMap/ConcurrentHashMap`的原始碼,可以在微信搜「**Java3y**」回覆「**Map**」即可獲取我之前寫的**原創**文章。 #### 涵蓋Java後端所有知識點的開源專案,已有10K+ star!內含1000+頁原創電子書!!! - [GitHub](https://github.com/ZhongFuCheng3y/3y) - [Gitee訪問更快](https://gitee.com/zhongfucheng/Java3y) PDF文件的內容**均為手打**,有任何的不懂都可以直接**來問我** ![](https://tva1.sinaimg.cn/large/007S8ZIlly1gi3buvldy0j31qs0u049w.jpg)