hashCode에서 소수를 사용하는 이유는 무엇입니까?


174

왜 소수가 클래스의 hashCode()메소드에 사용되는지 궁금합니다 . 예를 들어, Eclipse를 사용하여 내 hashCode()메소드 를 생성 할 때 항상 소수가 31사용됩니다.

public int hashCode() {
     final int prime = 31;
     //...
}

참고 문헌 :

다음은 Hashcode에 대한 좋은 입문서와 내가 찾은 해싱의 작동 방식에 대한 기사입니다 (C #이지만 개념을 양도 할 수 있음). Eric Lippert의 GetHashCode ()에 대한 지침 및 규칙



이것은 질문 stackoverflow.com/questions/1145217/… 의 복제본입니다 .
Hans-Peter Störr

1
stackoverflow.com/questions/1145217/ 에서 답을 확인하십시오. 이것은 필드 (반지가 아닙니다!)의 다항식의 속성과 관련이 있으므로 소수입니다.
TT_

답변:


104

곱할 수와 삽입하는 버킷 수에 직교 소수 인수를 사용하기를 원하기 때문입니다.

삽입 할 버킷이 8 개 있다고 가정합니다. 곱하기 위해 사용하는 숫자가 8의 배수 인 경우 삽입 된 버킷은 가장 중요하지 않은 항목 (곱하지 않은 항목)에 의해서만 결정됩니다. 유사한 항목이 충돌합니다. 해시 함수에는 좋지 않습니다.

31은 버킷 수를 나눌 수 없을 정도로 큰 소수입니다 (실제로 현대 Java HashMap 구현은 버킷 수를 2의 거듭 제곱으로 유지합니다).


9
그런 다음 31을 곱한 해시 함수는 최적화되지 않은 방식으로 수행됩니다. 그러나 승수로서의 일반적인 31을 감안할 때 이러한 해시 테이블 구현이 제대로 설계되지 않은 것으로 간주합니다.
ILMTitan

11
따라서 해시 테이블 구현자가 31이 해시 코드에서 일반적으로 사용된다는 것을 알고 있다는 가정에 따라 31이 선택됩니까?
Steve Kuo

3
도 31은 대부분의 구현이 비교적 작은 소수의 인수 분해를 갖는다는 아이디어에 기초하여 선택된다. 보통 2, 3, 5입니다. 10에서 시작하여 너무 가득 차면 3 배가 될 수 있습니다. 크기는 거의 무작위가 아닙니다. 그리고 그것이 되었더라도 30/31은 잘 동기화 된 해시 알고리즘을 갖는 나쁜 확률이 아닙니다. 다른 사람들이 말한 것처럼 계산하기도 쉽습니다.
ILMTitan

8
다시 말해, 우리는 입력 값 집합과 집합 규칙에 대해 알아야합니다. 규칙을 제거하도록 설계된 함수를 작성하려면 집합의 값이 동일하게 충돌하지 않습니다. 해시 버킷. X- 항목이있는 LOOP가 있고 루프에서 Y- 공백을 점프하면 X가 Y의 요인이 될 때까지 동일한 지점으로 돌아 오지 않기 때문에 소수에 곱셈 / 분할 / 모듈화를하면 그 영향을받습니다. X는 종종 짝수 또는 2의 거듭 제곱이므로 Y는 소수이므로 X + X + X ...는 Y의 요소가 아니므로 31 yay입니다! : /
Triynko

3
@ 프랭크 큐. 모듈 식 산술의 본질입니다. (x*8 + y) % 8 = (x*8) % 8 + y % 8 = 0 + y % 8 = y % 8
ILMTitan

135

해시 버킷간에 데이터를 가장 잘 분배하기 위해 소수를 선택합니다. 입력의 분포가 임의적이고 균등하게 분산 된 경우 해시 코드 / 모듈의 선택은 중요하지 않습니다. 입력에 특정 패턴이있는 경우에만 영향을 미칩니다.

메모리 위치를 다룰 때 종종 그렇습니다. 예를 들어, 모든 32 비트 정수는 4로 나눌 수있는 주소에 정렬됩니다. 프라임 대 비 프라임 계수를 사용한 효과를 시각화하려면 아래 표를 확인하십시오.

Input       Modulo 8    Modulo 7
0           0           0
4           4           4
8           0           1
12          4           5
16          0           2
20          4           6
24          0           3
28          4           0

프라임 모듈러스 대 비 프라임 모듈러스를 사용할 때 거의 완벽한 분포를 확인하십시오.

그러나, 위의 예가 주로 고안되었지만, 일반적인 원리는 입력 패턴을 처리 할 때 소수 모듈러스를 사용하면 최상의 분포를 얻을 수 있다는 것입니다.


17
해시 코드를 버킷으로 분류하는 데 사용되는 모듈로가 아니라 해시 코드를 생성하는 데 사용되는 승수에 대해 이야기하고 있지 않습니까?
ILMTitan

3
같은 원리. I / O와 관련하여 해시는 해시 테이블의 모듈로 연산에 공급됩니다. 요점은 소수를 곱하면 모듈로가 중요하지 않은 지점에 더 무작위로 분포 된 입력을 얻을 수 있다는 것입니다. 해시 함수는 입력을 더 잘 분배하여 느슨하게 만들지 않기 때문에 입력을 덜 규칙적으로 만들므로 입력을 버킷에 배치하는 데 사용 된 모듈로에 관계없이 충돌 가능성이 줄어 듭니다.
Triynko

9
이런 종류의 답변은 다른 사람을 잡는 대신 낚시하는 법을 가르치는 것과 매우 유사하기 때문에 매우 유용합니다. 그것은 사람들이 도움 을 참조 하고 이해 : moduloed 일단 그들이 버킷으로 균일하게 떨어질 수 있도록 불규칙 입력을 배포하는 것입니다있는 ... 해시를 위해 소수를 사용하여 뒤에 기본 원칙을.
Triynko

29

가치있는 것을 위해, Effective Java 2nd Edition 은 수학 문제를 해결하고 31을 선택하는 이유는 다음과 같습니다.

  • 그것은 소수이며, 소수를 사용하는 것이 "전통적"이기 때문에
  • 또한 2의 거듭 제곱보다 1이 적으므로 비트 단위 최적화가 가능합니다.

항목 9hashCodeequals 의 전체 인용문은 다음과 같습니다 . 재정의 하면 항상 재정의하십시오 .

값 31은 홀수 소수이므로 선택되었습니다. 짝수이고 곱셈이 오버플로 된 경우 2의 곱셈은 이동과 동일하므로 정보가 손실됩니다. 소수를 사용하는 이점은 명확하지 않지만 전통적입니다.

31의 좋은 속성은 곱셈 을 더 나은 성능을 위해 교대 ( §15.19 )와 빼기 로 대체 할 수 있다는 것입니다 .

 31 * i == (i << 5) - i

최신 VM은 이러한 종류의 최적화를 자동으로 수행합니다.


이 항목의 레시피는 상당히 좋은 해시 함수를 생성하지만 최신 해시 함수를 생성하지는 않으며 Java 플랫폼 라이브러리가 릴리스 1.6 현재와 같은 해시 함수를 제공하지도 않습니다. 이러한 해시 함수를 작성하는 것은 연구 주제이며, 수학자 및 이론적 컴퓨터 과학자들에게 가장 왼쪽에 있습니다.

아마도이 플랫폼의 이후 릴리스는 일반 프로그래머가 그러한 해시 함수를 구성 할 수 있도록 클래스 및 유틸리티 메소드에 최신 해시 함수를 제공 할 것입니다. 그 동안이 항목에서 설명하는 기술은 대부분의 응용 프로그램에 적합해야합니다.

간단히 말해서, 제수가 많은 승수를 사용하면 더 많은 해시 충돌 이 발생한다고 말할 수 있습니다 . 효과적인 해싱을 위해 충돌 횟수를 최소화하고자하므로 제수가 적은 승수를 사용하려고합니다. 정의상 소수는 정확히 두 개의 구별되는 양의 제수를 갖습니다.

관련 질문


4
Eh, 그러나 2 ^ n + 1 (소위 Fermat primes ) 또는 2 ^ n-1 ( Mersenne primes ) 인 많은 적합한 소수 가 있습니다 . 그러나 (이 아닌 )가 선택되었습니다. 3, 5, 17, 257, 655373, 7, 31, 127, 8191, 131071, 524287, 214748364731127
Dmitry Bychenko

4
"이 홀수 소수이기 때문에" P : ... 하나에도 소수가
마틴 슈나이더

"Effective Java"에서 "명확하지는 않지만 전통적"이라는 표현이 마음에 들지 않습니다. 그가 수학적 세부 사항에 들어가고 싶지 않다면 "수학적 이유가 있습니다"와 같은 것을 작성해야합니다. 그가 글을 쓰는 방식은 역사적 배경 만있는 것처럼 들린다 :(
Qw3ry

5

컴파일러가 곱셈을 왼쪽 시프트 5 비트로 최적화하고 값을 뺄 수 있도록 31을 선택했다고 들었습니다.


컴파일러는 어떻게 그런 식으로 최적화 할 수 있습니까? x * 31 == x * 32-1은 모든 x에 대해 사실이 아닙니다. 당신이 의미하는 것은 왼쪽 시프트 5 (32와 곱하기)를 남긴 다음 원래 값을 뺍니다 (예제에서 x). 이것은 곱셈보다 빠를 수도 있지만 (현재의 CPU 프로세서에서는 그렇지 않을 수도 있음), haschcode에 대한 곱셈을 선택할 때 고려해야 할 더 중요한 요소가 있습니다 (버킷에 입력 값을 균등하게 분배 함)
Grizzly

약간의 검색을 수행하십시오. 이것은 매우 일반적인 의견입니다.
Steve Kuo

4
일반적인 의견은 관련이 없습니다.
fractor

1
@Grizzly, 그것은 이다 빠른 곱셈보다. IMul은 최신 CPU에서 최소 3 사이클의 대기 시간을 갖습니다. (agner fog 's manuals 참조) mov reg1, reg2-shl reg1,5-sub reg1,reg22 주기로 실행할 수 있습니다. mov는 이름 만 바꾸고 0 사이클이 걸립니다.
Johan

3

여기 소스에 조금 더 가까운 인용 이 있습니다.

그것은 다음과 같이 요약됩니다 :

  • 31은 소수이며 충돌을 줄입니다.
  • 31은
  • 합리적인 속도의 균형

3

먼저 해시 값 modulo 2 ^ 32 (a의 크기 int)를 계산하므로 2 ^ 32에 상대적으로 소수를 원합니다 (상대적으로는 제수가 없습니다). 홀수는 그렇게 할 것입니다.

그런 다음 주어진 해시 테이블의 색인은 일반적으로 해시 테이블의 크기를 해시 값 모듈로 계산하므로 해시 테이블의 크기에 비해 상대적으로 소수의 것을 원합니다. 이러한 이유로 해시 테이블의 크기는 종종 소수로 선택됩니다. Java의 경우 Sun 구현은 크기가 항상 2의 거듭 제곱인지 확인하므로 홀수로도 충분합니다. 충돌을 더욱 제한하기 위해 해시 키의 일부 추가 마사지가 있습니다.

해시 테이블과 승수가 공통 요소를 갖는 경우 나쁜 영향 n은 특정 상황에서 해시 테이블의 1 / n 항목 만 사용한다는 것입니다.


2

소수가 사용되는 이유는 데이터가 특정 패턴을 나타낼 때 충돌을 최소화하기 위해서입니다.

가장 먼저해야 할 일 : 데이터가 무작위 인 경우 소수가 필요하지 않은 경우 임의의 수에 대해 mod 연산을 수행 할 수 있으며 모듈의 가능한 각 값에 대해 동일한 충돌 횟수가 발생합니다.

그러나 데이터가 무작위가 아닌 경우 이상한 일이 발생합니다. 예를 들어 항상 10의 배수 인 숫자 데이터를 고려하십시오.

mod 4를 사용하면 다음을 찾을 수 있습니다.

10 모드 4 = 2

20 모드 4 = 0

30 모드 4 = 2

40 모드 4 = 0

50 모드 4 = 2

따라서 모듈러스 (0,1,2,3)의 3 가지 가능한 값에서 0과 2 만 충돌이 발생합니다.

7과 같은 소수를 사용하면

10 모드 7 = 3

20 모드 7 = 6

30 모드 7 = 2

40 모드 7 = 4

50 모드 7 = 1

기타

또한 5는 좋은 선택이 아니라 5는 소수입니다. 이유는 모든 키가 5의 배수이기 때문입니다. 즉, 키를 나누지 않는 소수를 선택해야합니다. 보통 충분합니다.

따라서 소수가 사용되는 이유는 반복적이라는 측면에서 잘못된 것은 해시 함수의 충돌 분포에서 키의 패턴 효과를 중화하는 것입니다.


1

31은 또한 int를 해시 데이터 유형으로 사용하는 Java HashMap에만 해당됩니다. 따라서 최대 용량은 2 ^ 32입니다. 더 큰 Fermat 또는 Mersenne 프라임을 사용할 필요는 없습니다.


0

일반적으로 특히 엔트로피가 낮은 키의 경우 해시 버킷간에 데이터를보다 고르게 분산시키는 데 도움이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.