Heim  >  Artikel  >  Java  >  Java-Verbesserung Kapitel 26------hashCode

Java-Verbesserung Kapitel 26------hashCode

黄舟
黄舟Original
2017-02-11 09:55:591226Durchsuche


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

In den vorherigen drei Blogbeiträgen erklärte LZ (HashMap, HashSet, HashTable), in denen LZ weiterhin ihren Put erläuterte Unter diesen beiden Methoden sollte die Berechnung des HashCodes des Schlüssels der wichtigste und wesentlichste Teil sein. Daher enthüllt LZ im Folgenden das „Geheimnis“ des HashCodes.

Die Rolle von HashCode

Um die inneren Prinzipien einer Methode zu verstehen, müssen wir Zuerst müssen Sie verstehen, was es tut, das heißt, was diese Methode bewirkt. Bei der Erläuterung von Arrays (Kapitel zur Java-Verbesserung (18) ------Arrays) haben wir erwähnt, dass Arrays die effizienteste Datenstruktur in Java sind, die „höchste“ jedoch eine Voraussetzung ist. Zuerst müssen wir den Speicherort der abgefragten Daten kennen. Zweitens: Wenn wir eine iterative Suche durchführen, muss die Datenmenge klein sein, und Sammlungen werden im Allgemeinen für große Datenmengen empfohlen.

Es gibt zwei Arten von Sammlungen in Java, eine ist List und die andere ist Set. Der Unterschied zwischen ihnen besteht darin, dass die Elemente in der List-Sammlung geordnet sind . und kann wiederholt werden, während die Elemente in der Set-Sammlung ungeordnet und nicht wiederholbar sind. List ist einfach zu handhaben, aber wie stellen wir für Set sicher, dass die Elemente nicht wiederholt werden? Überprüfen Sie durch Iteration, ob equal() gleich ist. Es ist akzeptabel, wenn die Datenmenge klein ist, aber die Effizienz kann man sich vorstellen, wenn die Datenmenge groß ist (natürlich können wir Algorithmen zur Optimierung verwenden). Wenn wir beispielsweise 1000 Daten in HashSet einfügen, müssen wir dann wirklich 1000 Mal iterieren und die Methode equal() 1000 Mal aufrufen? hashCode bietet die Lösung. Wie erreicht man es? Schauen wir uns zunächst den Quellcode (Objekt) von hashCode an.

public native int hashCode();

Es handelt sich um eine lokale Methode, deren Implementierung sich auf die lokale Maschine bezieht Wir belassen es vorerst. Es wird angenommen, dass das, was er zurückgibt, der physische Standort des Objektspeichers ist (eigentlich nicht, es wird hier zum leichteren Verständnis geschrieben). Wenn wir einer Sammlung ein Element hinzufügen, ruft die Sammlung zunächst die Methode hashCode auf, sodass der Speicherort direkt ermittelt werden kann. Wenn dort keine anderen Elemente vorhanden sind, wird es direkt gespeichert. Wenn dort bereits ein Element vorhanden ist, rufen Sie die Methode equal auf, um festzustellen, ob die beiden Elemente identisch sind. Wenn sie identisch sind, werden sie nicht gespeichert. Wenn sie unterschiedlich sind, werden sie an andere Orte gehasht. Weitere Informationen finden Sie unter (Java-Verbesserung) ()-----HashMap)). Auf diese Weise können wir beim Speichern einer großen Anzahl von Elementen die Anzahl der Aufrufe der Methode equal() erheblich reduzieren und so die Effizienz erheblich verbessern.

Also hashCode spielt die Rolle der Domänensuche (Finden des Standorts eines Objekts in der Sammlung) ) . HashCode kann die Sammlung in mehrere Bereiche unterteilen. Die Hash-Codes können anhand des Hash-Codes eines Objekts bestimmt werden , wodurch die Anzahl der Abfrage-Matching-Elemente erheblich reduziert und die Abfrageeffizienz verbessert wird.

Die Bedeutung von HashCode für ein Objekt

HashCode Ist es wichtig? Nicht wichtig, für Listensammlungen und Arrays ist es eine Belastung, aber für HashMap, HashSet und HashTable wird es äußerst wichtig. Daher müssen Sie bei der Verwendung von HashMap, HashSet und HashTable auf hashCode achten. Für ein Objekt ist sein HashCode-Prozess die Implementierung eines einfachen Hash-Algorithmus, und sein Implementierungsprozess spielt eine sehr wichtige Rolle bei der Realisierung des Zugriffsprozesses des Objekts.

LZ erwähnte zwei Datenstrukturen, HashMap und HashTable. Obwohl sie mehrere Unterschiede aufweisen, sind ihre Implementierungsprinzipien die gleichen. Hier verwende ich ein Beispiel zur Veranschaulichung die Bedeutung von HashCode für ein Objekt.

           一个对象势必会存在若干个属性,如何选择属性来进行散列考验着一个人的设计能力。如果我们将所有属性进行散列,这必定会是一个糟糕的设计,因为对象的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)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn