主頁 > 後端開發 > 史上最強HashMap面試教程(建議收藏)

史上最強HashMap面試教程(建議收藏)

2021-10-29 09:52:01 後端開發

前言

寫這篇文章的目的是因為我大學四年的室友,龍哥在培訓java,剛好最近學習HashMap,于是我寫一篇文章來模擬他以后面試被問到HashMap的場景;另外就是因為HashMap的使用確實廣泛,深受面試官的喜愛,大廠的面試官也有很多會用HashMap來考察你的基礎到底如何,

在這里插入圖片描述

面試經過

面試官: 我們開始吧,看發型就知道是老程式員了,自我介紹一下吧,

龍哥: 我叫龍哥,剛過完三十大壽,平時喜歡打籃球,優勢是身體好,可以996,

面試官: 嗯,還不錯嘛,都30了,集合框架肯定熟悉吧,我們先從HashMap開始吧,簡單的說一下內部的資料結構是怎么樣的吧?

龍哥: 大航子果然沒有騙我,HashMap果然是開頭菜,幸好我連夜讓他給我培訓了一番(內心狂喜中…),咳咳,HashMap嘛,在jdk7是陣列+鏈表,jdk8資料結構做了升級,變成了陣列+鏈表+紅黑樹了,

面試官: 哦?這傻小子笑啥呢,,,看我探探你的底,嗯,那你說說為什么這么設計,以及jdk8為什么要做出這種升級呢?

龍哥: 是這個樣子的,Map這種集合容器,最主要的應用就是想通過一個key最快的時間找到對應的value,事實上這個時間復雜度接近為O(1),那么怎么樣才能實作這么快的速度呢?于是就引入了陣列,陣列可以理解為記憶體中一塊連續的記憶體空間,且每一小塊空間都有自己的索引,通過這個索引就能直接找到對應空間的值,

在這里插入圖片描述
那么是想一下,我通過某種演算法,可以直接把key和陣列中的某個索引對應上,我放也放到這個索引里面,取也直接取這個索引去取,是不是依賴陣列的特性,我就可以用O(1)的時間復雜度快速定位到我想要的值了,

在這里插入圖片描述

面試官: 嗯嗯嗯,有點意思,那鏈表和紅黑樹又是怎么回事?

龍哥: 瞧給你急的,猴急猴急的,我接著說,在把key映射成陣列索引值這件事情上,期望的不同的key映射到不同的陣列索引值中,但是天不遂人愿,就比如我之前勵志想成為優秀的數學老師,奈何教育減重,,,咳咳,遠了、遠了,繼續哈,但是天不遂人愿,總有可能兩個key好巧不巧就映射成了同一個陣列索引值,這就是哈希碰撞,那么碰撞之后就有兩種情況了,一是同一個key,我就要把原有的key的值覆寫掉,二就是真的是好巧不巧,兩個key算出來的真就是一樣的,那就只能把兩個key和value放到同一個陣列索引的記憶體里面,也就自然而然形成了鏈表,當然,如果碰撞的越來越多,這個鏈表就會越來越長,長,

面試官: 咳咳,禁止開車,好好說,

龍哥: 不是說鏈表過長么,于是,紅黑樹就出來了,就是解決jdk7中,鏈表可能過長的情況,那么為什么紅黑樹就能解決呢?要從鏈表和紅黑樹的查找效率說起,鏈表這種資料結構是不需要連續的記憶體空間的,內部通常是持有了下一個節點的參考,所以,鏈表要查詢出某一個元素,就要從頭節點開始查,不是的話,再去看next是不是,直到next為null才能確定整條鏈表沒有我想要的元素,在最壞情況下,鏈表的長度是n,就是遍歷n次才能找到元素,所以,鏈表的時間復雜度為O(n),

在這里插入圖片描述
這種的查詢速度如果資料多了是不可以容忍的,要知道HashMap這個工具用的地方有多少,所以jdk8做出了優化,如果鏈表的長度大于了8,達到了9,就變成紅黑樹,當然還要滿足整個hash表中的元素達到了64,之所以條件比較苛刻,是因為鏈表轉陣列本身就很耗性能,不到萬不得已,萬萬使不得啊,紅黑樹這種資料結構首先是二叉搜索樹,二叉搜索樹就滿足了查找的時間復雜度是O(lgn),是一種折半查找,折半查找的效率就很恐怖了,一次排除一半的資料,就算你有100W資料,查找固定的數,也只需要20次,就是這么拽,而紅黑樹在有二叉搜索樹的查詢效率的前提下,又保證了樹的平衡,所以鏈表在上述情況下會進化成紅黑樹,當然,又進化也有退化,退化的節點就是同一個哈希桶中的元素數小于6,就會從紅黑樹變回鏈表,之所以中間留了個7,就是為了防止頻繁的樹化、鏈表化、樹化、鏈表化,

