자바에서hashcode 방법에 대한 간단한 설명(추천)

11555 단어 javahashcode
해시표라는 데이터 구조는 대다수 사람들이 낯설지 않을 뿐만 아니라, 많은 곳에서hash표를 이용하여 검색 효율을 높일 것이다.Java의 Object 클래스에서 다음 방법이 있습니다.

public native int hashCode(); 
이 방법의 성명에 의하면 이 방법은 int 형식의 수치를 되돌려주고 로컬 방법이기 때문에 Object 클래스에서 구체적인 실현을 제시하지 못했다.
왜 Object 클래스는 이런 방법을 필요로 합니까?그것은 어떤 작용을 합니까?오늘은 해시코드 방법을 구체적으로 살펴보겠습니다.
하나.hashCode 방법의 역할
용기 유형을 포함하는 프로그램 설계 언어는 기본적으로hashCode와 관련된다.자바에서도 마찬가지로hashCode 방법의 주요 역할은 산열 기반의 집합과 함께 정상적으로 운행하기 위한 것이다. 이런 산열 집합은 HashSet,HashMap과 HashTable을 포함한다.
왜 그랬을까?집합에 대상을 삽입할 때 집합에 이미 존재하는지 아닌지를 어떻게 판별합니까?(참고: 컬렉션에서 중복되는 요소는 허용되지 않음)
아마도 대다수 사람들은 equals 방법을 호출하여 하나하나 비교할 것을 생각할 것이다. 이 방법은 확실히 실행할 수 있다.그러나 집합에 이미 만 개의 데이터나 더 많은 데이터가 존재한다면 equals 방법으로 하나하나 비교하면 효율은 반드시 문제이다.이때hashCode 방법의 역할은 다음과 같다. 집합이 새로운 대상을 추가할 때 이 대상의hashCode 방법을 먼저 호출하여 대응하는hashcode 값을 얻는다. 실제적으로hashMap의 구체적인 실현에서 이미 저장된 대상의hashcode 값을 하나의 테이블로 저장한다. 만약table에 이hashcode 값이 없다면 직접 저장할 수 있고 더 이상 비교할 필요가 없다.만약에 이hashcode 값이 존재한다면 그 equals 방법을 새로운 요소와 비교하고 같으면 저장하지 않고 다르면 다른 주소를 산열하기 때문에 여기에 충돌로 해결된 문제가 존재한다. 그러면 실제 equals 방법을 호출하는 횟수가 크게 줄어든다.통속적으로 말하자면 자바의hashCode 방법은 일정한 규칙에 따라 대상과 관련된 정보(예를 들어 대상의 저장 주소, 대상의 필드 등)를 하나의 수치로 비추는 것이다. 이 수치를 산열 값이라고 한다.아래 코드는java입니다.util.HashMap의 중put 방법의 구체적인 실현:

public V put(K key, V value) {

    if (key == null)

      return putForNullKey(value);

    int hash = hash(key.hashCode());

    int i = indexFor(hash, table.length);

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {

      Object k;

      if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

        V oldValue = e.value;

        e.value = value;

        e.recordAccess(this);

        return oldValue;

      }

    }

    modCount++;

    addEntry(hash, key, value, i);

    return null;

  } 
put 방법은 HashMap에 새로운 요소를 추가하는 데 사용됩니다.put 방법의 구체적인 실현을 통해 알 수 있듯이hashCode 방법을 호출하여 이 요소의hashCode 값을 얻은 다음table에 이hashCode 값이 존재하는지 확인하고,equals 방법을 호출하여 이 요소가 존재하는지 확인하고,존재하면value 값을 업데이트합니다. 그렇지 않으면 새로운 요소를 HashMap에 추가합니다.여기서 알 수 있듯이hashCode 방법의 존재는 equals 방법의 호출 횟수를 줄이고 프로그램 효율을 높이기 위한 것이다.
어떤 친구들은 기본적으로 hashCode가 되돌아오는 것이 대상의 저장 주소라고 착각한다. 사실상 이런 견해는 전면적이지 않다. 확실히 어떤 JVM은 실현될 때 대상의 저장 주소를 직접 되돌려주지만 대부분은 그렇지 않다. 저장 주소가 일정한 관련이 있다고 말할 수 밖에 없다.다음은 HotSpot JVM에서 hash 산열 값을 생성하는 방법입니다.

