Java中equals和hashcode用法
equals和hashCode的關(guān)系
1.基本規(guī)則:
- 如果兩個對象根據(jù)
equals
方法被認為是相等的,那么它們的hashCode
值必須相等。 - 如果兩個對象的
hashCode
值相等,它們不一定通過equals
方法相等。
2.原因:
- 哈希表數(shù)據(jù)結(jié)構(gòu)(如
HashMap
、HashSet
)使用對象的哈希碼(hashCode
)來快速查找對象。 - 當(dāng)你向
HashSet
中添加一個對象時,集合會先調(diào)用該對象的hashCode
方法來找到該對象應(yīng)放置的“桶”(bucket)。 - 如果在桶中找到了對象的哈希碼相同的其他對象,集合會再調(diào)用
equals
方法來判斷這些對象是否真正相等。
equals 方法
用于比較兩個對象的內(nèi)容是否相同。
默認情況下(如果沒有重寫),Object
類中的equals
方法是比較兩個對象的內(nèi)存地址(即引用),只有當(dāng)兩個引用指向同一個對象時才返回true
。
哈希表的工作原理
- 哈希碼 (
hashCode
) 用于快速定位對象: 哈希表使用哈希碼來將對象分配到不同的“桶”中,從而加速查找過程。哈希碼相同的對象會被放置在同一個桶中。 - 相等 (
equals
) 方法 用于在桶中比較對象: 當(dāng)兩個對象的哈希碼相同(即它們位于同一個桶中),哈希表會使用equals
方法來逐一比較這些對象,以判斷它們是否真的是相等的。
為什么 equals 和 hashCode 必須保持一致性?
假設(shè)兩個對象相等(x.equals(y)
返回 true
),但它們的哈希碼不同(x.hashCode() != y.hashCode()
):
- 在這種情況下,如果將其中一個對象添加到
HashSet
中,然后查找另一個對象,哈希表會去不同的桶中查找,這樣可能找不到它。 - 這違背了
HashSet
等集合的合同,即它們不允許重復(fù)對象(根據(jù)equals
方法判斷相等)。
如果兩個對象的哈希碼相同(x.hashCode() == y.hashCode()
),但它們不相等(x.equals(y)
返回 false
):
- 這是可以的,因為哈希表會使用
equals
方法來判斷真正的相等性。 - 唯一的影響是性能上的下降,因為桶中的對象數(shù)量增加了。
HashSet
HashSet 的基本工作原理
HashSet
是基于 哈希表 實現(xiàn)的集合,它用于存儲沒有重復(fù)值的對象。
它的效率很高,通常 add
、remove
和 contains
操作都可以在常量時間(O(1))內(nèi)完成。然而,這種高效率依賴于正確實現(xiàn) hashCode
和 equals
方法。
工作步驟:
- 添加元素到
HashSet
中: 當(dāng)你調(diào)用HashSet
的add
方法時,HashSet
會先調(diào)用該對象的hashCode
方法計算出該對象的哈希碼。 - 查找存儲桶(bucket):
HashSet
使用哈希碼來決定將該對象存儲在哪個 桶(bucket) 中。每個桶可以包含多個對象(這是處理哈希沖突的方式)。桶是HashSet
中用于存儲對象的數(shù)組中的某個位置。 - 檢查對象的相等性: 如果哈希表中已經(jīng)有其他對象在相同的桶中,
HashSet
會通過調(diào)用equals
方法逐個比較這些對象,看看新對象是否已經(jīng)存在。如果equals
返回true
,則HashSet
不會再插入這個對象,因為集合中不能有重復(fù)的對象。 - 添加到集合: 如果
hashCode
和equals
都確認新對象與集合中的任何對象都不相等,HashSet
就會把這個新對象放入集合中。
hashCode 和 equals 如何協(xié)作
1. hashCode
用于快速定位
- 當(dāng)插入或查找元素時,
HashSet
首先根據(jù)對象的hashCode
值找到相應(yīng)的桶(bucket)。 - 哈希碼的作用是將對象分散到不同的桶中,以提高查詢效率。
2. equals
用于精確比較
- 因為不同對象可能有相同的
hashCode
值(稱為哈希沖突),所以HashSet
在找到對應(yīng)的桶后,還需要進一步用equals
方法檢查桶中是否有真正相等的對象。 - 如果
equals
返回true
,表示這個對象已經(jīng)存在,不再添加。
哈希沖突
哈希沖突(Hash Collision)是在使用哈希表或類似的數(shù)據(jù)結(jié)構(gòu)時,兩個或多個不同的對象生成了相同的哈希碼(hashCode
),導(dǎo)致它們被存儲在哈希表的同一個位置(即同一個桶,bucket)。雖然哈希碼相同,但這些對象在邏輯上是不相等的(equals
返回 false
)。
為什么會發(fā)生哈希沖突?
哈希碼的范圍是有限的(通常是32位整數(shù)),但可能存儲的對象組合是無限的。
因此,不同的對象有可能生成相同的哈希值,這是哈希沖突發(fā)生的根本原因。
如何處理哈希沖突?
現(xiàn)代哈希表通過多種方式處理哈希沖突,以下是常見的兩種方法:
鏈地址法(Separate Chaining):
- 當(dāng)多個對象具有相同的哈希值時,它們會存儲在同一個“桶”中,而每個桶內(nèi)部可以通過鏈表、樹等數(shù)據(jù)結(jié)構(gòu)來存放多個對象。
- 當(dāng)需要查找對象時,先通過哈希值找到對應(yīng)的桶,然后再在桶內(nèi)通過逐個比較
equals
來找到目標對象。
開放地址法(Open Addressing):
- 這種方法不使用鏈表,而是當(dāng)哈希沖突發(fā)生時,直接尋找哈希表中下一個可用的位置(通過某種探查策略,如線性探查、二次探查等),將對象存儲到其他空閑位置。
哈希沖突的影響
- 哈希沖突會導(dǎo)致哈希表的性能下降,因為需要處理沖突的額外操作,如遍歷鏈表或進行線性探查。
- 當(dāng)沖突過多時,哈希表的操作效率會從理想的 O(1) 降到 O(n),特別是在極端情況下(例如所有對象都被放入同一個桶中)。
如何降低哈希沖突的發(fā)生
1.使用良好的哈希碼算法:
- 避免簡單的哈希計算,如直接返回某個字段的值。
- Java 中常用的做法是結(jié)合對象的多個字段進行哈希碼的計算,并使用質(zhì)數(shù)乘數(shù)(例如
31
)來減少沖突。
2.均勻分布哈希碼:
- 確保
hashCode
方法生成的哈希值在可能的范圍內(nèi)盡量均勻分布。 - 不同的對象應(yīng)該有不同的哈希碼,即使它們的某些屬性相同。
哈希沖突是不可避免的,因為哈希碼的范圍有限,而對象的可能組合是無限的。因此,即使是使用良好的哈希算法,也難以避免沖突。
總結(jié)
在 Java 中,`equals` 和 `hashCode` 方法密切相關(guān),必須保持一致性:如果兩個對象通過 `equals` 方法相等,它們的 `hashCode` 也必須相同。
這對于基于哈希的數(shù)據(jù)結(jié)構(gòu)(如 `HashMap`、`HashSet`)至關(guān)重要,因為這些結(jié)構(gòu)依賴哈希值進行快速查找和存儲。
為了減少哈希沖突,`hashCode` 的計算通常結(jié)合多個字段,并使用質(zhì)數(shù)(如 31)進行乘法,以保證哈希值的均勻分布和較低的沖突率。設(shè)計良好的 `equals` 和 `hashCode` 方法可以確保對象比較的準確性與哈希結(jié)構(gòu)的高效性。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java?Servlet響應(yīng)httpServletResponse過程詳解
HttpServletResponse是處理http響應(yīng)的對象,調(diào)用該對象的方法,設(shè)置到對象屬性的內(nèi)容,tomcat最終會組織為http響應(yīng)報文2022-02-02Spring中allowedOriginPatterns和allowedOrigins方法有何不同詳解
這篇文章主要給大家介紹了關(guān)于Spring中allowedOriginPatterns和allowedOrigins方法有何不同,allowedOriginPatterns和allowedOrigins都是用來設(shè)置允許跨域請求的來源,需要的朋友可以參考下2023-10-10SpringBoot如何注冊Servlet、Filter、Listener的幾種方式
在Servlet 3.0之前都是使用web.xml文件進行配置,這篇文章主要介紹了SpringBoot如何注冊Servlet、Filter、Listener的幾種方式,在Servlet 3.0之前都是使用web.xml文件進行配置,2018-10-10