在這里插入圖片描述

面試官:(我天,這算是這幾天來最會說的了,大航子不來,恐怕沒人能限制的了他了)那你說下,這個key是怎么轉換成陣列索引的呢?

龍哥:(這個問題,嘿嘿嘿,show,time),我們都知道在java中所有的物件繼承自Object,而Object類里面有一個方法,
在這里插入圖片描述
這個方法可以回傳一個32位的數字,當然,這個32位的數字是不能直接去當陣列的索引的,因為一般情況下不會有那么大的陣列,所以這個hashcode肯定是要經過某種轉換,如果陣列的長度是16的話,應該轉換成為的值在0到15之間,才能保證落在陣列的某一個索引值里面,這種的實作方式,常規的能想到的是取模,但是我們都知道,在計算機中,位運算的效率是最高的,于是,這個公式是這個樣子的 (n - 1) & hash ,這種運算的結果和取模不一定相同(有很多博客說相同是錯誤的),至于這么做為什么就能達到和取模一樣的效果的呢?我們還是拿16舉例,

在這里插入圖片描述
再加上位運算的速度相當快,所以HashMap是采用這種方式尋找陣列索引的,好吧,你問我具體快多少?我曾經對相同的100W樣本做過取模和位運算,大概快了幾十倍把,

面試官: 哦?有破綻,看我坑他一把,這么說,我明白了,是用物件的hashcode然后&陣列的長度啊,

龍哥: 不是的不是的,(急了),公式 (n - 1) & hash 中的hash不是直接用到hashcode,這個jdk7和jdk8還是不同的,我們先來看看jdk7和jdk8的代碼,

在這里插入圖片描述

我們可以看到,jdk8的相對簡單一點,做了1次異或操作,而jdk7做了四次,而且都是向低位做異或操作,這是為了什么呢?那是因為在計算put的元素應該放到哪個哈希桶中,也就是陣列中的哪一個位置的時候,剛才說了,是通過公式 (n - 1) & hash 計算的,而&運算的特性是兩位同時為“1”,結果才為“1”,否則為0,而我們陣列的長度一般不會特別大,所以hash值中的高位的特性會用不到,所以為了更加分散,將高位和低位的特性融合,使得元素更容易分散到不同的哈希桶中,避免哈希碰撞的發生,這就叫擾動函式,至于jdk8為什么只異或一次,可能是開發人員覺得這樣就夠分散了吧,

面試官: 嗯嗯,不錯不錯,那我懂了,先取得key的hashcode,然后擾動函式高位低位特性融合,最后算出在陣列中的索引,就比如我陣列長度為14,就會索引出0到13的值,這回沒錯了把?

龍哥:(mmmmm,,,你咋老給我挖坑,我雖然禿但是并不強啊,你老把我當琦玉可還行)不是的面試官,陣列的長度只會是2的整數倍,不是有14這種情況發生的,

面試官: 略加思索,不對把,我年輕的時候new過一個HashMap,建構式里面就是扔的14啊,沒有報錯啊(內心os,這看你怎么接),

龍哥: 是這樣的,雖然你給建構式的是14,但是HashMap幫你轉換成了離14最近的而得整數倍的數字,如果你是14就會變成16(也是陣列長度的默認值),但是你給的是32,已經是2的整數倍了,就還是32,具體怎么做的呢?
在這里插入圖片描述
因為int是32位的,所以只要經過五次的或運算就能從最高位到最低位都變成1,之所以先減一,就是為了防止傳入32的情況,如果不減一就會變成64的,

面試官:( 我怎么感覺場子hold不住了)嗯,之前我們公司,有一次線上cpu100%,好像和HashMap有關,你知道怎么發生的么?

龍哥: 那咱們公司(呸,不要臉,是你們公司),當時一定是用的jdk1.7,jdk1.7是采用的頭插法會造成鏈表成環,jdk8是尾插發,就不會了,

面試官: 詳細說說,

