首頁 > Java > java教程 > 主體

Java提高篇(二六)------hashCode

黄舟
發布: 2017-02-11 09:55:59
原創
1258 人瀏覽過


-------------------------------------------- -------------------------------------------------- -------------------------------------------------- ----------------------------

       在前面三篇文章中LZ解說了(HashMap、HashSet 、HashTable),在其中LZ不斷地講解他們的put和get方法,在這兩個方法中計算key的hashCode應該是最重要也是最精華的部分,所以下面LZ揭開hashCode的「神秘」面紗。

      hashCode的作用

         在講解數組時(java提高篇(十八)------數組),我們提到數組是java中效率最高的資料結構,但是「最高」是有前提的。第一我們需要知道所查詢資料的所在位置。第二:如果我們進行迭代查找時,資料量一定要小,對於大資料量而言一般推薦集合。

           在Java集合中有兩類,一類是List,一類是Set他們之間的區別在於List集合中的元素師有序的,並且可以重複,而SetSet中元素是無序不可重複的。對於List好處理,但對於Set而言我們要如何來保證元素不重複呢?透過迭代來equals()是否相等。資料量小還可以接受,當我們的資料量大的時候效率可想而知(當然我們可以利用演算法進行最佳化)。例如我們在HashSet插入1000數據,難道我們真的要迭代1000次,呼叫1000次equals()方法嗎? hashCode提供了解決方案。怎麼實現?我們先看hashCode的源碼(Object)。

public native int hashCode();
登入後複製

           它是一個本地方法,它的實現與本地機器有關,這裡我們暫且認為他認為自己是暫時和認為自己的當我們在一個集合中加入某個元素,集合會先呼叫hashCode方法,這樣就可以直接定位它所儲存的位置,若該處沒有其他元素,則直接儲存。若該處已經有元素存在,就呼叫equals方法來匹配這兩個元素是否相同,相同則不存,不同則散列到其他位置(具體情況請參考(Java提高篇()-----HashMap ))。這樣處理,當我們存入大量元素時就可以大幅減少呼叫equals()方法的次數,大大提高了效率。

           所以hashCode在上面扮演的角色為尋域(尋找某個物件在集合中區域位置)。 hashCode可以將集合分成若干個區域,每個物件都可以計算出他們的hash碼,可以將hash碼分組,每個分組對應著某個儲存區域,根據一個物件的hash碼就可以確定該物件所存儲區域,這樣就大大減少查詢匹配元素的數量,並提高了查詢效率。

      hashCode對於一個物件的重要性

   不重要,對List集合、陣列而言,他就是一個累贅,但是對於HashMap、HashSet、HashTable而言,它變得異常重要。所以在使用HashMap、HashSet、HashTable時一定要注意hashCode。對於一個物件而言,其hashCode過程就是一個簡單的Hash演算法的實現,其實現過程對你實現物件的存取過程起到非常重要的作用。      

     

在前面LZ提到了HashMap和HashTable兩種資料結構,雖然他們存在若干個區別,但是他們的實作原理是相同的,這裡我以HashTable兩種資料結構,雖然他們存在若干個區別,但是他們的實作原則是相同的,這裡我以HashTableCode為說明一個物件的重要性。

一个对象势必会存在若干个属性,如何选择属性来进行散列考验着一个人的设计能力。如果我们将所有属性进行散列,这必定会是一个糟糕的设计,因为对象的hashCode方法无时无刻不是在被调用,如果太多的属性参与散列,那么需要的操作数时间将会大大增加,这将严重影响程序的性能。但是如果较少属相参与散列,散列的多样性会削弱,会产生大量的散列“冲突”,除了不能够很好的利用空间外,在某种程度也会影响对象的查询效率。其实这两者是一个矛盾体,散列的多样性会带来性能的降低。

那么如何对对象的hashCode进行设计,LZ也没有经验。从网上查到了这样一种解决方案:设置一个缓存标识来缓存当前的散列码,只有当参与散列的对象改变时才会重新计算,否则调用缓存的hashCode,这样就可以从很大程度上提高性能。

在HashTable计算某个对象在table[]数组中的索引位置,其代码如下:

int index = (hash & 0x7FFFFFFF) % tab.length;
登入後複製

