Java之HashMap的底層原理

一缠借、 面試知識點

  1. 隨著18年以來現(xiàn)在互聯(lián)網(wǎng)對java面試題也是越問越深,其中hashmap更是java必問問題宜猜,那么我們今天就來總結(jié)一下hashmap 的底層原理和面試沉姨浚考知識點。
  2. HashMap 是一種存儲高校但是不保證有序的容器宝恶,它的數(shù)據(jù)結(jié)構(gòu)為"數(shù)組+鏈表/紅黑樹"的結(jié)構(gòu)(當鏈表長度到8以后數(shù)據(jù)結(jié)構(gòu)改為紅黑樹)


    image.png
  • 底層實現(xiàn)了Map<k,v> 的接口并實現(xiàn)了淺拷貝和序列化符隙,HashMap 默認初始值大小為16 ,初始值大小必須為2的冪次垫毙,如果用戶輸入的不是2的冪霹疫,那么系統(tǒng)自動更新為輸入值附近的2的冪次,最大大小為2的30次冪综芥。HashMap的閾值默認為 0.75丽蝎,當存儲節(jié)點超過該值,對map進行擴容。
    每次擴容為原來的1倍屠阻。


    image.png
  • HashMap 提供了四種構(gòu)造方法红省,分別是默認構(gòu)造方法;可以指定初始容量構(gòu)造方法国觉;可以指定初始值和閾值的構(gòu)造方法吧恃;以及基于一個Map的構(gòu)造方法。一般常用的都是給定初始容量大小的構(gòu)造方法


    image.png
  • 在一次put(添加操作)的時候麻诀,HashMap 會先進行初始化痕寓,如果沒有先進行初始化操作,初始化過程會取比用戶指定容量大的最近2的冪次數(shù)作為數(shù)組的初始容量蝇闭,如果設(shè)置了擴容的閾值也一并更新呻率。初始化完成以后繼續(xù)put 方法
    1. 先判斷有沒有初始化
    2. 在判斷傳入的key是否為空 就存儲在table(0)位置
    3. key不為空就對key進行hash,hash的結(jié)果在 & 上數(shù)組的長度就得到了位置呻引。
    4. 如果存儲位置為空就創(chuàng)建新節(jié)點礼仗,不為空就說存在hash沖突了。
    5. 解決沖突HashMap會遍歷整個鏈表逻悠,如果有相同的value值就更新元践,否則創(chuàng)建節(jié)點添加到鏈表頭。
    6. 添加還要判斷存儲節(jié)點是否達到閾值蹂风,達到閾值要進行擴容卢厂。
    7. 擴容兩倍乾蓬,擴容的時候使用Arrays.copy() 進行擴容惠啄。
    8. 擴容過后新插入的節(jié)點也要重新進行hash 一遍才能插入。
// jdk8 源碼
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 取值的操作和添加差不多任内。
    1. 先判斷是否為空撵渡,為空就取table(0)去找
    2. 不為空也低先hash & 數(shù)組長度得到下標位置
    3. 在遍歷找到相同的找到相應的值。
  • 以上這些是一些常用的知識點死嗦,但是如果你只是知道以上這些還是不夠的趋距,一般面試官還會問你HashMap 線程安全碼? 當然大家都知道是不安全的越除,但是要是問你怎么解決呢节腐? 如果你要是回答加鎖或者回答使用HashTable 基本上就掛了。HashMao是一個線程不安全的容器摘盆,在并發(fā)操作會出現(xiàn)丟失更新問題翼雀,嚴重會導致cpu宕機的,一般報錯為java.util.ConcurrentModificationException.那我們該怎么解決呢孩擂?
    1. 使用java類庫提供的collections工具包下的Collections.synchronizedMap(new HashMap()),返回一個線程安全的Map
    2. 使用并發(fā)包(java.util.concurrent)下的ConcurrentHashMap,ConcurrentHashMap采用分段式鎖機制實現(xiàn)線程安全狼渊。
  • 能不能手寫一個HashMap 線程不安全的案例
private static void hashMapNotSafe() {
     // 線程不安全版本
       Map<String, String> hashMap = new HashMap<>();
       //并發(fā)版本的hashMap。
  // Map<String, String> hashMap = new ConcurrentHashMap<>() ;
       for (int i = 0; i < 30; i++) {
           new Thread(()-> {
               hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
               System.out.println(hashMap);
           }, String.valueOf(i)).start();
       }
   }
  • java8和java7的區(qū)別
    1. Hash1.7 和1.8 最大的不同在于1.8 采用了“數(shù)組+鏈表+紅黑樹”的數(shù)據(jù)結(jié)構(gòu)类垦,在鏈表長度超過8 時狈邑,把鏈表轉(zhuǎn)化成紅黑樹來解決HashMap 因鏈表變長而查詢變慢的問題城须;
    2. 1.7 的底層節(jié)點為Entry,1.8 為node 米苹,但是本質(zhì)一樣糕伐,都是Map.Entry 的實現(xiàn)
    3. 還有就是在存取數(shù)據(jù)時添加了關(guān)于樹結(jié)構(gòu)的遍歷更新與添加操作,并采用了尾插法來避免環(huán)形鏈表的產(chǎn)生

