나는 대부분의 사람들이 해시 테이블 데이터 구조에 익숙하다고 생각하며, 많은 곳에서 해시 테이블은 검색 효율을 향상시키는 데 사용됩니다. Java의 객체 클래스에는 방법이 있습니다.
공개 기본 int hashcode ();
이 방법의 선언에 따르면,이 메소드는 유형 int의 숫자 값을 반환하고 로컬 메소드라는 것을 알 수 있으므로 객체 클래스에 구체적인 구현이 제공되지 않습니다.
객체 클래스에 그러한 방법이 필요한 이유는 무엇입니까? 그 기능은 무엇입니까? 오늘 우리는 해시 코드 방법에 대해 자세히 논의 할 것입니다.
1. 해시 코드 방법의 기능
컨테이너 유형을 포함하는 프로그래밍 언어의 경우 해시 코드가 기본적으로 관련됩니다. Java에서도 마찬가지입니다. 해시 코드 방법의 주요 기능은 해시 기반 세트에서 정상적으로 작동하는 것입니다. 해시 세트에는 해시 세트, 해시 맵 및 해시 가능이 포함됩니다.
왜 그렇게 말합니까? 객체를 컬렉션에 삽입 할 때 컬렉션에 이미 존재하는지 여부를 결정하는 방법을 고려하십시오. (참고 : 컬렉션에서 중복 요소가 허용되지 않습니다)
아마도 대부분의 사람들은 평등 방법을 하나씩 비교하기 위해 Equals 방법을 호출한다고 생각할 것입니다.이 방법은 실제로 실현 가능합니다. 그러나 이미 세트에 천만 조각 이상의 데이터가있는 경우, Equals 메소드가 하나씩 비교하는 데 사용되면 효율성이 필연적으로 문제가됩니다. 현재 해시 코드 방법의 기능이 반영됩니다. 새 개체를 컬렉션에 추가 할 때, 해당 해시 코드 값을 얻기 위해 객체의 해시 코드 메소드가 먼저 호출됩니다. 실제로, 해시 맵의 특정 구현에서, 테이블은 저장된 객체의 해시 코드 값을 저장하는 데 사용됩니다. 테이블에 해시 코드 값이없는 경우 비교없이 직접 저장할 수 있습니다. 해시 코드 값이 존재하는 경우, 동등한 방법은 새 요소와 비교하도록 호출됩니다. 마찬가지 인 경우 다른 주소는 저장되지 않습니다. 따라서 여기에는 갈등 해결 문제가 있습니다. 이런 식으로, 평등 방법이 실제로 호출되는 횟수는 크게 줄어 듭니다. 간단히 말해서 Java의 해시 코드 메소드는 객체와 관련된 정보 (예 : 객체의 저장 주소, 객체 필드 등)와 특정 규칙에 따라 숫자 값에 맵핑 되며이 값을 해시 값이라고합니다. 다음 코드는 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 = 테이블 [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 = 값; e.recordaccess (this); OldValue를 반환하십시오. }} modcount ++; Addentry (해시, 키, 값, i); 널 리턴; } PUT 방법은 해시 맵에 새 요소를 추가하는 데 사용됩니다. PUT 방법의 특정 구현에서, 우리는 해시 코드 방법이 먼저 호출되어 요소의 해시 코드 값을 얻은 다음 테이블에 해시 코드 값이 존재하는지 확인할 수 있습니다. 그것이 존재하는 경우, Equals 메소드를 호출하여 요소가 존재하는지 여부를 다시 결정하십시오. 존재하는 경우 값 값을 업데이트하고 그렇지 않으면 새 요소를 해시 맵에 추가하십시오. 여기에서 해시 코드 메소드가 존재한다는 것을 알 수 있습니다.
일부 친구는 실수로 기본적으로 해시 코드가 객체의 스토리지 주소를 반환한다고 생각합니다. 실제로이 견해는 불완전합니다. 일부 JVM이 구현시 객체의 스토리지 주소를 직접 반환하는 것은 사실이지만 대부분의 경우에는 그렇지 않습니다. 스토리지 주소가 관련 될 수 있다고 만 말할 수 있습니다. 다음은 핫스팟 JVM에서 해시 해시 값을 생성하는 것입니다.
정적 인라인 intptr_t get_next_hash (스레드 * self, oop obj) {intptr_t value = 0; if (hashcode == 0) {//이 양식은 지폐가없는 Global Park-Miller RNG를 사용하므로 두 개의 스레드가 경주하여 동일한 RNG를 생성 할 수 있습니다. // MP 시스템에서는 전역에 많은 RW 액세스가 있으므로 // 메커니즘은 많은 일관성 트래픽을 유도합니다. 값 = os :: random (); } else if (hashcode == 1) {//이 변형은 stw 연산간에 안정적인 (idempotent) //의 속성을 가지고 있습니다. 이것은 1-0 // 동기화 체계 중 일부에 유용 할 수 있습니다. intptr_t addrbits = intptr_t (obj) >> 3; value = addrbits ^ (addrbits >> 5) ^ gvars.stwrandom; } else if (hashcode == 2) {value = 1; // 감도 테스트의 경우} else if (hashcode == 3) {value = ++ gvars.hcestecence; } else if (hashcode == 4) {value = intptr_t (obj); } else {// 스레드 별 상태를 가진 Marsaglia의 XOR-Shift 체계 // 아마도 가장 좋은 전체 구현 일 것입니다. // 우리는 이것을 향후 릴리스에서 기본값으로 만들 것입니다. 서명되지 않은 t = self-> _ hashstatex; t ^= (t << 11); self-> _ hashstatex = self-> _ Hashstatey; self-> _ Hashstatey = self-> _ Hashstatez; self-> _ Hashstatez = self-> _ Hashstatew; 부호없는 v = self-> _ hashstatew; v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)); self-> _ hashstatew = v; 값 = V; } value & = markoopdesc :: hash_mask; if (value == 0) value = 0xbad; assert (value! = markoopdesc :: no_hash, "불변"); Tevent (Hashcode : Generate); 반환 값;}이 구현은 핫스팟/src/share/vm/runtime/synchronizer.cpp 파일에 있습니다.
따라서 어떤 사람들은 두 객체가 해시 코드 값에 따라 동일인지 직접 판단 할 수 있다고 말할 수 있습니까? 다른 객체가 동일한 해시 코드 값을 생성 할 수 있기 때문에 확실히 불가능합니다. 해시 코드 값에 따라 두 객체가 동일인지 판단 할 수는 없지만 두 객체가 해시 코드 값에 따라 동일하지 않다고 직접 판단 할 수 있습니다. 두 객체의 해시 코드 값이 같지 않으면 두 개의 다른 객체 여야합니다. 두 객체가 진정으로 같은지 여부를 결정하려면 Equals 메소드를 사용해야합니다.
즉, 두 객체의 경우, Equals 메소드를 호출하여 얻은 결과가 참이면 두 객체의 해시 코드 값이 동일해야합니다.
평등 메소드에 의해 얻은 결과가 False 인 경우, 두 객체의 해시 코드 값은 다르지 않을 수 있습니다.
두 객체의 해시 코드 값이 동일하지 않은 경우, 평등 메소드에 의해 얻은 결과는 False 여야합니다.
두 객체의 해시 코드 값이 동일하면, Equals 메소드에 의해 얻은 결과는 알려져 있지 않습니다.
2. 제비 방법 및 해시 코드 방법
경우에 따라 클래스를 설계 할 때 프로그래머는 문자열 클래스와 같은 동등한 메소드를 다시 작성해야하지만, Equals 메소드를 다시 작성하는 동안 해시 코드 메소드를 다시 작성해야합니다. 왜 그렇게 말합니까?
아래의 예를 보자 :
package com.cxh.test1; import java.util.hashmap; import java.util.hashset; import java.util.set; class people {private String name; 사적인 int 연령; 공개인 (문자열 이름, int age) {this.name = 이름; this.age = age; } public void 설정 (int Age) {this.age = age; } @override public boolean equals (object obj) {// todo 자동 생성 메소드 스텁이 this.name.equals ((사람) obj) .name) && this.age == ((people) obj) .age; }} public class main {public static void main (String [] args) {people p1 = 새로운 사람 ( "Jack", 12); System.out.println (p1.hashcode ()); Hashmap <People, Integer> Hashmap = New Hashmap <People, Integer> (); hashmap.put (p1, 1); System.out.println (hashmap.get (새로운 사람 ( "Jack", 12));}}여기에 나는 Equals 방법 만 다시 작성하여 두 사람의 개체가 동일한 이름과 나이를 가지고 있으면 같은 사람으로 간주됩니다.
이 코드의 원래 의도는 "1"의 결과를 출력하는 것이지만 실제로는 "null"을 출력합니다. 왜? 그 이유는 Equals 메소드를 다시 작성하면서 해시 코드 메소드를 다시 작성하는 것을 잊어 버리기 때문입니다.
이름과 나이 같은 두 개의 객체는 논리적으로 문자열 클래스와 유사하게 결정되지만 기본적으로 해시 코드 메소드는 객체의 저장 주소를 매핑한다는 것을 알아야합니다. 위의 코드의 출력 결과가 "null"이라는 것은 놀라운 일이 아닙니다. 그 이유는 매우 간단하고 P1과
System.out.println (hashmap.get (새로운 사람 ( "Jack", 12));이 문장에서 새로운 사람 ( "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; }따라서 해시 맵이 얻어지는 경우, 획득 된 해시도 값이 다르기 때문에 (위의 코드는 경우에 따라 동일한 해시 코드 값을 얻을 수 있지만, 두 객체의 스토리지 주소가 다르기 때문에 확률은 비교적 작다는 점에 유의하십시오. 비록 동일한 해시 코드 값이 다르지만, 루프는 직접 실행되지 않으며 GET 방법으로 직접 실행될 수 있습니다.
따라서 위의 코드가 결과 "1"을 출력하려면 매우 간단합니다. 해시 코드 메소드를 다시 작성하고 Equals 메소드 및 해시 코드 메소드를 항상 논리적으로 일관성있게 만들면됩니다.
package com.cxh.test1; import java.util.hashmap; import java.util.hashset; import java.util.set; 클래스 사람들 {개인 문자열 이름; 사적인 int 연령; 공개인 (문자열 이름, int age) {this.name = 이름; this.age = age; } public void 설정 (int Age) {this.age = age; } @override public int hashcode () {// todo 자동 생성 메소드 스터브 리턴 이름 .hashcode ()*37+age; } @override public boolean equals (object obj) {// todo 자동 생성 메소드 스텁이 this.name.equals ((사람) obj) .name) && this.age == ((people) obj) .age; }} public class main {public static void main (String [] args) {people p1 = 새로운 사람 ( "Jack", 12); System.out.println (p1.hashcode ()); Hashmap <People, Integer> Hashmap = New Hashmap <People, Integer> (); hashmap.put (p1, 1); System.out.println (hashmap.get (새로운 사람 ( "Jack", 12))); }}이러한 방식으로 출력 결과는 "1"입니다.
다음 구절은 효과적인 Java 책에서 발췌 한 것입니다.
프로그램 실행 중에, Equals 메소드의 비교 작업에 사용 된 정보가 수정되지 않는 한, 해시 코드 방법은 동일한 정수를 지속적으로 반환해야합니다.
두 객체가 Equals 메소드에 따라 같으면 두 객체의 해시 코드 메소드를 호출하면 동일한 정수 결과를 반환해야합니다.
두 객체가 평등 방법에 따라 불평등을 비교하면 해시 코드 방법이 반드시 다른 정수를 반환 할 필요는 없습니다.
두 번째와 세 번째 기사를 이해하기는 쉽지만 첫 번째 기사는 종종 무시됩니다. "Java Programming Thinks"라는 책의 P495 페이지의 첫 번째 구절도 있습니다.
"hashcode ()를 설계 할 때 가장 중요한 요소는 다음과 같습니다. 동일한 객체에서 hashcode ()을 호출 할 때마다 동일한 값이 동일한 값을 생성해야합니다. put ()을 사용하여 객체가 해시 맵에 추가되면 get ()와 함께 꺼낼 때 다른 해시 코드 값이 생성 될 때 객체에 따라 변수 데이터에 의존 할 수 없을 때, 사용자가 달라야합니다. hashcode () 메소드는 다른 해시 코드를 생성합니다. "
예는 다음과 같습니다.
패키지 com.cxh.test1; import java.util.hashmap; import java.util.hashset; import java.util.set; 클래스 사람 {private String name; 사적인 int 연령; 공개인 (문자열 이름, int age) {this.name = 이름; this.age = age; } public void 설정 (int Age) {this.age = age; } @override public int hashcode () {// todo 자동 생성 메소드 스터브 리턴 이름 .hashcode ()*37+age; } @override public boolean equals (object obj) {// todo 자동 생성 메소드 스텁이 this.name.equals ((사람) obj) .name) && this.age == ((people) obj) .age; }} public class main {public static void main (String [] args) {people p1 = 새로운 사람 ( "Jack", 12); System.out.println (p1.hashcode ()); Hashmap <People, Integer> Hashmap = New Hashmap <People, Integer> (); hashmap.put (p1, 1); P1. 세트 (13); System.out.println (hashmap.get (p1)); }}이 코드 출력의 결과는 "NULL"이며 모든 사람은 그 이유에 대해 분명해야합니다.
따라서 해시 코드 방법을 설계하고 메소드와 동일 할 때 객체의 데이터가 휘발성이면 동등한 메소드 및 해시 코드 메소드 에서이 필드에 의존하지 않는 것이 가장 좋습니다.
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.