为什么要&0x7FFFFFFF?因为某些对象的hashCode可能会为负值,与0x7FFFFFFF进行与运算可以确保index为一个正数。通过这步我可以直接定位某个对象的位置,所以从理论上来说我们是完全可以利用hashCode直接定位对象的散列表中的位置,但是为什么会存在一个key-value的键值对,利用key的hashCode来存入数据而不是直接存放value呢?这就关系HashTable性能问题的最重要的问题:Hash冲突!

我们知道冲突的产生是由于不同的对象产生了相同的散列码,假如我们设计对象的散列码可以确保99.999999999%的不重复,但是有一种绝对且几乎不可能遇到的冲突你是绝对避免不了的。我们知道hashcode返回的是int,它的值只可能在int范围内。如果我们存放的数据超过了int的范围呢?这样就必定会产生两个相同的index,这时在index位置处会存储两个对象,我们就可以利用key本身来进行判断。所以具有相索引的对象,在该index位置处存在多个对象,我们必须依靠key的hashCode和key本身来进行区分。

hashCode与equals

在Java中hashCode的实现总是伴随着equals,他们是紧密配合的,你要是自己设计了其中一个,就要设计另外一个。当然在多数情况下,这两个方法是不用我们考虑的,直接使用默认方法就可以帮助我们解决很多问题。但是在有些情况,我们必须要自己动手来实现它,才能确保程序更好的运作。

对于equals,我们必须遵循如下规则:

对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

反射性:x.equals(x)必须返回是“true”。

类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

对于hashCode,我们应该遵循如下规则:

1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。

2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。

3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

至于两者之间的关联关系,我们只需要记住如下即可:

如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。

如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

理清了上面的关系我们就知道他们两者是如何配合起来工作的。先看下图:



整个处理流程是:

1、判断两个对象的hashcode是否相等,若不等,则认为两个对象不等,完毕,若相等,则比较equals。

2、若两个对象的equals不等,则可以认为两个对象不等,否则认为他们相等。

实例:

public class Person {
    private int age;
    private int sex;    //0:男,1:女
    private String name;
    
    private final int PRIME = 37;
    
    Person(int age ,int sex ,String name){
        this.age = age;
        this.sex = sex;
        this.name = name;
    }

    /** 省略getter、setter方法 **/
    
    @Override
    public int hashCode() {
        System.out.println("调用hashCode方法...........");

        int hashResult = 1;
        hashResult = (hashResult + Integer.valueOf(age).hashCode() + Integer.valueOf(sex).hashCode()) * PRIME;
        hashResult = PRIME * hashResult + ((name == null) ? 0 : name.hashCode()); 
        System.out.println("name:"+name +" hashCode:" + hashResult);
        
        return hashResult;
    }

    /**
     * 重写hashCode()
     */
    public boolean equals(Object obj) {
        System.out.println("调用equals方法...........");
        
        if(obj == null){
            return false;
        }
        if(obj.getClass() != this.getClass()){
            return false;
        }
        if(this == obj){
            return true;
        }

        Person person = (Person) obj;
        
        if(getAge() != person.getAge() || getSex()!= person.getSex()){
            return false;
        }
        
        if(getName() != null){
            if(!getName().equals(person.getName())){
                return false;
            }
        }
        else if(person != null){
            return false;
        }
        return true;
    }
}
登入後複製

该Bean为一个标准的Java Bean,重新实现了hashCode方法和equals方法。

public class Main extends JPanel {

    public static void main(String[] args) {
        Set<Person> set = new HashSet<Person>();
        
        Person p1 = new Person(11, 1, "张三");
        Person p2 = new Person(12, 1, "李四");
        Person p3 = new Person(11, 1, "张三");
        Person p4 = new Person(11, 1, "李四");
        
        //只验证p1、p3
        System.out.println("p1 == p3? :" + (p1 == p3));
        System.out.println("p1.equals(p3)?:"+p1.equals(p3));
        System.out.println("-----------------------分割线--------------------------");
        set.add(p1);
        set.add(p2);
        set.add(p3);
        set.add(p4);
        System.out.println("set.size()="+set.size());
    }
}
登入後複製

            运行结果如下:



           从上图可以看出,程序调用四次hashCode方法,一次equals方法,其set的长度只有3。add方法运行流程完全符合他们两者之间的处理流程。

以上就是Java提高篇(二六)------hashCode 的内容,更多相关内容请关注PHP中文网(www.php.cn)!


來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板