如何正確實現 Java 中的 HashCode

相等 和 Hash Code

從一般角度來看,Equality 是不錯的,但是 hash code 更則具技巧性。如果我們在 hash code上多下點功夫,我們就能了解到 hash code 就是用在細微處去提升性能的。

大部分的數據結構使用equals去檢查是否他們包含一個元素。例如:

1
2
List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");

這個變量 contains 是true。因為他們是相等的,雖然b的實例化(instance)雖然不完全一樣(再說一次,忽略String interning)。

將傳遞給 contains 的實例與每個元素進行比較很浪費時間。還好,整個這類數據結構使用了一種更高效的方法。它不會將請求的實例與每個元素比較,而是使用捷徑,找到可能與之相等的實例,然后只比較這幾項。

這個捷徑就是哈希碼——從對象計算出來的一個能代表該對象的整數值。與哈希碼相同的實例不必相等,但相等的實例一定有相同的哈希碼。(或者說應該有,我們稍后會對這個問題進行簡單討論)。這類的數據結構常常使用這種技術命名,在名稱中加入 Hash 以便識別,其中最具代表性的就是 HashMap。

一般情況下它們會這樣進行:

  • 添加一個元素的時候,使用它的哈希碼來計算存放在內部數組(稱為桶)中的位置(序號)。
  • 另一個不等同的元素如果具有相同的哈希碼,它會被放在同一個桶中,與原來那個放在一起,比如把它們放在一個列表中。
  • 如果傳遞一個實例給 contains 方法,會先計算它的哈希碼來找到桶,只有同一個桶中的元素需要與這個實例進行比較。

使用這種方法實現 contains 的情況很少,在理想的狀態下根本不需要 equals 比較。

將 equals、hashCode 定義在 Object 中。

關于哈希的一些思考

如果把 hashCode 作為一種快捷方式取決于其是否相等,那么只有一件事情我們需要關心:相等的對象應該有一致的哈希碼。

這也是為什么,如果我們覆寫 equals 方法,就必須創建一個匹配的 hashCode 實現!此外,實現 equal 應該是依據我們的實現而實現的,這可能會導致沒有相同的哈希碼,因為他們使用的是 Object 的實現。

hashCode 約定

從原文檔引用:

對于 hashCode 的一般約定:

  • 在 Java 應用程序中,任何時候對同一對象多次調用 hashCode 方法,都必須一直返回同樣的整數,對它提供的信息也用于對象的相等比較,且不會被修改。這個整數在兩次對同一個應用程序的執行不中不需要保持一致。
  • 如果兩個對象通過 equals(Object) 方法來比較相等,那么這兩個對象的 hashCode 方法必須產生同樣的整型結果。
  • 如果兩個對象通過 equals(Object) 方法比較結果不等,這兩個對象的 hashCode 不必產生同不整型結果。然而,開發者應該了解對不等的對象產生不同的整型結果有助于提高哈希表的性能。

第一條反映了 equals 的一致性。第二條是我們在上面提到的要求。第三條陳述了我們下面要討論的一個重要細節。

實現 hashCode

Person.hashCode 有個很簡單的實現:

1
2
3
4
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}

通過計算相關字段的哈希碼,再把這些哈希碼組合起來得到 person 的哈希碼。它們用 Object 的工具函數 hash 來參與計算。

選擇字段

然而什么字段才是相關的?這些要求有助于回答這個問題:如果相等的對象必須有相同的哈希碼,那么在計算哈希碼的時候就不應該使用那些不用于相等性檢查的字段。(否則,如果兩個對象只有那些字段不同的話,它們會相等但哈希碼不同。)

所以用于計算哈希碼的那些字段應該是用于相等性比較的那些字段的子集。默認情況下,它們會使用相同的字段,但有幾個細節需要考慮。

一致性

第一是一致性要求。它應該經過非常嚴格的計算。如果有字段產生了變化,哈希碼也應該允許變化(對于可變類來說,這往往是不可避免的),依賴哈希的數據結構并未準備應付這種情況。

正如我們在上面看到的那樣,哈希碼用于確定一個元素的桶,但是如果哈希相關的字段發生變化,并不會立即重新計算哈希碼,而且內部的數組也不會更新。

這就意味著,再對一個相等的對象甚至同一個對象的查詢會失敗!這個數據結構會計算當前的哈希碼,這個哈希碼與實例存入時的哈希碼并不相同,這直接導致找錯了桶。

小結:最好不要用可變的字段來計算哈希碼!

性能

哈希碼可能最終會在每次調用 equals 的時候計算,這可能正好發生在代碼中性能極為關鍵的部分,所以考慮性能是很有意義的。相比之下 equals 的優化空間就非常小。

除非是使用了復雜的算法,或者使用的字段非常非常多,組合他們哈希碼的計算成本可以忽略不計,因為這不可避免。但是應該考慮是否所有字段都需要包含在計算中!尤其應該以審視的眼光來看待集合,例如計算列表和集合中所有元素的哈希碼。需要根據不同的情況來考慮是否需要它們參與計算。

