欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java hashCode原理以及與equals()區(qū)別聯(lián)系詳解

 更新時(shí)間:2022年11月29日 11:53:30   作者:小蝦仁蕪湖  
在 Java 應(yīng)用程序執(zhí)行期間,在同一對(duì)象上多次調(diào)用 hashCode 方法時(shí),必須一致地返回相同的整數(shù),前提是對(duì)象上 equals 比較中所用的信息沒有被修改。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無需保持一致

1、什么是hashCode

hashCode就是對(duì)象的散列碼,是根據(jù)對(duì)象的某些信息推導(dǎo)出的一個(gè)整數(shù)值,默認(rèn)情況下表示是對(duì)象的存儲(chǔ)地址。通過散列碼,可以提高檢索的效率,主要用于在散列存儲(chǔ)結(jié)構(gòu)中快速確定對(duì)象的存儲(chǔ)地址,如Hashtable、hashMap中。

為什么說hashcode可以提高檢索效率呢?我們先看一個(gè)例子,如果想判斷一個(gè)集合是否包含某個(gè)對(duì)象,最簡(jiǎn)單的做法是怎樣的呢?逐一取出集合中的每個(gè)元素與要查找的對(duì)象進(jìn)行比較,當(dāng)發(fā)現(xiàn)該元素與要查找的對(duì)象進(jìn)行equals()比較的結(jié)果為true時(shí),則停止繼續(xù)查找并返回true,否則,返回false。如果一個(gè)集合中有很多個(gè)元素,比如有一萬個(gè)元素,并且沒有包含要查找的對(duì)象時(shí),則意味著你的程序需要從集合中取出一萬個(gè)元素進(jìn)行逐一比較才能得到結(jié)論,這樣做的效率是非常低的。這時(shí),可以采用哈希算法(散列算法)來提高從集合中查找元素的效率,將數(shù)據(jù)按特定算法直接分配到不同區(qū)域上。將集合分成若干個(gè)存儲(chǔ)區(qū)域,每個(gè)對(duì)象可以計(jì)算出一個(gè)哈希碼,可以將哈希碼分組(使用不同的hash函數(shù)來計(jì)算的),每組分別對(duì)應(yīng)某個(gè)存儲(chǔ)區(qū)域,根據(jù)一個(gè)對(duì)象的哈希碼就可以確定該對(duì)象應(yīng)該存儲(chǔ)在哪個(gè)區(qū)域,大大減少查詢匹配元素的數(shù)量。

比如HashSet就是采用哈希算法存取對(duì)象的集合,它內(nèi)部采用對(duì)某個(gè)數(shù)字n進(jìn)行取余的方式對(duì)哈希碼進(jìn)行分組和劃分對(duì)象的存儲(chǔ)區(qū)域,當(dāng)從HashSet集合中查找某個(gè)對(duì)象時(shí),Java系統(tǒng)首先調(diào)用對(duì)象的hashCode()方法獲得該對(duì)象的哈希碼,然后根據(jù)哈希嗎找到相應(yīng)的存儲(chǔ)區(qū)域,最后取得該存儲(chǔ)區(qū)域內(nèi)的每個(gè)元素與該對(duì)象進(jìn)行equals()比較,這樣就不用遍歷集合中的所有元素就可以得到結(jié)論。

下面通過String類的hashCode()計(jì)算一組散列碼:

public class HashCodeTest {
	public static void main(String[] args) {
		int hash= 0;
		String s= "ok";
		StringBuilder sb = new StringBuilder(s);
		System.out.println(s.hashCode() + "  " + sb.hashCode());
		String t = new String("ok");
		StringBuilder tb =new StringBuilder(s);
		System.out.println(t.hashCode() + "  " + tb.hashCode());
	}
}

運(yùn)行結(jié)果:
3548  1829164700
3548  2018699554