static inline intptr_t get_next_hash(Thread * Self, oop obj) {

 intptr_t value = 0 ;

 if (hashCode == 0) {

   // This form uses an unguarded global Park-Miller RNG,

   // so it's possible for two threads to race and generate the same RNG.

   // On MP system we'll have lots of RW access to a global, so the

   // mechanism induces lots of coherency traffic.

   value = os::random() ;

 } else

 if (hashCode == 1) {

   // This variation has the property of being stable (idempotent)

   // between STW operations. This can be useful in some of the 1-0

   // synchronization schemes.

   intptr_t addrBits = intptr_t(obj) >> 3 ;

   value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;

 } else

 if (hashCode == 2) {

   value = 1 ;      // for sensitivity testing

 } else

 if (hashCode == 3) {

   value = ++GVars.hcSequence ;

 } else

 if (hashCode == 4) {

   value = intptr_t(obj) ;

 } else {

   // Marsaglia's xor-shift scheme with thread-specific state

   // This is probably the best overall implementation -- we'll

   // likely make this the default in future releases.

   unsigned t = Self->_hashStateX ;

   t ^= (t << 11) ;

   Self->_hashStateX = Self->_hashStateY ;

   Self->_hashStateY = Self->_hashStateZ ;

   Self->_hashStateZ = Self->_hashStateW ;

   unsigned v = Self->_hashStateW ;

   v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;

   Self->_hashStateW = v ;

   value = v ;

 }

 

 value &= markOopDesc::hash_mask;

 if (value == 0) value = 0xBAD ;

 assert (value != markOopDesc::no_hash, "invariant") ;

 TEVENT (hashCode: GENERATE) ;

 return value;

} 

이 구현은 hotspot/src/share/vm/runtime/synchronizer에 있습니다.cpp 파일 아래.
그래서 어떤 사람들은 직접hashcode값에 따라 두 대상이 같은지 아닌지를 판단할 수 있다고 말할까?틀림없이 안 될 것이다. 왜냐하면 서로 다른 대상이 같은hashcode 값을 생성할 수 있기 때문이다.해시코드 값에 따라 두 대상이 같은지 아닌지를 판단할 수는 없지만 해시코드 값에 따라 두 대상이 같지 않다고 직접 판단할 수 있다. 만약 두 대상의 해시코드 값이 같지 않으면 반드시 두 개의 다른 대상이다.만약 두 대상이 진정으로 같은지 아닌지를 판단하려면 반드시 equals 방법을 통과해야 한다.
즉, 두 대상에 대해 equals 방법을 호출하여 얻은 결과가true라면 두 대상의hashcode값은 반드시 같다.
만약에 equals 방법이 얻은 결과가false라면 두 대상의hashcode값이 반드시 다르지 않다.
만약 두 대상의hashcode 값이 같지 않으면equals 방법이 얻은 결과는 반드시false이다.
만약 두 대상의hashcode 값이 같다면, equals 방법이 얻은 결과는 알 수 없습니다.
2.equals 방법과hashCode 방법
어떤 경우 프로그래머는 하나의 클래스를 설계할 때 equals 방법, 예를 들어 String 클래스를 다시 써야 하지만 equals 방법을 다시 쓰는 동시에 hashCode 방법을 다시 써야 한다는 것을 주의해야 한다.왜 그랬을까?
다음 예제를 살펴보겠습니다.

package com.cxh.test1;

import java.util.HashMap;

import java.util.HashSet;

import java.util.Set;

class People{

  private String name;

  private int age;  

  public People(String name,int age) {

    this.name = name;

    this.age = age;

  }   

  public void setAge(int age){

    this.age = age;

  }    

  @Override

  public boolean equals(Object obj) {

    // TODO Auto-generated method stub

    return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;

  }

}

public class Main {

  public static void main(String[] args) {  

    People p1 = new People("Jack", 12);

    System.out.println(p1.hashCode());    

    HashMap<People, Integer> hashMap = new HashMap<People, Integer>();

    hashMap.put(p1, 1);

    System.out.println(hashMap.get(new People("Jack", 12)));

  }

} 
 
여기서 나는 equals 방법만 다시 썼다. 즉, 두 People 대상이 이름과 나이가 같으면 같은 사람이라고 생각했다.
이 코드는 원래 이 코드의 출력 결과가'1'이라고 생각했지만, 사실상 출력된 것은'null'이다.왜죠?이유는 equals 방법을 다시 쓰는 동시에hashCode 방법을 다시 쓰는 것을 잊어버리는 데 있다.
equals 다시 쓰기 방법을 통해 논리적으로 이름과 나이가 같은 두 대상을 같은 대상으로 판정하지만 기본적으로 해시코드 방법은 대상의 저장 주소를 비추는 것이다.그러면 상술한 코드의 출력 결과가'null'인 것은 이상할 것이 없다.이유는 간단하다. p1이 가리키는 대상과
System.out.println(hashMap.get(new People("Jack", 12)));이 문장의 new People("Jack", 12)은 두 개의 대상을 생성하는데 그들의 저장 주소는 틀림없이 다르다.다음은 HashMap의 get 방법의 구체적인 실현입니다.

 public V get(Object key) {

    if (key == null)

      return getForNullKey();

    int hash = hash(key.hashCode());

    for (Entry<K,V> e = table[indexFor(hash, table.length)];

       e != null;

       e = e.next) {

      Object k;

      if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

        return e.value;

    }

    return null;

  } 

