主頁 > 軟體設計 > 七夕也要學起來,哈希哈希哈希!

七夕也要學起來,哈希哈希哈希!

2020-09-10 05:45:23 軟體設計

file

前言

本文收錄于專輯:http://dwz.win/HjK,點擊解鎖更多資料結構與演算法的知識,

你好,我是彤哥,

上一節,我們一起學習了,在Java中如何構建高性能佇列,里面牽涉到很多底層的知識,不知道你有Get到多少呢?!

本節,我想跟著大家一起重新學習下關于哈希的一切——哈希、哈希函式、哈希表,

這三者有什么樣的愛恨情仇?

為什么Object類中需要有一個hashCode()方法?它跟equals()方法有什么關系?

如何撰寫一個高性能的哈希表?

Java中的HashMap中的紅黑樹可以使用其它資料結構替換嗎?

何為哈希?

Hash,是指把任意長度的輸入通過一定的演算法變成固定長度的輸出的程序,這個輸出稱作Hash值,或者Hash碼,這個演算法叫做Hash演算法,或者Hash函式,這個程序我們一般就稱作Hash,或者計算Hash,Hash翻譯為中文有哈希、散列、雜湊等,

file

既然是固定長度的輸出,那就意味著輸入是無限多的,輸出是有限的,必然會出現不同的輸入可能會得到相同的輸出的情況,所以,Hash演算法一般來說也是不可逆的,

那么,Hash演算法有哪些用途呢?

哈希演算法的用途

哈希演算法,是一種廣義的演算法,或者說是一種思想,它沒有一個固定的公式,只要滿足上面定義的演算法,都可以稱作Hash演算法,

通常來說,它具有以下用途:

  1. 加密密碼,比如,使用MD5+鹽的方式來加密密碼;
  2. 快速查詢,比如,哈希表的使用,通過哈希表能夠快速查詢元素;
  3. 數字簽名,比如,系統間呼叫加上簽名,可以防止篡改資料;
  4. 檔案檢驗,比如,下載騰訊游戲的時候通常都有有一個MD5值,安裝包下載下來之后計算出來一個MD5值與官方的MD5值進行對比,就可知道下載程序中有沒有檔案損壞,有沒有被篡改等;

好了,說起Hash演算法,或者Hash函式,在Java中,所有物件的父類Object都有一個Hash函式,即hashCode()方法,為什么Object類中需要定義這么一個方法呢?

嚴格來說,Hash演算法和Hash函式還是有點區別的,相信你能根據語境進行區分,

讓我們來看看JDK原始碼的注釋怎么說:

file

請看紅框的部分,翻譯一下大致為:為這個物件回傳一個Hash值,它是為了更好地支持哈希表而存在的,比如HashMap,簡單點說,這個方法就是給HashMap等哈希表使用的,

// 默認回傳的是物件的內部地址
public native int hashCode();

此時,我們不得不提起Object類中的另一個方法——equals(),

// 默認是直接比較兩個物件的地址是否相等
public boolean equals(Object obj) {
    return (this == obj);
}

hashCode()和equals又有怎樣的糾纏呢?

通常來說,hashCode()可以看作是一種弱比較,回歸Hash的本質,將不同的輸入映射到固定長度的輸出,那么,就會出現以下幾種情況:

  1. 輸入相同,輸出必然相同;
  2. 輸入不同,輸出可能相同,也可能不同;
  3. 輸出相同,輸入可能相同,也可能不同;
  4. 輸出不同,輸入必然不同;

而equals()是嚴格比較兩個物件是否相等的方法,所以,如果兩個物件equals()為true,那么,它們的hashCode()一定要相等,如果不相等會怎樣呢?

如果equals()回傳true,而hashCode()不相等,那么,試想將這兩個物件作為HashMap的key,它們很大可能會定位到HashMap不同的槽中,此時就會出現一個HashMap中插入了兩個相等的物件,這是不允許的,這也是為什么重寫了equals()方法一定要重寫hashCode()方法的原因,

