String에서 Java의 hashCode ()가 31을 승수로 사용하는 이유는 무엇입니까?


480

자바 문서 단위의 해시 코드 A의 String객체는 다음과 같이 계산된다 :

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

int산술 사용 ( 여기서 문자열 s[i]i 번째 문자)은 문자열 n의 길이이며 ^지수를 나타냅니다.

31이 승수로 사용되는 이유는 무엇입니까?

승수가 상대적으로 큰 소수 여야 함을 이해합니다. 그렇다면 29, 37 또는 97이 아닌 이유는 무엇입니까?


1
또한 stackoverflow.com/questions/1835976/…을 비교하십시오.- 자신의 hashCode 함수를 작성하는 경우 31은 잘못된 선택이라고 생각합니다.
Hans-Peter Störr

6
29 세, 37 세 또는 심지어 97 세라면 '31이 아닌 이유 '를 묻는 것입니다.
Lorne의 후작

2
@EJP는 아니오를 선택한 이유를 아는 것이 중요합니다. 숫자가 흑 마술의 결과가 아닌 한.
Dushyant Sabharwal

: @ 여기에 대한 피터 lawrey에 의해 블로그 포스트가 vanilla-java.github.io/2018/08/12/... 여기 : vanilla-java.github.io/2018/08/15/...은
크리스토프 후씨

@DushyantSabharwal 내 지점은 수도 있다는 것이다 되어 매우 실용적인 변화하지 않고, 29 또는 37 또는 97 또는 41 또는 많은 다른 값. 1976 년에 37을 사용했습니다.
Lorne의 후작

답변:


405

Joshua Bloch의 Effective Java (충분히 추천 할 수 없으며 스택 오버 플로우에 대한 지속적인 언급 덕분에 구입 한 책)에 따르면 :

값 31은 홀수 소수이므로 선택되었습니다. 짝수이고 곱셈이 오버플로 된 경우 2의 곱셈은 이동과 동일하므로 정보가 손실됩니다. 소수를 사용하는 이점은 명확하지 않지만 전통적입니다. 31의 좋은 속성은 곱셈을 더 나은 성능을 위해 시프트와 빼기로 대체 할 수 있다는 것 31 * i == (i << 5) - i입니다. 최신 VM은 이러한 종류의 최적화를 자동으로 수행합니다.