我們可以看出,字符串s與t擁有相同的散列碼,這是因?yàn)樽址纳⒘写a是由內(nèi)容導(dǎo)出的。而字符串緩沖sb與tb卻有著不同的散列碼,這是因?yàn)镾tringBuilder沒有重寫hashCode()方法,它的散列碼是由Object類默認(rèn)的hashCode()計(jì)算出來的對(duì)象存儲(chǔ)地址,所以散列碼自然也就不同了。那么該如何重寫出一個(gè)較好的hashCode方法呢,其實(shí)并不難,我們只要合理地組織對(duì)象的散列碼,就能夠讓不同的對(duì)象產(chǎn)生比較均勻的散列碼。例如下面的例子:

public class Model {
	private String name;
	private double salary;
	private int sex;
	@Override
	public int hashCode() {
		return name.hashCode() + new Double(salary).hashCode() + new Integer(sex).hashCode();
	}
}

上面的代碼我們通過合理的利用各個(gè)屬性對(duì)象的散列碼進(jìn)行組合,最終便能產(chǎn)生一個(gè)相對(duì)比較好的或者說更加均勻的散列碼,當(dāng)然上面僅僅是個(gè)參考例子而已,我們也可以通過其他方式去實(shí)現(xiàn),只要能使散列碼更加均勻(所謂的均勻就是每個(gè)對(duì)象產(chǎn)生的散列碼最好都不沖突)就行了。不過這里有點(diǎn)要注意的就是java 7中對(duì)hashCode方法做了兩個(gè)改進(jìn),首先java發(fā)布者希望我們使用更加安全的調(diào)用方式來返回散列碼,也就是使用null安全的方法Objects.hashCode(注意不是Object而是java.util.Objects)方法,這個(gè)方法的優(yōu)點(diǎn)是如果參數(shù)為null,就只返回0,否則返回對(duì)象參數(shù)調(diào)用的hashCode的結(jié)果。Objects.hashCode 源碼如下:

public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }

因此我們修改后的代碼如下:

import java.util.Objects;
public  class Model {
	private   String name;
	private double salary;
	private int sex;
	@Override
	public int hashCode() {
		return Objects.hashCode(name) + new Double(salary).hashCode() + new Integer(sex).hashCode();
	}
}

java 7還提供了另外一個(gè)方法java.util.Objects.hash(Object… objects),當(dāng)我們需要組合多個(gè)散列值時(shí)可以調(diào)用該方法。進(jìn)一步簡(jiǎn)化上述的代碼:

import java.util.Objects;
public  class Model {
	private   String name;
	private double salary;
	private int sex;
	@Override
	public int hashCode() {
		return Objects.hash(name,salary,sex);
	}
}

好了,到此hashCode()該介紹的我們都說了,還有一點(diǎn)要說的,如果我們提供的是一個(gè)數(shù)組類型的變量的話,那么我們可以調(diào)用Arrays.hashCode()來計(jì)算它的散列碼,這個(gè)散列碼是由數(shù)組元素的散列碼組成的。

2、equals()與hashCode()的聯(lián)系

Java的超類Object類已經(jīng)定義了equals()和hashCode()方法,在Obeject類中,equals()比較的是兩個(gè)對(duì)象的內(nèi)存地址是否相等,而hashCode()返回的是對(duì)象的內(nèi)存地址。所以hashCode主要是用于查找使用的,而equals()是用于比較兩個(gè)對(duì)象是否相等的。但有時(shí)候我們根據(jù)特定的需求,可能要重寫這兩個(gè)方法,在重寫這兩個(gè)方法的時(shí)候,主要注意保持一下幾個(gè)特性:

(1)如果兩個(gè)對(duì)象的equals()結(jié)果為true,那么這兩個(gè)對(duì)象的hashCode一定相同;

(2)兩個(gè)對(duì)象的hashCode()結(jié)果相同,并不能代表兩個(gè)對(duì)象的equals()一定為true,只能夠說明這兩個(gè)對(duì)象在一個(gè)散列存儲(chǔ)結(jié)構(gòu)中。