比如,String這個類,我們都知道它的equals()方法是比較兩個字串的內容是否相等,而不是兩個字串的地址,下面是它的equals()方法:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

所以,對于下面這兩個字串物件,使用equals()比較它們是相等的,而它們的記憶體地址并不相同:

String a = new String("123");
String b = new String("123");
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false

此時,如果不重寫hashCode()方法,那么,a和b將回傳不同的hash碼,對于我們常常使用String作為HashMap的key將造成巨大的干擾,所以,String重寫的hashCode()方法:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

這個演算法也很簡單,用公式來表示為:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],

好了,既然這里屢次提到哈希表,那我們就來看看哈希表是如何一步步進化的,

哈希表進化史

陣列

講哈希表之前,我們先來看看資料結構的鼻祖——陣列,

陣列比較簡單,我就不多說了,大家都會都懂,見下圖,

file

陣列的下標一般從0開始,依次往后存盤元素,查找指定元素也是一樣,只能從頭(或從尾)依次查找元素,

比如,要查找4這個元素,從頭開始查找的話需要查找3次,

早期的哈希表

上面講了陣列的缺點,查找某個元素只能從頭或者從尾依次查找元素,直到匹配為止,它的均衡時間復雜是O(n),

那么,利用陣列有沒有什么方法可以快速的查找元素呢?

聰明的程式員哥哥們想到一種方法,通過哈希函式計算元素的值,用這個值確定元素在陣列中的位置,這樣時間復雜度就能縮短到O(1)了,

比如,有5個元素分別為3、5、4、1,把它們放入到陣列之前先通過哈希函式計算位置,精確放置,而不是像簡單陣列那樣依次放置元素(基于索引而不是元素值來查找位置),

假如,這里申請的陣列長度為8,我們可以造這么一個哈希函式為hash(x) = x % 8,那么最后的元素就變成了下圖這樣:

file

這時候我們再查找4這個元素,先算一下它的hash值為hash(4) = 4 % 8 = 4,所以直接回傳4號位置的元素就可以了,

進化的哈希表

事情看著挺完美,但是,來了一個元素13,要插入的哈希表中,算了一下它的hash值為hash(13) = 13 % 8 = 5,納尼,它計算的位置也是5,可是5號已經被人先一步占領了,怎么辦呢?

這就是哈希沖突

為什么會出現哈希沖突呢?

因為我們申請的陣列是有限長度的,把無限的數字映射到有限的陣列上早晚會出現沖突,即多個元素映射到同一個位置上,

好吧,既然出現了哈希沖突,那么我們就要解決它,必須干!

How to?

線性探測法

既然5號位置已經有主了,那我元素13認慫,我往后挪一位,我到6號位置去,這就是線性探測法,當出現沖突的時候依次往后挪直到找到空位置為止,

file

然鵝,又來了個新元素12,算得其hash值為hash(12) = 12 % 8 = 4,What?按照這種方式,要往后移3次到7號位置才有空位置,這就導致了插入元素的效率很低,查找也是一樣的道理,先定位的4號位置,發現不是我要找的人,再接著往后移,直到找到7號位置為止,

二次探測法

使用線性探測法有個很大的弊端,沖突的元素往往會堆積在一起,比如,12號放到7號位置,再來個14號一樣沖突,接著往后再陣列結尾了,再從頭開始放到0號位置,你會發現沖突的元素有聚集現象,這就很不利于查找了,同樣不利于插入新的元素,

這時候又有聰明的程式員哥哥提出了新的想法——二次探測法,當出現沖突時,我不是往后一位一位這樣來找空位置,而是使用原來的hash值加上i的二次方來尋找,i依次從1,2,3...這樣,直到找到空位置為止,

還是以上面的為例,插入12號元素,程序是這樣的,本文來源于公主號彤哥讀原始碼:

file

這樣就能很快地找到空位置放置新元素,而且不會出現沖突元素堆積的現象,

然鵝,又來了新元素20,你瞅瞅放哪?

發現放哪都放不進去了,