如果性能是關鍵,使用 Object.hash 就可能不是最好的選擇,因為它會為可變參數創建數組。

一般的優化原則是:謹慎處理!使用一個公共哈希算法的,可能需要放棄集合,并在分析可能的改進之后進行優化。

碰撞

如果只關注性能,下面這個實例怎么樣?

1
2
3
4
@Override
public int hashCode() {
return 0;
}

毫無疑問,它很快。而且相等的對象會有相同的哈希碼,這也讓我們覺得不錯。還有個亮點,它不涉及可變的字段!

但是,想想我們提到的桶是什么?這種情況下所有實例會被裝進同一個桶中!通常這會導致使用一個鏈表來容納所有元素,這樣的性能太糟糕了——比如,每次執行 contains 都會對列表進行線性掃描。

因此,我們得讓每個桶里的內容盡可能的少!一個即使對非常相似的對象計算的哈希碼也大不相同的算法,會是一個不錯的開始。

如何取得,一定程度上取決于選擇的字段。我們用于計算的細節,更多時候是為了生成不同的哈希碼。注意,這與我們對性能的想法完全相反。結果很有趣,用太多或者太少字段都會導致性能不佳。

防止碰撞的算法是哈希算法的另一部分。

計算哈希

計算字段的哈希碼最簡單的辦法就是直接調用這個字段的 `hashCode`。可以手工來進行合并。一個公共算法是從任意的某個數開始,讓它與另一個數(通常是一個小素數)相乘,再加上一個字段的哈希碼,然后重復:

1
2
3
4
5
int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;

這有可能造成溢出,但這不是什么大問題,因為在 Java 中不會引發異常。

注意,如果輸入數據有著特定的模式,最好的哈希算法都可能出現異常頻繁的碰撞。舉個簡單的例子,假設我們用一個點的 x 坐標和 y 坐標來計算哈希。一開始不太糟,直到我們發現這樣一條直線上的點:f(x) = -x,這些點的 x + y = 0。就會發生大量的碰撞!

轉載于:https://www.cnblogs.com/frankyou/p/9848681.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/396372.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/396372.shtml
英文地址,請注明出處:http://en.pswp.cn/news/396372.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一億小目標成就_成就卓越的一種方式:自我選擇

一億小目標成就by Prosper Otemuyiwa通過Prosper Otemuyiwa 成就卓越的一種方式&#xff1a;自我選擇 (One way to Greatness: Pick Yourself) I’ve heard many people say this: “I want to be great”, but most people only just have wild thoughts & imaginations …

java操作文件愛女_Java的IO操作---File類

目標1)掌握File類作用2)可以使用file類中方法對文件進行讀寫操作。File類唯一與文件有關的類。使用file類可進行創建或刪除操作&#xff0c;要想使用File類&#xff0c;首先觀察File類的構造方法。public File(String pathname);實例化File類的時候&#xff0c;必須設置好路徑。…

openssl創建私有ca

openssl創建私有ca1.ssl大概內容PKI&#xff1a;公鑰基礎設施結構CA&#xff1a;證書權威機構&#xff0c;PKI的核心CRL&#xff1a;證書吊銷列表,使用證書之前需要檢測證書有效性證書存儲格式常見的X509格式包含內容 公鑰有效期限證書的合法擁有人證書該如何使用CA的信息CA簽名…

查詢顯示注釋_SQL的簡單查詢

1.基本的查詢語句-- *代表查詢所有的列select * from <表名>;distinct表示列中不包括重復的值&#xff0c;例如select distinct 姓名&#xff1b;如果是select distinct 姓名,學號&#xff1b;則表示姓名和學號都重復的值才會顯示。as為列設定別名&#xff0c;例如select…

【AC自動機】【數據結構】【樹】【Aho-Corasick automation】AC自動機理解(入門)...

引入 我們首先提出一個問題&#xff1a; 給出n個串每個串的長度≤m 然后給出一個長度為k的串&#xff0c;詢問前n個串中有多少個是匹配成了的 暴力搜索 這題不是sb題目嗎&#xff1f; 隨隨便便O(kmn)跑過。 。。。。 n10000 m50 k1000000 。。。。 好吧——我們用AC自動…

域控dns無法解析域控_域注冊商,DNS和托管

域控dns無法解析域控by ????? ??????????由??????????????? 域名注冊商&#xff0c;DNS和托管 (Domain registrars, DNS, and hosting) 如何正確設置網站 (How to set up your website the right way) It took me a while to set up the infras…

java 棧空間_初學JAVA——棧空間堆空間的理解

1.Person pangzi; //這是在“開拓空間”于棧空間pangzinew Person(); //這是賦值于堆空間上兩步就是在做與空間對應的事。2.值類型直接存入棧空間&#xff0c;如AF&#xff0c;引用類型存入堆空間&#xff0c;在棧空間存有“索引地址”&#xff0c;如當需要B時&#xff0…

