主頁 >  其他 > 面試官聽我講完HashMap直呼要為我點贊

面試官聽我講完HashMap直呼要為我點贊

2021-02-22 12:33:57 其他

面試官聽我講完HashMap直呼要為我點贊
前言

這是Homer與面試官系列第一篇,在一個周末陽光明媚的午后,Homer拿起他的電腦,便開始了一頓知識的輸出,HashMap作為面試必問重點,也是必須非常清晰掌握的,

開場

面試開始,一個還剩少量稀疏頭發的大叔,踏著輕快的步伐走過來,喃喃道:又來一個不怕死,開口說道:“用過Hashmap吧?”,

心里想:這不是廢話嘛!當然用過,這也是問題?然后回到道:嗯,用過的,

那你跟我講講他的結構吧

在JDK1.7中HashMap是由陣列加鏈表構成的資料結構,
在這里插入圖片描述陣列的每一個地方存盤有key-value的資料節點,在1.7中稱為Entry,1.8中為Node,
當執行put(“A”,“Homer”)方法時,會先將key值的hash值進行計算得到一個存盤地址,如hash(“A”)得到2,就將其存入陣列2的位置,繼續執行put(“B”,“Jack”),在某些情況之下,如果hash(“B”)得到又是2,會通過equal()比較key值是否相等,如果相等覆寫,如果不相等就會形成鏈表,(Hash相等,值不一定相等)

那JDK1.8呢?

在JDK1.8中添加了紅黑樹,變成了陣列+鏈表/紅黑樹的結構,
在這里插入圖片描述當鏈表的長度大于8時,鏈表會轉換成紅黑樹,

小伙子,那你跟我講講Node是啥樣呢?插入流程呢?

每一個Node節點都會保存自身的hash、key、value、以及下個節點,
在這里插入圖片描述
1.當第一Node節點插入時,判斷陣列是否為空,為空則進行初始化,
2.若不為空,通過key的hash值進行計算得到陣列存盤下標index,
3.查看陣列index位置是否有資料,如果為空則初始化一個node節點放入,
4.若陣列index位置不為空,則判斷key值是否相等,相等則覆寫掉原有value值,
5.key值不等(發生hash沖突),判斷是否是樹型節點,如果是則插入紅黑樹中,
6.不是樹型節點,創建Node加入鏈表,判斷鏈表長度是否大于 8并且陣列長度大于64, 大于的話鏈表轉換為紅黑樹
7.插入完畢后判斷節點個數是否大于閾值(容量*裝載因子),大于(沒有等于)則進行擴容,

你說的還是比較清楚,你剛才提到容量,那是怎么設計的呢?

我這臭嘴,咦!多說啥呢,跪著也得繼續講清楚啊,當進行new HashMap()時不傳入值,則初始化容量為16,如果傳入值,初始化大小為 大于值的 2的整數次方,如傳入17,初始化為32,

static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

cap為傳入值,減1操作后,進行初始二進制右移1,2,4,8,16位,分別與自己位或,把高位第一個為1的數通過不斷右移,把高位為1的后面全變為1,最后再進行+1操作,就變成 大于值的 2的整數次方,
如傳入14,為1110,進行-1,就為1101,
>>>1 = 0110,進行或操作:1101 | 0110 = 1111.
后面操作同理,最后如果不大于Integer最大值,則加1回傳,1111+1 = 10000,也就是16.

那為啥要減一呢?

咦!這也問?當然要減一,如果傳入16,不減一,則10000變成11111,最后加一則回傳了32.

嗯,小伙子不錯,那裝載因子是怎么回事呢?

裝載因子用于表示HashMap滿的程度,默認值為0.75f,設定成0.75,因為0.75正好是3/4,而capacity又是2的冪,所以,兩個數的乘積都是整數,
之所以設定為0.75,因為可以盡可能提高查詢效率和降低空間利用的成本,滿足泊松分布,碰撞最小,如果加載因子過高如1,如第16個元素插入時,碰撞概率非常高了,導致查詢效率降低,加載因子過低如0.5則擴容的頻率加大,對空間使用效率很低,

你剛才提到容量初始化為16,那為啥是16呢?