研究表明,使用二次探測法的哈希表,當放置的元素超過一半時,就會出現新元素找不到位置的情況,

所以又引出一個新的概念——擴容,

什么是擴容?

已放置元素達到總容量的x%時,就需要擴容了,這個x%時又叫作擴容因子

很顯然,擴容因子越大越好,表明哈希表的空間利用率越高,

所以,很遺憾,二次探測法無法滿足我們的目標,擴容因子太小了,只有0.5,一半的空間都是浪費的,

這時候又到了程式員哥哥們發揮他們聰明特性的時候了,經過996頭腦風暴后,又想出了一種新的哈希表實作方式——鏈表法,

鏈表法

不就是解決沖突嘛!出現沖突我就不往陣列中去放了,我用一個鏈表把同一個陣列下標位置的元素連接起來,這樣不就可以充分利用空間了嘛,啊哈哈哈哈~~

file

嘿嘿嘿嘿,完美△△,

真的完美嘛,我是一名黑客,我一直往里面放*%8=4的元素,然后你就會發現幾乎所有的元素都跑到同一個鏈表中去了,呵呵,最后的結果就是你的哈希表退化成了鏈表,查詢插入元素的效率都變成了O(n),

file

此時,當然有辦法,擴容因子干啥滴?

比如擴容因子設定為1,當元素個數達到8個時,擴容成兩倍,一半的元素還在4號位置,一半的元素去到了12號位置,能緩解哈希表的壓力,

然鵝,依舊不是很完美,也只是從一個鏈表變成兩個鏈表,本文來源于公主號彤哥讀原始碼,

聰明的程式員哥哥們這次開啟了一次長大9127的頭腦風暴,終于搞出了一種新的結構——鏈表樹法,

鏈表樹法

雖然上面的擴容在元素個數比較少的時候能解決一部分問題,整體的查找插入效率也不會太低,因為元素個數少嘛,

但是,黑客還在攻擊,元素個數還在持續增加,當增加到一定程度的時候,總會導致查找插入效率特別低,

所以,換個思路,既然鏈表的效率低,我把它升級一下,當鏈表長的時候升級成紅黑樹怎么樣?

嗯,我看行,說干就干,

file

嗯,不錯不錯,媽媽再也不怕我遭到黑客攻擊了,紅黑樹的查詢效率為O(log n),比鏈表的O(n)要高不少,

所以,到這就結束了嗎?

你想多了,每次擴容還是要移動一半的元素好么,一顆樹分化成兩顆樹,這樣真的好么好么好么?

程式員哥哥們太難了,這次經過了12127的頭腦風暴,終于想出個新玩意——一致性Hash,

一致性Hash

一致性Hash更多地是運用在分布式系統中,比如說Redis集群部署了四個節點,我們把所有的hash值定義為0~2^32個,每個節點上放置四分之一的元素,

此處只為舉例,實際Redis集群的原理是這樣的,具體數值不是這樣的,

此時,假設需要給Redis增加一個節點,比如node5,放在node3和node4中間,這樣只需要把node3到node4中間的元素從node4移動到node5上面就行了,其它的元素保持不變,

這樣,就增加了擴容的速度,而且影響的元素比較少,大部分請求幾乎無感知,

file

好了,到這里關于哈希表的進化歷史就講到這里了,你有沒有Get到呢?

后記

本節,我們一起重新學習了關于哈希、哈希函式、哈希表相關的知識,在Java中,HashMap的終極形態是以陣列+鏈表+紅黑樹的形式呈現的,

據說,這個紅黑樹還可以換成其它的資料結構,比如跳表,你造嗎?

下一節,我們就來聊聊跳表這個資料結構,并使用它來改寫HashMap,欲獲取最新推廣,快點來關注我吧!

關注公號主“彤哥讀原始碼”,解鎖更多原始碼、基礎、架構知識,

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

標籤:架構設計

上一篇:為什么TCP的初始序列號是隨機的

下一篇:Java 在線考試系統專案原始碼 springboot mybaits vue.js 前后分離跨域

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

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more