(참조 : 3 장, 항목 9 : 항상 해시 코드 무시 (48 페이지)


346
2를 제외한 모든 소수는 홀수입니다.
Kip

38
나는 Bloch가 그것이 홀수 프라임이기 때문에 선택되었다고 생각하지 않는다고 생각하지만 홀수이며 AND이기 때문에 프라임 (및 시프트 / 뺄셈에 쉽게 최적화 될 수 있기 때문에) 때문입니다.
matt b

50
31이 선택되었다는 이상한 홀수 ??? 그 나던 말이 - 나는 그것이 최선의 분포를 주었기 때문에 (31)가 선정되었다라고 - 체크 computinglife.wordpress.com/2008/11/20/...
computinglife

65
나는 31의 선택이 오히려 불행하다고 생각합니다. 물론, 오래된 시스템에서는 몇 개의 CPU주기를 절약 할 수 있지만 "@ 및 #! 또는 Ca 및 DB와 같은 짧은 ASCII 문자열에서 이미 해시 충돌이 발생합니다. 예를 들어 1327144003 또는 또한 허용 bitshift 적어도 524,287 : 524,287 난 << 19 == * - 난.
한스 피터 스토

15
@Jason 내 답변보기 stackoverflow.com/questions/1835976/… . 내 요점은 더 큰 소수를 사용하면 충돌이 훨씬 적어지고 요즘에는 아무것도 잃지 않습니다. 영어 이외의 일반적인 문자를 영어 이외의 언어로 사용하면 문제가 더 악화됩니다. 그리고 31은 자신의 hashCode 함수를 작성할 때 많은 프로그래머에게 나쁜 예입니다.
Hans-Peter Störr

80

Goodrich와 Tamassia가 지적한 것처럼 50,000 개 이상의 영어 단어 (유닉스의 두 변형에서 제공되는 단어 목록의 결합으로 구성됨 )를 사용하는 경우 상수 31, 33, 37, 39 및 41을 사용하면 7 번 미만의 충돌이 발생합니다. 각각의 경우에. 이것을 알면 많은 Java 구현이 이러한 상수 중 하나를 선택한다는 것은 놀라운 일이 아닙니다.

우연히도이 질문을 보았을 때 "다항식 해시 코드"섹션을 읽는 중이었습니다.

편집 : 여기에 내가 언급하고있는 ~ 10mb PDF 책에 대한 링크가 있습니다. Java데이터 구조 및 알고리즘에 대한 섹션 10.2 해시 테이블 (413 페이지)을 참조하십시오.


6
그러나 ASCII 범위를 벗어난 공통 문자로 모든 종류의 국제 문자 집합을 사용하면 충돌이 더 많이 발생할 수 있습니다. 적어도 나는 이것을 31과 독일어로 확인했다. 그래서 31의 선택이 잘못되었다고 생각합니다.
Hans-Peter Störr

1
@ jJack, 귀하의 답변에 제공된 링크가 손상되었습니다.
SK Venkat

이 답변의 두 링크가 모두 끊어졌습니다. 또한 첫 번째 단락의 주장은 불완전합니다. 이 벤치 마크에 나열된 5 개와 다른 홀수를 어떻게 비교합니까?
Mark Amery

58

(대부분의) 오래된 프로세서에서는 31을 곱하면 비교적 저렴할 수 있습니다. 예를 들어 ARM에서는 하나의 명령어 일뿐입니다.

RSB       r1, r0, r0, ASL #5    ; r1 := - r0 + (r0<<5)

대부분의 다른 프로세서에는 별도의 시프트 및 빼기 명령이 필요합니다. 그러나 승수가 느린 경우에도 여전히 승리입니다. 최신 프로세서는 빠른 승수를 갖는 경향이 있으므로 32가 올바른 쪽을 유지하는 한 큰 차이는 없습니다.

그것은 훌륭한 해시 알고리즘은 아니지만 1.0 코드보다 좋고 훌륭합니다 (1.0 사양보다 훨씬 낫습니다!).


7
재미있게도, 31의 곱셈은 내 데스크탑 컴퓨터에서 실제로 92821과 같은 곱셈보다 약간 느립니다. 컴파일러가 그것을 "최적화"하여 시프트하고 추가하려고 시도합니다. :-)
Hans-Peter Störr

1
필자는 +/- 255 범위의 모든 값에서 똑같이 빠르지 않은 ARM을 사용한 적이 없다고 생각합니다. 2-1의 거듭 제곱을 사용하면 두 값을 일치하게 변경하면 해시 코드가 2의 거듭 제곱으로 변경되는 불행한 효과가 있습니다. -31의 값이 더 좋았을 것이고, -83 (64 + 16 + 2 + 1)과 같은 것이 더 좋을 것이라고 생각할 것입니다 (비트를 약간 더 좋게 만듭니다).
supercat

@supercat 마이너스로 확신하지 않습니다. 다시 0으로 향할 것 같습니다. / String.hashCodeIIRC는 8 비트 멀티 플라이어를 도입했으며 시프트 연산과 함께 산술 / 논리를 위해 2 사이클로 증가한 StrongARM보다 앞서 있습니다.
Tom Hawtin-tackline

1
@ TomHawtin-tackline : 31을 사용하면 4 개의 값의 해시는 29791 * a + 961 * b + 31 * c + d입니다. -31을 사용하면 -29791 * a + 961 * b-31 * c + d가됩니다. 4 개의 항목이 독립적 인 경우 그 차이는 중요하지 않다고 생각하지만 인접한 항목 쌍이 일치하면 결과 해시 코드는 모든 unpaired 항목과 32의 배수 (페어링 된 항목)의 기여입니다. 문자열의 경우 그다지 중요하지 않을 수도 있지만 해시 집계에 대한 범용 방법을 작성하는 경우 인접한 항목이 일치하는 상황이 불균형하게 일반적입니다.
supercat

3
재미있는 사실 @supercat의 해시 코드 Map.Entry로 사양으로 수정되었습니다 key.hashCode() ^ value.hashCode()로서에도 불구하고, 심지어 정렬되지 않은 쌍되지 않습니다 keyvalue완전히 다른 의미를 가지고있다. 예, Map.of(42, 42).hashCode()또는 Map.of("foo", "foo", "bar", "bar").hashCode()등이 예상대로 0 임을 의미합니다 . 따라서지도를 다른지도의 열쇠로 사용하지 마십시오…
Holger