(3)如果對(duì)象的equals()被重寫,那么對(duì)象的hashCode()也要重寫。

3、為什么重寫equals()的同時(shí)要重寫hashCode()方法

在將這個(gè)問題的答案之前,我們先了解一下將元素放入集合的流程,如下圖:

將對(duì)象放入到集合中時(shí),首先判斷要放入對(duì)象的hashcode值與集合中的任意一個(gè)元素的hashcode值是否相等,如果不相等直接將該對(duì)象放入集合中。如果hashcode值相等,然后再通過equals()判斷要放入對(duì)象與該存儲(chǔ)區(qū)域的任意一個(gè)對(duì)象是否相等,如果equals()判斷不相等,直接將該元素放入到集合中,否則不放入。

同樣,在使用get()查詢?cè)氐臅r(shí)候,集合類也先調(diào)key.hashCode()算出數(shù)組下標(biāo),然后看equals()的結(jié)果,如果是true就是找到了,否則就是沒找到。

假設(shè)我們我們重寫了對(duì)象的equals(),但是不重寫hashCode()方法,由于超類Object中的hashcode()方法始終返回的是一個(gè)對(duì)象的內(nèi)存地址,而不同對(duì)象的這個(gè)內(nèi)存地址永遠(yuǎn)是不相等的。這時(shí)候,即使我們重寫了equals()方法,也不會(huì)有特定的效果的,因?yàn)椴荒艽_保兩個(gè)equals()結(jié)果為true的兩個(gè)對(duì)象會(huì)被散列在同一個(gè)存儲(chǔ)區(qū)域,即 obj1.equals(obj2) 的結(jié)果為true,但是不能保證 obj1.hashCode() == obj2.hashCode() 表達(dá)式的結(jié)果也為true;這種情況,就會(huì)導(dǎo)致數(shù)據(jù)出現(xiàn)不唯一,因?yàn)槿绻BhashCode()都不相等的話,就不會(huì)調(diào)用equals方法進(jìn)行比較了,所以重寫equals()就沒有意義了。

以HashSet為例,如果一個(gè)類的hashCode()方法沒有遵循上述要求,那么當(dāng)這個(gè)類的兩個(gè)實(shí)例對(duì)象用equals()方法比較的結(jié)果相等時(shí),他們本來應(yīng)該無法被同時(shí)存儲(chǔ)進(jìn)set集合中,但是,如果將他們存儲(chǔ)進(jìn)HashSet集合中時(shí),由于他們的hashCode()方法的返回值不同(HashSet使用的是Object中的hashCode(),它返回值是對(duì)象的內(nèi)存地址),第二個(gè)對(duì)象首先按照哈希碼計(jì)算可能被放進(jìn)與第一個(gè)對(duì)象不同的區(qū)域中,這樣,它就不可能與第一個(gè)對(duì)象進(jìn)行equals方法比較了,也就可能被存儲(chǔ)進(jìn)HashSet集合中了;所以,Object類中的hashCode()方法不能滿足對(duì)象被存入到HashSet中的要求,因?yàn)樗姆祷刂凳峭ㄟ^對(duì)象的內(nèi)存地址推算出來的,同一個(gè)對(duì)象在程序運(yùn)行期間的任何時(shí)候返回的哈希值都是始終不變的,所以,只要是兩個(gè)不同的實(shí)例對(duì)象,即使他們的equals方法比較結(jié)果相等,他們默認(rèn)的hashCode方法的返回值是不同的。

接下來,我們就舉幾個(gè)小例子測(cè)試一下:

3.1、測(cè)試一

覆蓋equals()但不覆蓋hashCode(),導(dǎo)致數(shù)據(jù)不唯一性。

public class HashCodeTest {  
    public static void main(String[] args) {  
        Collection set = new HashSet();  
        Point p1 = new Point(1, 1);  
        Point p2 = new Point(1, 1);  
        System.out.println(p1.equals(p2));  
        set.add(p1);   //(1)  
        set.add(p2);   //(2)  
        set.add(p1);   //(3)  
        Iterator iterator = set.iterator();  
        while (iterator.hasNext()) {  
            Object object = iterator.next();  
            System.out.println(object);  
        }  
    }  
}  
class Point {  
    private int x;  
    private int y;  
    public Point(int x, int y) {  
        super();  
        this.x = x;  
        this.y = y;  
    }  
    @Override  
    public boolean equals(Object obj) {  
        if (this == obj)  
            return true;  
        if (obj == null)  
            return false;  
        if (getClass() != obj.getClass())  
            return false;  
        Point other = (Point) obj;  
        if (x != other.x)  
            return false;  
        if (y != other.y)  
            return false;  
        return true;  
    }  
    @Override  
    public String toString() {  
        return "x:" + x + ",y:" + y;  
    }  
}  

輸出結(jié)果:
true
x:1,y:1  
x:1,y:1 

原因分析:

  • 當(dāng)執(zhí)行set.add(p1)時(shí)(1),集合為空,直接存入集合;
  • 當(dāng)執(zhí)行set.add(p2)時(shí)(2),首先判斷該對(duì)象p2的hashCode值所在的存儲(chǔ)區(qū)域是否有相同的hashCode,因?yàn)闆]有覆蓋hashCode方法,所以默認(rèn)使用Object的hashCode方法,返回內(nèi)存地址轉(zhuǎn)換后的整數(shù),因?yàn)椴煌瑢?duì)象的地址值不同,所以這里不存在與p2相同hashCode值的對(duì)象,所以直接存入集合。
  • 當(dāng)執(zhí)行set.add(p1)時(shí)(3),時(shí),因?yàn)閜1已經(jīng)存入集合,同一對(duì)象返回的hashCode值是一樣的,繼續(xù)判斷equals是否返回true,因?yàn)槭峭粚?duì)象所以返回true。此時(shí)jdk認(rèn)為該對(duì)象已經(jīng)存在于集合中,所以舍棄。

3.2、測(cè)試二

覆蓋hashCode(),但不覆蓋equals(),仍然會(huì)導(dǎo)致數(shù)據(jù)的不唯一性。

修改Point類:

class Point {  
    private int x;  
    private int y;  
    public Point(int x, int y) {  
        super();  
        this.x = x;  
        this.y = y;  
    }  
    @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + x;  
        result = prime * result + y;  
        return result;  
    }  
    @Override  
    public String toString() {  
        return "x:" + x + ",y:" + y;  
    }  
}  

輸出結(jié)果:
false
x:1,y:1  
x:1,y:1 

原因分析:

  • 當(dāng)執(zhí)行set.add(p1)時(shí)(1),集合為空,直接存入集合;
  • 當(dāng)執(zhí)行set.add(p2)時(shí)(2),首先判斷該對(duì)象p2的hashCode值所在的存儲(chǔ)區(qū)域是否有相同的hashCode,這里覆蓋了hashCode方法,p1和p2的hashCode相等,所以繼續(xù)判斷equals()是否相等,因?yàn)檫@里沒有覆蓋equals(),默認(rèn)使用 “” 來判斷,而 “” 比較的是兩個(gè)對(duì)象的內(nèi)存地址,所以這里equals()會(huì)返回false,所以集合認(rèn)為是不同的對(duì)象,所以將p2存入集合。
  • 當(dāng)執(zhí)行set.add(p1)時(shí)(3),時(shí),因?yàn)閜1已經(jīng)存入集合,同一對(duì)象返回的hashCode值是一樣的,并且equals返回true。此時(shí)認(rèn)為該對(duì)象已經(jīng)存在于集合中,所以舍棄。

綜合上述兩個(gè)測(cè)試,要想保證元素的唯一性,必須同時(shí)覆蓋hashCode和equals才行。

(注意:在HashSet中插入同一個(gè)元素(hashCode和equals均相等)時(shí),新加入的元素會(huì)被舍棄,而在HashMap中插入同一個(gè)Key(Value 不同)時(shí),原來的元素會(huì)被覆蓋。)