二驱入、 考點分析

HashMap 作為最基礎(chǔ)的容器赤炒,常用來考1.7和1.8的區(qū)別,除了這個要想在面試中脫穎而出還要對HashMap的前因后果要多了解亏较。

  1. 考點一:為什么初始容量為2的冪等次莺褒?為什么負載因子為0.75f?為什么做那么多擾動處理雪情。
  • 這些問題都要圍繞一個點回答:減少hash 沖突遵岩。
  • 容量必須為2的冪次是為了增加取值的可能性。
    • 2的n次冪轉(zhuǎn)換為二進制為1后面n個0巡通,在計算下標的是否為hash&(length-1),也就是&(n-1)個1.所有二進制為1的好處尘执?
      • 0/1 & 1 都為它本身
      • 0/1 & 0 都為 0
    • 可以看出&1保證了取值的平均。如果某一位為0 宴凉,比如最后一位誊锭,那么它&出來下標就一定是個偶數(shù),減少了HashMap 數(shù)組一半的取值弥锄,大大增加了沖突的可能丧靡。
  • 負載因子為0.75f是空間與時間的均衡。
    • 如果負載因子小籽暇,意味著閾值變小温治。比如容量為10 的HashMap,負載因子為0.5f戒悠,那么存儲5個就會擴容到20熬荆,出現(xiàn)哈希沖突的可能性變小,但是空間利用率不高绸狐。適用于有足夠內(nèi)存并要求查詢效率的場景卤恳。
    • 相反如果閾值為1 ,那么容量為10寒矿,就必須存儲10個元素才進行擴容突琳,出現(xiàn)沖突的概率變大,極端情況下可能會從O(1)退化到O(n)劫窒。適用于內(nèi)存敏感但不要求要求查詢效率的場景
  • hash()的意義在于使hash結(jié)果不同hash 算法的好壞直接印象hash結(jié)構(gòu)的效率本今。1.8 之所以把9 次擾動降到2 次,是出于計算效率的考慮。
  1. 考點二:& 字符串和%字符串 雖然效果一樣冠息,但是操作效果更高挪凑。
  2. 考點三:為什么int String 更適合做key?
  • int 和 String 的好處在于hash 出來的值不會改變。如果是一個對象逛艰,那么他們可能會因為內(nèi)部引用的改變而hashCode 值的改變躏碳,會導致存儲重復的數(shù)據(jù)或找不到數(shù)據(jù)的情況。

三散怖、 面試時候由于HashMap 引導出來的其他問題菇绵?

  • 不僅僅是HashMap 的問題,在面試的時候镇眷,面試官會引導出很多其他問題咬最,所以這個地方你在回答問題的時候要設(shè)計引導到你熟悉的內(nèi)容上
  • 說說concurrentHashMap 和 HashMap 的區(qū)別以及底層的實現(xiàn)?
  • 說說HashMap 如何實現(xiàn)有序(LinkHashMap 和TreeMap)以及他們的差別
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欠动,一起剝皮案震驚了整個濱河市永乌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌具伍,老刑警劉巖翅雏,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異人芽,居然都是意外死亡望几,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門萤厅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橄抹,“玉大人,你說我怎么就攤上這事祈坠『δ耄” “怎么了矢劲?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵赦拘,是天一觀的道長。 經(jīng)常有香客問我芬沉,道長躺同,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任丸逸,我火速辦了婚禮蹋艺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黄刚。我一直安慰自己捎谨,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涛救,像睡著了一般畏邢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上检吆,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天舒萎,我揣著相機與錄音,去河邊找鬼蹭沛。 笑死臂寝,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的摊灭。 我是一名探鬼主播咆贬,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼帚呼!你這毒婦竟也來了素征?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤萝挤,失蹤者是張志新(化名)和其女友劉穎御毅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怜珍,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡端蛆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酥泛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片今豆。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖柔袁,靈堂內(nèi)的尸體忽然破棺而出呆躲,到底是詐尸還是另有隱情,我是刑警寧澤捶索,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布插掂,位于F島的核電站,受9級特大地震影響腥例,放射性物質(zhì)發(fā)生泄漏辅甥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一燎竖、第九天 我趴在偏房一處隱蔽的房頂上張望璃弄。 院中可真熱鬧,春花似錦构回、人聲如沸夏块。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脐供。三九已至凳鬓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間患民,已是汗流浹背缩举。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留匹颤,地道東北人仅孩。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像印蓖,于是被迫代替她去往敵國和親辽慕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容