龍哥: 是這樣的,這是jdk7的擴容代碼,
在這里插入圖片描述
jdk7的大體流程是這樣子,在單執行緒下,遍歷老陣列的所有節點,并且遍歷每一個節點下鏈表的所有節點,重新hash計算在新陣列中的位置,然后將這個節點的next指標指向原本新陣列中對應位置的節點,自己成為新陣列那個位置上的頭節點,就是把原有節點頂下去的程序,
在這里插入圖片描述
在單執行緒情況下是沒有問題的,但是多執行緒請款下就有可能造成您說的cpu100%,因為成了死回圈,按理說不應該在多執行緒情況下使用HashMap,但是造成cpu100%還是太過嚴重了,所以jdk8才變成了尾插法,那造成死回圈的原因,還是根據擴容的代碼,走一遍就知道了,
假設原HashMap是這個樣子的(圖中陣列長度不準確,應該是2的整數倍,這里省空間,大家記得區分),
在這里插入圖片描述
這時候,t1、t2兩個執行緒同時請求擴容,t1,執行到了下圖這里被cpu釋放了執行權,
在這里插入圖片描述
此時t1、t2都有自己擴容之后的陣列,此時t2完成擴容,值得注意的是,t1、t2的陣列不是一個,但是陣列中的鏈表物件參考的都是一個,
在這里插入圖片描述
這時候t1從睡夢中醒來了,它會主要做這三步操作,
在這里插入圖片描述
操作完了之后是這個樣子的,
在這里插入圖片描述
進第二次回圈,這時候,e指標和next指標都是b,再走那三步,
在這里插入圖片描述
這時候,e和next還都是b,但是t1執行緒下,索引2的位置的頭節點由a,變成了b,然后執行了這么一段代碼,e.next = newTable[i];,把a的next指向b,當當當當,環出來了,
在這里插入圖片描述
于是,a的next是b,b的next是a,a的next是b,b的next是a,a的next是b,b的next是a,cpu就這么執行啊執行啊,沒完沒了,就100%了,

面試官: 好了好了,說的還算詳細,看來原始碼沒少看啊,剛才你說jdk7要重新rehash,jdk8也是這樣了唄?

龍哥: 不是的,不是的,當然不是的,

面試官: 好好說話,挺大歲數了,別賣萌,

龍哥: 好的哈,jdk8避免了擴容之后重新的rehash計算,因為jdk8計算在陣列中的位置的方式是 hash &(n - 1) ,n是2的整數,舉個例子,如果n是16的時候,(n-1)也就是15的二進制就是1111;如果n是32的時候,那么(n-1)也就是31的二進制就是11111,可以看出,二者的差別就是31比15的高位多了一個1,對于整個 hash &(n - 1) 的結果來看,因為&的運算邏輯是相同的二進制位都是1就是1,一個是0就是0,那么,n等于16和32的運算結果的是否相同就取決于hashcode第五位到底是0還是1,如果是0的話,同一個hashcode對于 hash &(n - 1),n=16、n=32的運算結果就是相同的;如果如果是1的話,同一個hashcode對于 hash &(n - 1) ,n=16、n=32的運算結果就是多了一個16(就是擴容前的容量),因為,n-1的最高位置的1代表的數字,就是n/2的結果,

面試官: 嗯,那jdk8之后HashMap是不是就執行緒安全了,

龍哥: 不是的,jdk8只是不會發生鏈表成環的情況,但是在put操作的時候,會出現元素被覆寫的情況,并且size++也是有執行緒安全問題的,如果要考慮多執行緒的情況下使用,建議使用ConcurrentHashMap,里面有分段鎖,所謂分段鎖,jdk7中ConcurrentHashMap外面有一個Segment陣列,這個Segment繼承了ReentrantLock,我們就可以對每一個Segment單獨上鎖,既能保證執行緒安全,鎖的粒度又不會太大,性能又不會太差,,,

面試官: 停停停,今天主要面試HashMap,最后問一個簡單的問題,陣列什么時候會擴容,

龍哥: 我不會啊,,,, (回家之后問大航子,大航子:有一個東西叫負載因子,默認是0.75,但是科學家前輩算出來的0.69幾才是最完美的值,這個值是用來在空間和時間上取得平衡,比如原來陣列長度是16,那么達到 16 *0.75=12 就會擴容了,一般擴容是原有陣列的兩倍,龍哥,你好棒!)

面試官: 好了你先回家吧,我們改天再面,

龍哥: 別別別,我還會很多,你問問我AQS啥的啊,mysql原理我都懂得,

面試官: 滾!!!!

龍哥: 好嘞!!!!


總結

hashmap的知識點還是比較多的,想要多理解,原始碼還是要多看看的,

常見的比較有深度的HashMap的面試題,應該在這次模擬面試中,龍哥都經歷過了,

最后,感謝大家的觀看,我是大航子,有問題,歡迎留言哈,


最后

如果你是一個新手,下面有龍哥的個人微信,你可以加他進群,一起學習,我會在群里答疑,大家一起進步!奧利給,另外龍哥單身,美女加微信看照片哈!!!!

微信號:nbl94953,昵稱:nbl

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/340733.html

標籤:java

上一篇:劍指offer系列——劍指 Offer 49. 丑數

下一篇:「保姆級教學」入門級java程式——薪資轉換器

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more