>  기사  >  Java  >  자바 개선 26장------hashCode

자바 개선 26장------hashCode

黄舟
黄舟원래의
2017-02-11 09:55:591225검색


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

이전 3개의 블로그 포스팅에서 LZ가 설명(HashMap, HashSet, HashTable)을 설명했고, 여기서 LZ는 계속해서 자신의 Put에 대한 설명을 이어갔습니다. 및 get 메서드를 사용하여 키의 hashCode를 계산하는 것이 이 두 메서드 중에서 가장 중요하고 필수적인 부분이므로 LZ 아래에서는 hashCode의 "미스터리"를 공개합니다.

hashCode의 역할

메소드의 내부 원리를 이해하려면, 먼저 이 메서드가 수행하는 작업이 무엇인지 이해해야 합니다. 배열(Java Improvement Chapter(18)------배열)을 설명할 때 배열은 Java에서 가장 효율적인 데이터 구조이지만 "최고"에는 전제 조건이 있다고 언급했습니다. 먼저 쿼리된 데이터의 위치를 ​​알아야 합니다. 둘째: 반복 검색을 수행하는 경우 데이터의 양이 적어야 합니다. 데이터의 양이 많은 경우 일반적으로 컬렉션을 권장합니다.

Java에는 두 가지 유형의 컬렉션이 있습니다. 하나는 List이고 다른 하나는 Set입니다. 차이점은 List 컬렉션의 요소가 정렬된다는 것입니다. . 및 반복될 수 있지만 Set 컬렉션의 요소는 순서가 없고 반복할 수 없습니다. List는 다루기 쉽지만 Set의 경우 요소가 반복되지 않도록 하려면 어떻게 해야 합니까? 반복을 통해 equals()가 같은지 확인합니다. 데이터의 양이 적을 때는 허용되지만, 데이터의 양이 많을 때 효율성을 상상할 수 있습니다(물론 알고리즘을 사용하여 최적화할 수 있습니다). 예를 들어, HashSet에 1000개의 데이터를 삽입하면 실제로 1000번 반복하고 equals() 메서드를 1000번 호출해야 합니까? hashCode가 솔루션을 제공합니다. 그것을 달성하는 방법? 먼저 hashCode의 소스코드(Object)를 살펴보겠습니다.

안돼요

                                               반환되는 것은 객체가 저장된 물리적 위치입니다(실제로는 이해하기 쉽도록 여기에 기록되어 있습니다). . 컬렉션에 요소를 추가하면 컬렉션이 먼저 hashCode 메서드를 호출하므로 해당 요소가 저장된 위치를 직접 찾을 수 있습니다. 거기에 다른 요소가 없으면 직접 저장됩니다. 이미 요소가 있는 경우에는 두 요소가 동일한지 확인하기 위해 equals 메소드를 호출하고, 서로 다른 경우에는 다른 위치로 해시됩니다. (Java 개선) ()-----HashMap ))을 참조하십시오. 이런 방식으로 많은 수의 요소를 저장할 때 equals() 메서드에 대한 호출 횟수를 크게 줄여 효율성을 크게 향상시킬 수 있습니다.

그래서 hashCode는 도메인 검색 역할을 합니다(컬렉션에서 개체의 위치 찾기) ) . HashCode는 컬렉션을 여러 영역으로 나눌 수 있으며, 각 개체는 해시 코드를 그룹화할 수 있으며, 각 그룹은 개체의 해시 코드에 따라 저장되는 영역을 결정할 수 있습니다. , 따라서 쿼리 일치 요소의 수를 크게 줄이고 쿼리 효율성을 향상시킵니다.

객체에 대한 hashCode의 중요성

hashCode 중요합니까? 중요하지 않습니다. List 컬렉션 및 배열의 ​​경우 부담이지만 HashMap, HashSet 및 HashTable의 경우 매우 중요합니다. 그러므로 HashMap, HashSet, HashTable을 사용할 때에는 hashCode에 주의해야 한다. 객체의 경우 hashCode 프로세스는 간단한 Hash 알고리즘의 구현이며 구현 프로세스는 객체의 액세스 프로세스를 구현하는 데 매우 중요한 역할을 합니다.

LZ는 HashMap과 HashTable이라는 두 가지 데이터 구조에 대해 언급했지만 몇 가지 차이점이 있지만 구현 원칙은 동일합니다. 여기서는 설명을 위해 HashTable을 사용합니다. 객체에 대한 hashCode의 중요성.

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


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.