33

곱하면 비트가 왼쪽으로 이동합니다. 사용 가능한 해시 코드 공간을 더 많이 사용하여 충돌을 줄입니다.

2의 거듭 제곱을 사용하지 않으면 가장 낮은 비트의 가장 오른쪽 비트도 채워져 해시로 들어가는 다음 데이터와 혼합됩니다.

식은와 n * 31같습니다 (n << 5) - n.


29

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622의 "설명"에서 Bloch의 원래 추론을 읽을 수 있습니다 . 그는 해시 테이블의 결과 "평균 체인 크기"와 관련하여 다른 해시 함수의 성능을 조사했습니다. P(31)그는 당시 K & R의 책에서 찾은 일반적인 기능 중 하나였습니다 (그러나 Kernighan과 Ritchie조차도 그것이 어디서 왔는지 기억하지 못했습니다). 결국 그는 기본적으로 하나를 선택해야 했으므로 P(31)성능이 충분히 좋아진 것처럼 보였습니다. 비록 P(33)정말 나빴다 33에 의한 곱셈 똑같이 빠른 계산 (단 5로 변화하고 추가)로, 그는 33 이후 (31)에 대한 거부는 주요되지 않습니다 :

나머지 4 개 중 RISC 머신에서 계산하는 것이 가장 저렴하기 때문에 P (31)을 선택했을 것입니다 (31은 2의 2 제곱의 차이이므로). P (33)는 계산하기가 비슷하지만 성능이 약간 떨어지고 33은 합성이므로 약간 긴장합니다.

따라서 추론은 여기에 많은 대답이 암시하는 것처럼 합리적이지 않았습니다. 그러나 우리는 직감 결정 후에 합리적 이유를 생각해내는 데 능숙합니다 (Bloch조차도 그 경향이 있습니다).


2
철저한 연구와 편견없는 답변!
Vishal K

22

실제로 37은 꽤 잘 작동합니다! z : = 37 * x는로 계산할 수 있습니다 y := x + 8 * x; z := x + 4 * y. 두 단계 모두 하나의 LEA x86 명령어에 해당하므로 매우 빠릅니다.

실제로, 짝수보다 큰 소수 ( 73) 와의 곱셈 은 설정하여 동일한 속도로 수행 될 수 있습니다 y := x + 8 * x; z := x + 8 * y.

73 또는 37 (31 대신)을 사용하면 코드더 밀도가 높아지기 때문에 더 좋을 수 있습니다 . 두 LEA 명령은 31만큼 곱하기 위해 이동 + 시프트 + 빼기의 경우 6 바이트 대 7 바이트 만 사용합니다. 가능한 한 가지주의 사항은 여기에 사용 된 3- 인수 LEA 명령어는 인텔의 샌디 브리지 아키텍처에서 3주기의 대기 시간이 증가함에 따라 느려졌습니다.

또한 73 은 Sheldon Cooper가 가장 좋아하는 숫자입니다.


5
파스칼 프로그래머입니까? : = 물건은 무엇입니까?
Mainguy

11
@Mainguy 실제로 ALGOL 구문이며 의사 코드에서 상당히 자주 사용됩니다.
ApproachingDarknessFish

4
그러나 ARM 어셈블리에서는 단일 명령어로 31을 곱할 수 있습니다
phuclv


에서 TPOP (1999) 한 초기 자바 (P.57)에 대해 읽을 수 있습니다 : "... 문제는 우리가 (의 승수로 표시 한 것과 1 당량과 해시를 대체하여 해결되었습니다 37 ...)"
miku

19

Neil Coffey 왜 31이 편견 다림질에 사용되는지 설명합니다 .

기본적으로 31을 사용하면 해시 함수에 대해 더욱 균일 한 세트 비트 확률 분포가 제공됩니다.


12

에서 JDK-4045622 , 여호수아 블로흐는 특정 (신규) 이유에 대해 설명 String.hashCode()구현이 선택되었다

아래 표에는 세 가지 데이터 세트에 대해 위에서 설명한 다양한 해시 함수의 성능이 요약되어 있습니다.

1) Merriam-Webster 's 2nd Int'l Unabridged Dictionary (311,141 개의 문자열, 평균 길이 10 자)에 항목이있는 모든 단어와 구.