따라서hashmap에서 get 작업을 할 때 얻은hashcdoe 값이 다르기 때문에 (상술한 코드는 어떤 상황에서 같은hashcode 값을 얻을 수 있지만 이런 확률은 비교적 적다. 두 대상의 저장 주소가 다르더라도 같은hashcode 값을 얻을 수 있기 때문에) get 방법에서 for 순환이 실행되지 않고 null로 되돌아오기 때문이다.
따라서 상기 코드 출력 결과가'1'이라면 간단하다. 해시코드 방법을 다시 써서 equals 방법과 해시코드 방법이 논리적으로 일치성을 유지하도록 해야 한다.

package com.cxh.test1;

import java.util.HashMap;

import java.util.HashSet;

import java.util.Set; 

class People{

  private String name;

  private int age;

  public People(String name,int age) {

    this.name = name;

    this.age = age;

  }  

 public void setAge(int age){

    this.age = age;

  }  

  @Override

  public int hashCode() {

    // TODO Auto-generated method stub

    return name.hashCode()*37+age;

  } 

  @Override

  public boolean equals(Object obj) {

    // TODO Auto-generated method stub

    return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;

  }

}

public class Main {

  public static void main(String[] args) {   

    People p1 = new People("Jack", 12);

    System.out.println(p1.hashCode());   

    HashMap<People, Integer> hashMap = new HashMap<People, Integer>();

    hashMap.put(p1, 1);  

    System.out.println(hashMap.get(new People("Jack", 12)));

  }

} 
이렇게 되면 출력 결과가'1'이 됩니다.
다음은 Effective Java 에서 발췌한 것입니다.
프로그램이 실행되는 동안 equals 방법의 비교 조작에 사용된 정보가 수정되지 않으면 이 같은 대상을 여러 번 호출하고hashCode 방법은 시종일관 같은 정수를 되돌려야 한다.
만약 두 대상이 equals 방법에 따라 비교가 같다면, 두 대상을 호출하는hashCode 방법은 반드시 같은 정수 결과를 되돌려야 한다.
만약 두 대상이 equals 방법에 따라 비교가 같지 않다면hashCode 방법은 반드시 다른 정수를 되돌려야 하는 것은 아니다.
제2조와 제3조는 이해하기 쉽지만 제1조는 소홀히 하는 경우가 많다.Java 프로그래밍 마인드의 P495페이지에도 다음과 같은 첫 번째 구절이 있습니다.
"hashCode () 를 설계할 때 가장 중요한 요소는 다음과 같습니다. 언제든지 같은 대상에 hashCode () 를 호출할 때 같은 값이 발생해야 합니다. 대상을put () 로 HashMap에 추가할 때 hashCdoe 값이 발생하고 get () 로꺼낼 때 다른hashCode 값이 생겼습니다. 그러면 이 대상을 얻을 수 없습니다.따라서 만약hashCode 방법이 대상의 변하기 쉬운 데이터에 의존한다면 사용자는 조심해야 한다. 왜냐하면 이 데이터가 변화할 때hashCode () 방법은 서로 다른 산열 코드를 생성하기 때문이다.
다음 예:

package com.cxh.test1; 

import java.util.HashMap;

import java.util.HashSet;

import java.util.Set;

class People{

  private String name;

  private int age; 

  public People(String name,int age) {

    this.name = name;

    this.age = age;

  }  

  public void setAge(int age){

    this.age = age;

  } 

  @Override

  public int hashCode() {

    // TODO Auto-generated method stub

    return name.hashCode()*37+age;

  }  

  @Override

  public boolean equals(Object obj) {

    // TODO Auto-generated method stub

    return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;

  }

}

public class Main {

  public static void main(String[] args) {  

    People p1 = new People("Jack", 12);

    System.out.println(p1.hashCode()); 

    HashMap<People, Integer> hashMap = new HashMap<People, Integer>();

    hashMap.put(p1, 1);

    p1.setAge(13);  

    System.out.println(hashMap.get(p1));

  }

} 
이 코드가 출력된 결과는'null'이니 그 이유를 모두가 잘 알고 있을 것이다.
따라서hashCode방법과equals방법을 설계할 때 대상의 데이터가 변하기 쉬우면equals방법과hashCode방법에서 이 필드에 의존하지 않는 것이 좋다.
이상은 본문의 전체 내용입니다. 여러분의 학습에 도움이 되고 저희를 많이 응원해 주십시오.

좋은 웹페이지 즐겨찾기