因為容量過大可能空間浪費,過小頻繁擴容影響效率,這應該一種經驗值,
在這里插入圖片描述
為了位運算的方便,位與運算比算數計算的效率高了很多,是為了在生成陣列下標index時更高的效率,

前面都講的很nice,你在說hash碰撞,那你說是怎么樣hash的?

先拿到key的hashcode,是一位32位的int數值,然后讓hashcode的高16位和低16位進行異或操作,此為擾動函式:盡量降低hash碰撞概率,同時采用位運算提高效率,

 static final int hash(Object key) {
        int h;
        //可以看出key可以為null,但也只能插入一個null,
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

為什么需要高16位和低16位異或可以降低碰撞呢?

 n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)

陣列的位置index = hash&(n - 1),在這里也解釋了為什么需要容量為2的n次方,Length-1的值是所有二進制位全為1,“與”操作的結果就是散列值的高位全部歸零,只保留低位值,用來做陣列下標訪問,以初始長度16為例,16-1=15,2進制表示是00000000 00000000 00001111,和某散列值做“與”操作如下,結果就是截取了最低的四位值,
在這里插入圖片描述
這時候問題就來了,這樣就算我的散列值分布再松散,要是只取最后幾位的話,碰撞也會很嚴重,更要命的是如果散列本身做得不好,分布上成等引數列的漏洞,恰好使最后幾個低位呈現規律性重復,
這時候“擾動函式”的價值就體現出來了:
在這里插入圖片描述
右移16位,正好是32位的一半,自己的高半區和低半區做異或,就是為了混合原始哈希碼的高位和低位,以此來加大低位的隨機性,
1.7做了四次移位和四次異或,但明顯Java 8覺得擾動做一次就夠了,做4次的話,多了可能邊際效用也不大,所謂為了效率考慮就改成一次了,
下面是1.7的hash代碼:

static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

為啥需要將鏈表轉為紅黑樹呢?

防止發生hash沖突,鏈表長度過長,將時間復雜度由O(n)降為O(logn);

嗯!!不錯不錯,看來我是低估你了呀?那我們繼續吧!談談HashMap怎么擴容的?

在插入時,1.7先判斷是否需要擴容,再插入,1.7采用頭插法,也就是將新元素放到陣列中,原始節點作為新節點的后繼節點,新節點到了頭部,因為寫這個代碼的作者認為后來的值被查找的可能性更大一點,提升查找的效率,

你提到頭插法,頭插法會面臨什么問題?

頭插法在多執行緒的條件下可能會導致產生環,陣列的容量是有限的,當超過了陣列所能裝載的節點個數,就會擴容,
擴容分為兩步:

  • 擴容:創建一個新的Entry空陣列,長度是原陣列的2倍,
  • ReHash:遍歷原Entry陣列,把所有的Entry重新Hash到新陣列,

為啥需要重新hash呢?

是因為長度擴大以后,Hash的規則也隨之改變,
Hash的公式—> index = HashCode(Key) & (Length - 1)

繼續你的產生環問題吧!

1.7的擴容代碼

void transfer(Entry[] newTable, boolean rehash) {
  int newCapacity = newTable.length;
  for (Entry<K,V> e : table) {
    while(null != e) {
      Entry<K,V> next = e.next;
      if (rehash) {
        e.hash = null == e.key ? 0 : hash(e.key);
      }
      int i = indexFor(e.hash, newCapacity);
      e.next = newTable[i]; 
      newTable[i] = e;
      e = next;
    }
  }
}