2) / bin / , / usr / bin / , / usr / lib / , / usr / ucb / 및 / usr / openwin / bin / *의 모든 문자열 (66,304 개 문자열, 평균 길이는 21 자).

3) 웹 크롤러가 지난 밤 몇 시간 동안 실행 한 URL 목록 (28,372 개의 문자열, 평균 길이는 49 자).

표에 표시된 성능 지표는 해시 테이블의 모든 요소에 대한 "평균 체인 크기"입니다 (예 : 키 수의 예상 값이 요소를 조회하기 위해 비교 함).

                          Webster's   Code Strings    URLs
                          ---------   ------------    ----
Current Java Fn.          1.2509      1.2738          13.2560
P(37)    [Java]           1.2508      1.2481          1.2454
P(65599) [Aho et al]      1.2490      1.2510          1.2450
P(31)    [K+R]            1.2500      1.2488          1.2425
P(33)    [Torek]          1.2500      1.2500          1.2453
Vo's Fn                   1.2487      1.2471          1.2462
WAIS Fn                   1.2497      1.2519          1.2452
Weinberger's Fn(MatPak)   6.5169      7.2142          30.6864
Weinberger's Fn(24)       1.3222      1.2791          1.9732
Weinberger's Fn(28)       1.2530      1.2506          1.2439

이 표를 보면 현재 Java 기능과 Weinberger 기능의 두 가지 깨진 버전을 제외한 모든 기능이 훌륭하고 거의 구별 할 수없는 성능을 제공한다는 것이 분명합니다. 이 성능은 본질적으로 "이론적 이상"이라고 강력하게 추측합니다. 이것은 해시 함수 대신 실제 난수 생성기를 사용하면 얻을 수있는 것입니다.

WAIS 함수에는 사양에 임의의 숫자 페이지가 포함되어 있으므로 성능이 훨씬 간단한 함수보다 우수하지 않기 때문에 WAIS 함수를 배제했습니다. 나머지 6 가지 기능 중 하나는 훌륭한 선택처럼 보이지만 하나를 선택해야합니다. 비록 비록 복잡하지는 않지만 추가 된 복잡성으로 인해 Vo의 변형과 Weinberger의 기능을 배제한다고 가정합니다. 나머지 4 개 중 RISC 머신에서 계산하는 것이 가장 저렴하기 때문에 P (31)을 선택했을 것입니다 (31은 2의 2 제곱의 차이이므로). P (33)는 계산하기가 비슷하지만 성능이 약간 떨어지고 33은 합성이므로 약간 긴장합니다.

조롱


5

Bloch는 이것에 대해 꽤 들어 가지 않지만, 항상 들었거나 믿었던 근거는 이것이 기본 대수학이라는 것입니다. 해시는 곱셈과 계수 연산으로 귀결됩니다. 즉, 도움이 될 수 있다면 공통 요소가있는 숫자를 사용하고 싶지 않습니다. 다시 말해서, 상대적 소수는 답변의 고른 분포를 제공합니다.

해시를 사용하여 구성되는 숫자는 일반적으로 다음과 같습니다.

  • 입력 한 데이터 유형의 계수 (2 ^ 32 또는 2 ^ 64)
  • 해시 테이블에서 버킷 수의 계수 (변함. 자바에서는 소수였습니다. 이제 2 ^ n)
  • 믹싱 함수에서 매직 넘버를 곱하거나 시프트
  • 입력 값

실제로 이러한 값 중 몇 가지만 제어 할 수 있으므로 약간의주의가 필요합니다.


4

최신 버전의 JDK에서는 여전히 31이 사용됩니다. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()

해시 문자열의 목적은

  • unique ( ^해시 코드 계산 문서에서 연산자 를 보자. 유일한 도움이 됨)
  • 계산을위한 저렴한 비용

31은 최대 값이 8 비트 (= 1 바이트) 레지스터에 넣을 수 있으며, 최대 소수는 1 바이트 레지스터에 넣을 수 있으며 홀수입니다.

곱하기 31은 << 5이고 그 자체를 빼기 때문에 값싼 자원이 필요합니다.


3

확실하지는 않지만 소수 샘플을 테스트 한 결과 31이 가능한 문자열 샘플에 가장 잘 분포되어 있음을 알았습니다.


1

31은 훌륭한 속성을 가지고 있기 때문에 곱셈은 표준 곱셈보다 빠른 비트 단위 시프트로 대체 될 수 있습니다.

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