자바 문서 단위의 해시 코드 A의 String
객체는 다음과 같이 계산된다 :
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
int
산술 사용 ( 여기서 문자열s[i]
의 i 번째 문자)은 문자열n
의 길이이며^
지수를 나타냅니다.
31이 승수로 사용되는 이유는 무엇입니까?
승수가 상대적으로 큰 소수 여야 함을 이해합니다. 그렇다면 29, 37 또는 97이 아닌 이유는 무엇입니까?
자바 문서 단위의 해시 코드 A의 String
객체는 다음과 같이 계산된다 :
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
int
산술 사용 ( 여기서 문자열s[i]
의 i 번째 문자)은 문자열n
의 길이이며^
지수를 나타냅니다.
31이 승수로 사용되는 이유는 무엇입니까?
승수가 상대적으로 큰 소수 여야 함을 이해합니다. 그렇다면 29, 37 또는 97이 아닌 이유는 무엇입니까?
답변:
Joshua Bloch의 Effective Java (충분히 추천 할 수 없으며 스택 오버 플로우에 대한 지속적인 언급 덕분에 구입 한 책)에 따르면 :
값 31은 홀수 소수이므로 선택되었습니다. 짝수이고 곱셈이 오버플로 된 경우 2의 곱셈은 이동과 동일하므로 정보가 손실됩니다. 소수를 사용하는 이점은 명확하지 않지만 전통적입니다. 31의 좋은 속성은 곱셈을 더 나은 성능을 위해 시프트와 빼기로 대체 할 수 있다는 것
31 * i == (i << 5) - i
입니다. 최신 VM은 이러한 종류의 최적화를 자동으로 수행합니다.
(참조 : 3 장, 항목 9 : 항상 해시 코드 무시 (48 페이지)
Goodrich와 Tamassia가 지적한 것처럼 50,000 개 이상의 영어 단어 (유닉스의 두 변형에서 제공되는 단어 목록의 결합으로 구성됨 )를 사용하는 경우 상수 31, 33, 37, 39 및 41을 사용하면 7 번 미만의 충돌이 발생합니다. 각각의 경우에. 이것을 알면 많은 Java 구현이 이러한 상수 중 하나를 선택한다는 것은 놀라운 일이 아닙니다.
우연히도이 질문을 보았을 때 "다항식 해시 코드"섹션을 읽는 중이었습니다.
편집 : 여기에 내가 언급하고있는 ~ 10mb PDF 책에 대한 링크가 있습니다. Java 의 데이터 구조 및 알고리즘에 대한 섹션 10.2 해시 테이블 (413 페이지)을 참조하십시오.
(대부분의) 오래된 프로세서에서는 31을 곱하면 비교적 저렴할 수 있습니다. 예를 들어 ARM에서는 하나의 명령어 일뿐입니다.
RSB r1, r0, r0, ASL #5 ; r1 := - r0 + (r0<<5)
대부분의 다른 프로세서에는 별도의 시프트 및 빼기 명령이 필요합니다. 그러나 승수가 느린 경우에도 여전히 승리입니다. 최신 프로세서는 빠른 승수를 갖는 경향이 있으므로 32가 올바른 쪽을 유지하는 한 큰 차이는 없습니다.
그것은 훌륭한 해시 알고리즘은 아니지만 1.0 코드보다 좋고 훌륭합니다 (1.0 사양보다 훨씬 낫습니다!).
String.hashCode
IIRC는 8 비트 멀티 플라이어를 도입했으며 시프트 연산과 함께 산술 / 논리를 위해 2 사이클로 증가한 StrongARM보다 앞서 있습니다.
Map.Entry
로 사양으로 수정되었습니다 key.hashCode() ^ value.hashCode()
로서에도 불구하고, 심지어 정렬되지 않은 쌍되지 않습니다 key
와 value
완전히 다른 의미를 가지고있다. 예, Map.of(42, 42).hashCode()
또는 Map.of("foo", "foo", "bar", "bar").hashCode()
등이 예상대로 0 임을 의미합니다 . 따라서지도를 다른지도의 열쇠로 사용하지 마십시오…
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조차도 그 경향이 있습니다).
실제로 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가 가장 좋아하는 숫자입니다.
에서 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은 합성이므로 약간 긴장합니다.
조롱
Bloch는 이것에 대해 꽤 들어 가지 않지만, 항상 들었거나 믿었던 근거는 이것이 기본 대수학이라는 것입니다. 해시는 곱셈과 계수 연산으로 귀결됩니다. 즉, 도움이 될 수 있다면 공통 요소가있는 숫자를 사용하고 싶지 않습니다. 다시 말해서, 상대적 소수는 답변의 고른 분포를 제공합니다.
해시를 사용하여 구성되는 숫자는 일반적으로 다음과 같습니다.
실제로 이러한 값 중 몇 가지만 제어 할 수 있으므로 약간의주의가 필요합니다.
최신 버전의 JDK에서는 여전히 31이 사용됩니다. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()
해시 문자열의 목적은
^
해시 코드 계산 문서에서 연산자 를 보자. 유일한 도움이 됨)31은 최대 값이 8 비트 (= 1 바이트) 레지스터에 넣을 수 있으며, 최대 소수는 1 바이트 레지스터에 넣을 수 있으며 홀수입니다.
곱하기 31은 << 5이고 그 자체를 빼기 때문에 값싼 자원이 필요합니다.