4、由hashCode()造成的內(nèi)存泄露問題

public class RectObject {
	public int x;
	public int y;
	public RectObject(int x,int y){
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode(){
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj){
		if(this == obj)
			return true;
		if(obj == null)
			return false;
		if(getClass() != obj.getClass())
			return false;
		final RectObject other = (RectObject)obj;
		if(x != other.x){
			return false;
		}
		if(y != other.y){
			return false;
		}
		return true;
	}
}

我們重寫了父類Object中的hashCode和equals方法,看到hashCode和equals方法中,如果兩個(gè)RectObject對(duì)象的x,y值相等的話他們的hashCode值是相等的,同時(shí)equals返回的是true;

import java.util.HashSet;
public class Demo {
	public static void main(String[] args){
		HashSet<RectObject> set = new HashSet<RectObject>();
		RectObject r1 = new RectObject(3,3);
		RectObject r2 = new RectObject(5,5);
		RectObject r3 = new RectObject(3,3);
		set.add(r1);
		set.add(r2);
		set.add(r3);
		r3.y = 7;
		System.out.println("刪除前的大小size:"+set.size());//2
		set.remove(r3);
		System.out.println("刪除后的大小size:"+set.size());//2
	}
}

運(yùn)行結(jié)果:
刪除前的大小size:3
刪除后的大小size:3

在這里,我們發(fā)現(xiàn)了一個(gè)問題,當(dāng)我們調(diào)用了remove刪除r3對(duì)象,以為刪除了r3,但事實(shí)上并沒有刪除,這就叫做內(nèi)存泄露,就是不用的對(duì)象但是他還在內(nèi)存中。所以我們多次這樣操作之后,內(nèi)存就爆了??匆幌聄emove的源碼:

    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

然后再看一下map的remove方法的源碼:

    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

再看一下removeEntryForKey方法源碼:

/**
     * Removes and returns the entry associated with the specified key
     * in the HashMap.  Returns null if the HashMap contains no mapping
     * for this key.
     */
    final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
        return e;
    }

我們看到,在調(diào)用remove方法的時(shí)候,會(huì)先使用對(duì)象的hashCode值去找到這個(gè)對(duì)象,然后進(jìn)行刪除,這種問題就是因?yàn)槲覀冊(cè)谛薷牧?r3 對(duì)象的 y 屬性的值,又因?yàn)镽ectObject對(duì)象的hashCode()方法中有y值參與運(yùn)算,所以r3對(duì)象的hashCode就發(fā)生改變了,所以remove方法中并沒有找到 r3,所以刪除失敗。即 r3的hashCode變了,但是他存儲(chǔ)的位置沒有更新,仍然在原來的位置上,所以當(dāng)我們用他的新的hashCode去找肯定是找不到了.

上面的這個(gè)內(nèi)存泄露告訴我一個(gè)信息:如果我們將對(duì)象的屬性值參與了hashCode的運(yùn)算中,在進(jìn)行刪除的時(shí)候,就不能對(duì)其屬性值進(jìn)行修改,否則會(huì)導(dǎo)致內(nèi)存泄露問題。

5、基本數(shù)據(jù)類型和String類型的hashCode()方法和equals()方法

(1)hashCode():八種基本類型的hashCode()很簡(jiǎn)單就是直接返回他們的數(shù)值大小,String對(duì)象是通過一個(gè)復(fù)雜的計(jì)算方式,但是這種計(jì)算方式能夠保證,如果這個(gè)字符串的值相等的話,他們的hashCode就是相等的。

(2)equals():8種基本類型的equals方法就是直接比較數(shù)值,String類型的equals方法是比較字符串的值的。

到此這篇關(guān)于Java hashCode原理以及與equals()區(qū)別聯(lián)系詳解的文章就介紹到這了,更多相關(guān)Java hashCode內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論