二進制安裝kubernetes v1.11.2 (第八章 kube-apiserver 部署)

繼續上一章部署。 八、部署kube-apiserver組件 使用第七章的haproxy和keepalived部署的高可用集群提供的VIP&#xff1a;${MASTER_VIP} 8.1 下載二進制文件&#xff0c;參考 第三章  8.2 創建 kubernetes 證書和私鑰 source /opt/k8s/bin/environment.sh cat > kubernetes-…

element手機驗證格式_vue封裝 element-ui form表單驗證 正則匹配手機號 自定義校驗表格內容...

效果image.png在methods中//檢查手機號isCellPhone(val) {if (!/^1(3|4|5|6|7|8)\d{9}$/.test(val)) {return false;} else {return true;}}在template中v-model"forgetForm.phone"type"text"auto-complete"off"placeholder"請輸入你的手機…

multi-mechanize error: can not find test script: v_user.py問題

從github上下載&#xff0c;安裝multi-mechanize&#xff0c;新建工程&#xff0c;運行工程報錯。 環境&#xff1a; win7-x64, python 2.7 multi-mechanize can not find test script: v_user.py 查看了github上的工程&#xff0c;項目無人維護&#xff0c;這個問題2016年11月…

@RequestMapping 用法詳解之地址映射

引言&#xff1a; 前段時間項目中用到了RESTful模式來開發程序&#xff0c;但是當用POST、PUT模式提交數據時&#xff0c;發現服務器端接受不到提交的數據&#xff08;服務器端參數綁定 沒有加任何注解&#xff09;&#xff0c;查看了提交方式為application/json&#xff0c; 而…

我的第一個網頁 代碼_我在免費代碼營的第一個月

我的第一個網頁 代碼by Elliott McNary埃利奧特麥克納里(Elliott McNary) 我在免費代碼營的第一個月 (My First Month At Free Code Camp) I wanted to build an app that would help artists to make more money.我想開發一個可以幫助藝術家賺更多錢的應用。 I had a clear …

java pem rsa_如何從java中的pfx文件/ pem文件中獲取RSA公鑰的指數和模數值

I want to extract information about RSA Public Key from the pfx file using java.我有一個pfx文件并轉換為x509 Pem文件 . 從pem文件&#xff0c;在終端中使用以下命令&#xff1a;openssl x509 -in file.pem -text我能夠查看公鑰指數和模數值主題公鑰信息&#xff1a;Publ…

jmeter+maven+jenkins自動化接口測試(下)

mavenjmeter已經寫好了&#xff0c;可以通過maven來執行jmeter的接口測試腳本&#xff0c;怎樣實現定時執行測試并發送報告郵件就需要通過jenkins了&#xff08;jmeter或者testng也可以結合不同的郵件jar包來發送郵件&#xff0c;這里使用jenkins&#xff09; 安裝jenkins筆記有…

在使用angularjs過程,ng-repeat中track by的作用

轉載&#xff1a;http://segmentfault.com/q/1010000000405730<div ng-repeat"links in slides"> <div ng-repeat"link in links track by $index">link.name</div></div>Error: [ngRepeat:dupes]這個出錯提示具體到題主的情況…

java判斷讀到末尾_IO流如何判斷讀取到了流的結尾,程序中以-1來判斷,是流中寫入一個EOF表示流結束嗎,底層實現呢?...

-1不是流中寫入的數據。read()方法返回的數據都是unsigned byte&#xff0c;即是[0,255]。底層實現有很多&#xff0c;比如socket IO和文件IO&#xff0c;甚至你自己也可以實現。——————————————————————給兩個類的代碼給你看看&#xff0c;理解一下這個東…

結束書

by William Countiss威廉Countiss 結束書 (Closing the Book on Closures) JavaScript closures are an important, but notoriously confusing concept. There’s no escaping it — if you want to grow as a developer, you need to understand what closures are and how …

java激勵_激勵干個人java的不足之處

1.你需要精通面向對象分析與設計(OOA/OOD)、涉及模式(GOF&#xff0c;J2EEDP)以及綜合模式。你應該十分了解UML&#xff0c;尤其是class&#xff0c;object&#xff0c;interaction以及statediagrams。2.你需要學習JAVA語言的基礎知識以及它的核心類庫(collections&#xff0c;…

Bioconductor軟件安裝與升級

1 安裝工具Bioc的軟件包不能使用直接install.packages函數&#xff0c;它有自己的安裝工具&#xff0c;使用下面的代碼&#xff1a; source("https://bioconductor.org/biocLite.R")biocLite() 上面第二個語句將安裝Bioconductor一些基礎軟件包&#xff0c;包括BiocI…

Laravel Kernel引導流程分析

Laravel Kernel引導流程分析 代碼展示 protected function sendRequestThroughRouter($request) {# $this->app->instance(request, $request);# Facade::clearResolvedInstance(request);// 主要是這句代碼$this->bootstrap();# return (new Pipeline($this->app)…