現在往一個容量大小為2的put兩個值,負載因子是0.75是不是我們在put第二個的時候就會進行transfer,現在我們要在容量為2的容器里面用不同執行緒插入3,7,5,假如我們在transfer之前打個斷點,那意味著資料都插入了但是還沒擴容前可能是這樣的,
可以看到鏈表的指向3->7->5:
![在這里插入圖片描述](https://img-blog.csdnimg.cn/20210221210358505.png
單鏈表的頭插入方式,同一位置上新元素總會被放在鏈表的頭部位置,在舊陣列中同一條Entry鏈上的元素,通過重新計算索引位置后,有可能被放到了新陣列的不同位置上,
在單執行緒條件下:
在這里插入圖片描述
然后在多執行緒環境下,假設有兩個執行緒A和B都在進行put操作,執行緒A在執行到transfer函式中newTable[i] = e;處掛起

 Entry<K,V> next = e.next;
 e.next = newTable[i]; 
 newTable[i] = e;
 e = next;

此時執行緒A中運行結果如下:
在這里插入圖片描述此為擴容關鍵代碼,e為原陣列的頭一個元素,也就是3,此時還未在newTable完成賦值,此時快取中e=3,e.next = null,next = 7,7.next = 5;
此時執行緒B前來擴容,執行緒A、B是共享一個table,也就是共享資料,執行緒B完成Transfer:
在這里插入圖片描述
此時,執行緒A繼續執行,但是由于共享資料,此時的7.next = 3,3.next =null,
再次貼出擴容代碼,因為核心就在這里:

void transfer(Entry[] newTable, boolean rehash) {
  int newCapacity = newTable.length;
  for (Entry<K,V> e : table) {
    while(null != e) {
      Entry<K,V> next = e.next;
      if (rehash) {
        e.hash = null == e.key ? 0 : hash(e.key);
      }
      int i = indexFor(e.hash, newCapacity);
      e.next = newTable[i]; 
      newTable[i] = e;// newTable[3] = 3
      e = next;// e = 7
    }
  }
}

執行緒A再執行時,如下:

  newTable[i] = e;// newTable[3] = 3
  e = next;// e = 7

在這里插入圖片描述繼續回圈:此時的e為A執行緒阻塞時next = 7(此為執行緒私有資料,不會改變)

e=7
next=e.next ----> next=3【從主存中取值】
e.next=newTable[3] ----> e.next=3【從主存中取值】
newTable[3]=e ----> newTable[3]=7
e=next ----> e=3

再次進行回圈:在正常情況這里就應該為null終止回圈了

e=3
next=e.next ----> next=null
e.next=newTable[3] ----> e.next=7 即:3.next=7
newTable[3]=e ----> newTable[3]=3
e=next ----> e=null

此次回圈:e.next=7,而在上次回圈中7.next=3,出現環形鏈表,并且此時e=null回圈結束,
在這里插入圖片描述只要涉及輪詢hashmap的資料結構,就會在這里發生死回圈,

嗯!你真是天生神人啊!知道這么多,不過你以為就完了?天真,1.8尾插就沒有問題了?

依然執行緒不安全,

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 2                    boolean evict) {
 3         Node<K,V>[] tab; Node<K,V> p; int n, i;
 4         if ((tab = table) == null || (n = tab.length) == 0)
 5             n = (tab = resize()).length;
 6         if ((p = tab[i = (n - 1) & hash]) == null) // 如果沒有hash碰撞則直接插入元素
 7             tab[i] = newNode(hash, key, value, null);

第6行代碼,如果沒有hash碰撞則會直接插入元素,如果執行緒A和執行緒B同時進行put操作,剛好這兩條不同的資料hash值一樣,并且該位置資料為null,所以這執行緒A、B都會進入第6行代碼中,假設一種情況,執行緒A進入后還未進行資料插入時掛起,而執行緒B正常執行,從而正常插入資料,然后執行緒A獲取CPU時間片,此時執行緒A不用再進行hash判斷了,問題出現:執行緒A會把執行緒B插入的資料給覆寫,發生執行緒不安全,

那怎么解決執行緒安全問題呢?

Java中有HashTable、Collections.synchronizedMap、以及ConcurrentHashMap可以實作執行緒安全的Map,
HashTable是直接在操作方法上加synchronized關鍵字,鎖住整個陣列,粒度比較大,Collections.synchronizedMap是使用Collections集合工具的內部類,通過傳入Map封裝出一個SynchronizedMap物件,內部定義了一個物件鎖,方法內通過物件鎖實作;ConcurrentHashMap使用分段鎖,降低了鎖粒度,讓并發度大大提高,

嗯,我還想繼續問一下關于執行緒安全問題,以及鎖粒度等,不過今天已經不早了,咋們下次再約吧!

嗯嗯嗯,好的,我們下一次再見

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

標籤:其他

上一篇:隱私合規--42--企業資料保護合規體系建設經驗總結(一)

下一篇:form表單

標籤雲
其他(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)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more