來吧!一文完全搞定哈希表!

哈希表是個啥?

小白: 慶哥,什麼是哈希表?這個哈希好熟悉,記得好像有HashMap和HashTable之類的吧,這是同樣的嘛?😊前端

慶哥: 這個哈希確實常常見😂,足以說明它是個使用很是頻繁的玩意兒,並且像你說的HashMap和HashTable之類的與哈希這個詞確定是有關係的,那哈希是個啥玩意啊,這個我們仍是得先來搞明白啥是個哈希表。😎java

咱們看看百科解釋吧:數組

散列表Hash table,也叫哈希表),是根據(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表微信

怎麼樣?看到這個,你知道哈希表是什麼了嘛?數據結構

小白: 我以前是對哈希表一竅不通啊,不過看了這個百科的解釋,我知道以下這些關於哈希表的簡單知識點:函數

一、哈希表其實也叫散列表,兩個是一個玩意,英文是Hash Table性能

二、哈希表是一個數據結構學習

這兩個概念是比較清晰的,至於其餘的說什麼映射函數叫作散列函數,存放記錄的數組叫作散列表這個就有點模糊了,尤爲說存放記錄的數組稱爲散列表,那意思是哈希表是個數組?🤣編碼

慶哥: 首先你說的很清晰的兩點說的是很準確的,哈希表也叫作散列表,這只不過是叫法而已,英文單詞是Hash table,看到這個英文單詞基本上就能猜到,哈希表其實就是直接根絕英文單詞音譯過來的,至此你應該知道了啥是哈希了吧,對於另一點,那就很重要了,那就是哈希表實際上是一種數據結構。.net

要知道數據結構有不少中,每一種都有各自的特色,那麼哈希表既然也是一種數據結構,那它有什麼特色呢?按照百科的解釋,咱們大體能知道:能夠根據一個key值來直接訪問數據,所以查找速度快

對了,你知道最基本的幾個數據結構中,哪一個的查詢效率是最高的嘛?

小白: 據我所知,應該是數組吧,咱們能夠直接使用數組下標來訪問數據,所以查詢效率是很高滴😁

慶哥: 對,很是對,哈希表其實本質上就是一個數組 。

小白: 那爲啥還叫哈希表呢?🤣,哈希表確定有啥特別的吧,爲啥本質上是一個數組呢?

哈希表本質是數組?

慶哥: 必須滴啊,哈希表本質上是個數組,只能說它的底層實現是用到了數組,簡單點說,在數組的這個基礎上再捯飭捯飭,加工加工,變得更加有特點了,而後人家就自立門戶,叫哈希表😂

小白: 這是咋個回事啊🤣

慶哥: 爲何說哈希表的本質是個數組呢?那就得看看,哈希表是怎麼來實現的了,通常來講啊,實現哈希表咱們能夠採用兩種方法:

一、數組+鏈表

二、數組+二叉樹

簡單點就有這麼兩種方式,其實說白了,不管哪一個都是必須有數組啊,都是再數組的基礎上取搞其餘的,並且好比第一種數組+鏈表的形式,本質上是出現哈希衝突的一種解決辦法,使用鏈表存放,因此綜合起來叫作數組+鏈表的方式來實現一個哈希表,另外數組中通常就是存放的單一的數據,而哈希表中存放的是一個鍵值對,這是個區別吧!

小白: 停!!!有點迷糊🤣,什麼哈希衝突,什麼玩意兒啊😂

慶哥: 🤪,好吧好吧,我說的有點着急了😂,你就記住,哈希表在本質上就是個數組就ok了。

小白: 但是我仍是像知道爲啥啊?🤣

慶哥: 彆着急啊,咱慢慢來說,另外在百科上有這麼一個例子,能夠幫助你更好的理解哈希表是個啥,它是這樣說的:

在這裏插入圖片描述

怎麼樣?看的懂嘛?

小白: 反正是有點模糊,這其中提到的函數關係啊,關鍵字啊,散列函數還有什麼函數法則的有點迷迷糊糊的🤣

哈希表的幾個概念

啥是散列函數

慶哥: 確實,這都是哈希表中很重要的幾個概念,那咱就先搞懂這幾個概念吧,我用大白話給你說說這個例子。😎

好比說,我如今給你個電話本,上面記錄的有姓名和對應的手機號,我想讓你幫我找王二的手機號是多少,那麼你會怎麼作呢?

小白: 這樣啊,那我就挨個找王二唄?😀

慶哥: 確實能夠,那麼你有沒有想過,若是這個王二是在最後幾頁,那你去豈不是前面幾頁都白找了,有沒有更快的方式呢?

小白: 也是哦,那這樣的話,是否是能夠按照人名給分個類,好比按照首字母來排序,就abcd那樣的順序,這樣根據王二我就知道去找w這些,這樣不久快不少了😁

慶哥: 的確,咱們能夠按照人名的首字母去弄一個表格,好比像這樣:

你看,假如如今我讓你去幫我找王二的手機號,你一會兒就能找到,不用再挨個的去查找了,這效率就高的多了,那麼如今重點來了,人家原本叫王二,你爲啥用一個w來標記人家呢?讓你找王二爲啥你不直接找王二而是去找w呢?

小白: 這個?😅,用w能夠更快速的去定位到王二啊😂

慶哥: 說的很對,咱們取姓名的首字母做爲一個標誌,就能夠很快的找到以這個字母開頭的人名了,那麼王二也就能更快的被咱們找到,咱們也不用再費力氣去找什麼張二和李二的,由於人家的名字首字母都不是w。

小白: 那必須啊,這個方法好吧😁

慶哥: 對對對,你說到點子上了,那就是方法二字,這裏咱們就是採用一種方法,什麼方法呢?那就是取姓名的首字母作一個排序,那麼這是否是就是經過一些特定的方法去獲得一個特定的值,好比這裏取人名的首字母,那麼若是是放到數學中,是否是就是相似一個函數似的,給你一個值,通過某些加工獲得另一個值,就像這裏的給你我的名,通過些許加工咱們拿到首字母,那麼這個函數或者是這個方法在哈希表中就叫作散列函數,其中規定的一些操做就叫作函數法則,這下你知道什麼是散列函數了吧😎

小白: 嗯呢,這下真的是很清楚了,說白了,不就是給我一個值,通過捯飭一下,變成另一個值嗎?花個圖的話就是這個樣子:

哈哈,是否是這樣?😂

慶哥: 簡單來講就是這樣滴😎,咋樣,這下知道什麼是散列函數了吧?

關鍵值key是啥?

小白: 這下知道了,很清楚😎,那這個關鍵字key是個啥玩意?

慶哥: 這個也好理解啊,就像你畫的這個圖,1是怎麼得出來得,是否是根據未加工以前得101得出來得,這個加工過程其實就是個散列函數,而丟給它的這個101就是這個關鍵值啊,爲啥叫它關鍵值嘞,那是由於咱們要對它作加工才能得出咱們想要的1啊,你說它關不關鍵😂

小白: 哦哦,原來是這樣啊,這下就明白啦!對了,我如今有這樣的一個理解,你看看對不對啊,那就是哈希表就是經過將關鍵值也就是key經過一個散列函數加工處理以後獲得一個值,這個值就是數據存放的位置,咱們就能夠根據這個值快速的找到咱們想要的數據,是否是這樣啊?😂

慶哥: 說的很正確😎,那你如今對以前那個百科的例子懂了嘛?

小白: 嗯呢,這下懂了😀

慶哥: 嗯呢,那就好,其實吧,上面的那中狀況並很差,爲啥嘞?你想啊,王二在那個位置,若是再來個王三呢?人家的首字母也是w啊,這咋辦,位置被王二佔了,那王三咋辦?這就是哈希衝突啊,撞衫啦🤣

小白: 阿西吧,又是哈希衝突,它到底彷佛個啥玩意啊😂

慶哥: 不着急,我們繼續來探究哈希表。

再探哈希表

慶哥: 咱們在以前已經知道了哈希表的本質實際上是個數組,數組有啥特色啊?

小白: 數組嘛,那就是下表從0開始啊,連續的,直接經過下標訪問,好比下面這樣:

有一個數組a,咱們能夠直接經過a[1]的形式來訪問到數值7,因此查詢效率很高。

慶哥: 徹底正確,那麼哈希表本質上是個數組,那它跟這個相似嗎?咱們來再深刻探究一下,首先看個圖:

這張圖但是信息量很大啊,你看出來個啥了嘛?

小白: 這個?我看到了哈希函數,這是啥?它跟散列函數有啥區別啊?還有Entry是個什麼鬼😂,還有鍵值對🤣,蒙圈啊😥

哈希函數

慶哥: 別蒙圈啊,我來仔細跟你說說,其實這個哈希函數就是咱們以前說的散列函數,爲啥嘞?這就跟哈希表也叫作散列表同樣啊,你叫做散列表的時候有個散列函數,那你叫哈希表的時候,也得有個哈希函數啊,這樣才公平嘛😀,咋樣,知道了吧?

小白: 我去,原來是這麼回事啊🤣,那鍵值對跟Entry嘞?

鍵值對和Entry

慶哥: 這個但是值得好好說道說道,咱們知道哈希表本質上是個數組,難道就跟數組的基本使用那樣,存個數值,而後經過下表讀取之類的嘛?固然不是啦,對於哈希表,它常常存放的是一些鍵值對的數據,啥是鍵值對啊,就是咱們常常說的key-value啊,簡單點說就是一個值對應另一個值,好比a對應b,那麼a就是key,b是value,哈希表存放的就是這樣的鍵值對,在哈希表中是經過哈希函數將一個值映射到另一個值的,因此在哈希表中,a映射到b,a就叫作鍵值,而b呢?就叫作a的哈希值,也就是hash值。

咋樣,這塊明白了嘛?

小白: 嗯嗯,明白的,慶哥繼續!😎

慶哥: 那好,咱們繼續,鍵值對說的簡單點就是有一個key和一個value對應着,好比這張圖裏的學生信息:

學生的學號和姓名就是一個鍵值對啊,根據這個學號就能找到這個學生的姓名,那啥是Entry嘞,咱們都知道鍵值對,在不少語言中也許都有鍵值對,說白了就是個大衆臉啊,咋弄,在咱jdk中可不能那麼俗氣,不能再叫鍵值對了,叫啥嘞,那就叫Entry吧😂

咋樣,知道啥是鍵值對和Entry了吧!

小白: 必須滴啊,講的那麼生動😁,這張圖感受遠不止如此啊,慶哥繼續啊😜

哈希表如何存數據

慶哥: 好滴,那我們就繼續,來講說哈希表是如何存放數據的,記得看上面的圖啊,咱們按照這個圖來講,咱們已經知道了哈希表本質是個數組,因此這裏有個數組,長度是8,如今咱們要作的是把這個學生信息存放到哈希表中,也就是這個數組中去,那咱們須要考慮怎麼去存放呢?

這裏的學號是個key,咱們以前也知道了,哈希表就是根據key值來經過哈希函數計算獲得一個值,這個值就是用來肯定這個Entry要存放在哈希表中的位置的,實際上這個值就是一個下標值,來肯定放在數組的哪一個位置上。

好比這裏的學號是101011,那麼通過哈希函數的計算以後獲得了1,這個1就是告訴咱們應該把這個Entry放到哪一個位置,這個1就是數組的確切位置的下標,也就是須要放在數組中下表爲1的位置,如圖中所示。

咱們以前已經介紹過什麼是Entry了,因此這裏你要知道,數組中1的位置存放的是一個Entry,它不是一個簡單的單個數值,而是一個鍵值對,也就是存放了key和value,key就是學號101011,value就是張三,咱們通過哈希函數計算得出的1只是爲了肯定這個Entry該放在哪一個位置而已。

如今咱們就成功把這個Entry放到了哈希表中了,怎麼樣,這塊聽懂了嘛?

小白: 嗯嗯,聽懂了,不過看到這裏我產生了一個疑問,那就是這個哈希函數,是否是有一個特定的加工過程,好比能夠通過某種計算把101011轉換成1,那麼有沒有可能其餘的學號通過哈希函數的計算也得出1呢?那這個時候是否是就撞衫啦😂

哈希衝突

慶哥: 的確,你分析得很正確,咱們再來看下面這張圖:

你說的這種狀況就像圖中展現的那樣,學號爲102011的李四,他的學號通過哈希函數的計算也得出了1,那麼也要放到數組中爲1的位置,但是這個位置以前已經被張三佔了啊,這怎麼辦?這種狀況就是哈希衝突或者也叫哈希碰撞。

既然出現了這狀況,不能無論李四啊,總得給他找個位置啊,怎麼找呢?

小白: 我猜確定有什麼方法能夠給李四找位置🤣

處理哈希衝突

慶哥: 那必須滴啊😄,有什麼方法呢?其實關於哈希衝突的解決辦法有好幾種嘞,可是我這裏只介紹兩種主要的方法,一個是開放尋址法,一個是拉鍊法。

那什麼是開放尋址法呢?咱們繼續來看圖:

我以爲看圖就足以說明問題了,這裏所說的開放尋址法其實簡單來講就是,既然位置被佔了,那就另外再找個位置不就得了,怎麼找其餘的位置呢?這裏其實也有不少的實現,咱們說個最基本的就是既然當前位置被佔用了,咱們就看看該位置的後一個位置是否可用,也就是1的位置被佔用了,咱們就看看2的位置,若是沒有被佔用,那就放到這裏唄,固然,也有可能2的位置也被佔用了,那咱就繼續往下找,看看3的位置,一次類推,直到找到空位置。

對了,Java中的ThreadLocal就是利用了開放尋址法。

小白: 啥是ThreadLocal啊😂

慶哥: 咋滴,你不知道啊,沒事,給你一篇文章,看了包裝你不再學ThreadLocal了,由於看完這篇,你就再也忘不掉啦,連接直達,走起:不再學ThreadLocal了,看這一篇就忘不掉了!(萬字總結)

小白: 嗯嗯,我會好好看看的。那什麼是拉鍊法啊?

慶哥: 拉鍊法也是比較經常使用的,像以前你說的HashMap就是使用了這種方法,那這個方法是怎麼個回事呢?咱們繼續來看圖:

以前說的開放尋址法採用的方式是在數組上另外找個新位置,而拉鍊法則不一樣,仍是在該位置,但是,該位置被佔用了咋整,總不能打一架,誰贏是誰的吧😂,固然不是這樣,這裏採用的是鏈表,什麼意思呢?就像圖中所示,如今張三和李四都要放在1找個位置上,可是張三先來的,已經佔了這個位置,待在了這個位置上了,那李四呢?解決辦法就是鏈表,這時候這個1的位置存放的不僅僅是以前的那個Entry了,此時的Entry還額外的保存了一個next指針,這個指針指向數組外的另一個位置,將李四安排在這裏,而後張三那個Entry中的next指針就指向李四的這個位置,也就是保存的這個位置的內存地址,若是還有衝突,那就把又衝突的那個Entry放在一個新位置上,而後李四的Entry中的next指向它,這樣就造成了一個鏈表。

好啦,這就是拉鍊法,咋樣,明白不😎

小白: 信息量很多啊,好在慶哥講的比較清楚,明白啦😀

慶哥: 明白了就好,那我問你一個問題啊,針對開放尋址和拉鍊法,你有沒有以爲會產生什麼問題呢?

小白: 嗯嗯,我還真有問題,首先是這個拉鍊法啊,若是衝突的不少,那這個增長的鏈表豈不是很長,這樣也不咋好吧😂

慶哥: 的確,若是衝突過多的話,這塊的鏈表會變得比較長,怎麼處理呢?這裏舉個例子吧,拿java集合類中的HashMap來講吧,若是這裏的鏈表長度大於等於8的話,鏈表就會轉換成樹結構,固然若是長度小於等於6的話,就會還原鏈表。以此來解決鏈表過長致使的性能問題。

小白: 爲啥是小於等於6啊,咋不是7嘞😂

慶哥: 這樣設計是由於中間有個7做爲一個差值,來避免頻繁的進行樹和鏈表的轉換,由於轉換頻繁也是影響性能的啊。

小白: 嗯嗯,這個知道了,關於開放尋址也有個疑問,那就是若是一直找不到空的位置咋整啊?🤣

慶哥: 這個不會的,爲啥嘞?你這樣想,是由於你考慮了一個前提,那就是位置已經被佔光了,沒有空位置了,可是實際狀況是位置不會被佔光的,由於有必定量的位置被佔了的時候就會發生擴容。

小白: 阿西吧,還有擴容,那這個擴容是咋回事呢?

哈希表的擴容

慶哥: 其實這裏不只僅是由於你說的那種狀況纔會擴容,還有一個很重要的緣由就是當哈希表被佔的位置比較多的時候,出現哈希衝突的機率也就變高了,因此頗有必要進行擴容。

那麼這個擴容是怎麼擴的呢?這裏通常會有一個增加因子的概念,也叫做負載因子,簡單點說就是已經被佔的位置與總位置的一個百分比,好比一共十個位置,如今已經佔了七個位置,就觸發了擴容機制,由於它的增加因子是0.7,也就是達到了總位置的百分之七十就須要擴容。

還拿HashMap來講,當它當前的容量佔總容量的百分之七十五的時候就須要擴容了。

並且這個擴容也不是簡單的把數組擴大,而是新建立一個數組是原來的2倍,而後把原數組的全部Entry都從新Hash一遍放到新的數組。

小白: 這個從新Hash一遍是啥意思啊?

慶哥: 由於數組擴大了,因此通常哈希函數也會有變化,這裏的Hash也就是把以前的數據經過新的哈希函數計算出新的位置來存放。

小白: 嗯嗯,原來是這麼回事啊,懂了,對了,那哈希表的數據讀取怎麼操做的啊?

哈希表如何讀取數據

慶哥: 要知道這個讀取操做,咱們還來看這個圖:

好比咱們如今要經過學號102011來查找學生的姓名,怎麼操做呢?咱們首先經過學號利用哈希函數得出位置1,而後咱們就去位置1拿數據啊,拿到這個Entry以後咱們得看看這個Entry的key是否是咱們的學號102011,一看是101011,什麼鬼,一邊去,這不是咱們要的key啊,而後根據這個Entry的next知道下一給位置,在比較key,好成功找到李四。

小白: 哦哦,原來是這麼回事啊,那對於開放尋址那種是否是也是這個思路,先肯定到這個位置,而後再看這個位置上的key是否是咱們要的,如過不是那就看看下一個位置的。

慶哥: 能夠的,徹底正確,好了如今咱們對哈希表的講解已經差很少了,那麼你以爲對於哈希表而言,什麼是核心呢?

哈希函數是核心

小白: 我以爲應該是哈希函數吧,通過上面的講解,我以爲,若是一個哈希函數設計的足夠好的話,就會減小哈希衝突的機率,若是設計的很差,那就會常常撞衫😂,那就很影響性能了,好比剛開始咱們舉的那個例子,拿姓名的首字母來肯定位置,這個哈希函數的設計就不咋滴,好比王二,王三,王四什麼的,這都會衝突啊😂

慶哥: 的確,在哈希表中,哈希函數的設計很重要,一個好的哈希函數能夠極大的提高性能,並且若是你的哈希函數設計的比較簡單粗陋,那很容易被那些不懷好意的人搗亂,好比知道了你哈希函數的規則,故意製造容易衝突的key值,那就有意思了,你的哈希表就會一直撞啊,一直撞啊😂

小白: 哈哈😂,那設計哈希函數有什麼方法嗎?

慶哥: 必須有啊,好比有直接定址法,數字分析法,摺疊法,隨機數法和除留餘數法等等,要不要繼續講啊😀

小白: 我去🤣,仍是不要了吧,消化不了啊,此次先到這吧,謝謝慶哥😘

感謝各位的閱讀!

本文原創做者:慶哥小白 轉載請註明出處,微信公衆號開白請聯繫我微信H653836923

▼ 你們好,很是感謝您能花時間閱讀個人文章,我在編碼以外維護了一個公衆號,名字就叫作「編碼以外」😀。這個公衆號是幹啥的?

編碼以外主要是用對話的形式爲你深刻講解java技術,堅持原創,並融入顏文字,讀來輕鬆有趣,詼諧幽默,且配以大量圖示加以說明,力求由淺入深,通俗易懂!對了,技術交流羣每週按期分享最新java視頻資源,如今關注,後臺回覆「慶哥」,立馬送你2019最新java視頻教程,且不斷更新ing。。。

編碼以外的福利時間 一、加入技術交流羣,每週按期分享最新java,Python和前端等視頻資源,幫下CSDN,幫找學習資源! 二、加入知識星球,獲取私人分享和精心維護的5T視頻教程,專屬微信客服服務,朋友圈按期推送最新學習資源,而且在網盤羣共享!不怕沒有資源,就怕你學不過來! 三、固然,我會優先服務於知識星球,感興趣的請勾搭我:H653836923

在這裏插入圖片描述

長按二維碼識別關注!

相關文章
相關標籤/搜索