Java 문자열에서 hashCode ()의 일관성


134

Java String의 hashCode 값은 ( String.hashCode () ) 로 계산됩니다 .

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

다음 표현식이 거짓으로 평가되는 환경 (JVM 버전, 공급 업체 등)이 있습니까?

boolean expression = "This is a Java string".hashCode() == 586653468

업데이트 # 1 : 답변이 "예, 그러한 환경이 있습니다"라고 주장하는 경우 "이것은 Java 문자열"입니다. 구체적인 예를 제공하십시오. hashCode ()! = 586653468 가능한 한.

업데이트 # 2 : 우리는 hashCode ()의 구현 세부 사항에 의존하는 것이 일반적으로 나쁘다는 것을 알고 있습니다. 그러나 String.hashCode ()에 대해 구체적으로 이야기하고 있으므로 String.hashCode ()에 집중하십시오. Object.hashCode ()는이 질문과 관련이 없습니다.


2
실제로이 기능이 필요합니까? 왜 정확한 가치가 필요합니까?
Brian Agnew

26
@ Brian : String.hashCode ()의 계약을 이해하려고합니다.
knorv

3
@Knorv 계약이 어떻게 작동하는지 정확하게 이해하는 것이 필요하지 않습니다. 계약과 그 의미를 이해하는 것이 더 중요합니다.
mP.

45
@ mP : 귀하의 의견에 감사하지만 결정하는 것은 나에게 달려 있다고 생각합니다.
knorv 2009

그들은 왜 첫 번째 캐릭터에게 가장 큰 힘을 주었습니까? 추가 계산을 유지하기 위해 속도를 최적화하려면 이전 계산의 힘을 저장하지만 이전 계산은 마지막 문자에서 첫 번째 문자까지입니다. 이는 캐시 누락도 발생한다는 의미입니다. s [0] + s [1] * 31 + s [2] * 31 ^ 2 + ... + s [n-1] * 31 ^ [n-1의 알고리즘을 갖는 것이 더 효율적이지 않습니다 ]?
안드로이드 개발자

답변:


101

Java 1.2까지 그 문서를 볼 수 있습니다.

일반적으로 동일하게 유지되는 해시 코드 구현에 의존해서는 안된다는 것은 사실이지만 이제는에 대한 동작이 문서화 java.lang.String되었으므로 변경하면 기존 계약을 위반하는 것으로 계산됩니다.

가능한 경우, 버전 등에서 동일하게 유지되는 해시 코드에 의존해서는 안됩니다.하지만 java.lang.String알고리즘 지정되어 있기 때문에 단순히 마음 에 특별한 경우 가 있습니다 ... 물론 알고리즘이 지정되었습니다.


7
Java 1.2 이후로 문서화 된 문자열의 동작이 지정되었습니다. API v1.1에서는 해시 코드 계산이 String 클래스에 대해 지정되지 않았습니다.
Martin OConnor

이 경우 자체 해싱 코드를 더 잘 작성하는 것이 좋을까요?
Felype

@Felype : 나는 당신이 여기서 무엇을 말하려고하는지 정말로 모르겠습니다.
Jon Skeet

@JonSkeet이 경우에는 이식성을 부여하기 위해 자체 해시를 생성하는 자체 코드를 작성할 수 있습니다. 그렇습니까?
Felype

@Felype : 어떤 종류의 이식성이 무엇인지, 또는 "이 경우"라는 의미가 무엇인지 명확하지 않습니다. 어떤 시나리오에서? 새로운 질문을해야한다고 생각합니다.
Jon Skeet 2016 년

18

JDK 1.0 및 1.1과> = 1.2에 대해 뭔가를 발견했습니다.

JDK 1.0.x 및 1.1.x에서 긴 문자열에 대한 hashCode 함수는 모든 n 번째 문자를 샘플링하여 작동했습니다. 이것은 꽤 많은 문자열을 같은 값으로 해싱하여 해시 테이블 조회 속도를 늦출 것을 보장합니다. JDK 1.2에서는 지금까지 결과에 31을 곱한 다음 다음 문자를 순서대로 추가하도록 기능이 개선되었습니다. 이것은 조금 느리지 만 충돌을 피하는 데 훨씬 좋습니다. 출처 : http://mindprod.com/jgloss/hashcode.html

해시 코드 대신 CRC32 또는 MD5를 사용하는 방법과 토론이 필요하지 않습니다.


8

특정 값과 동일한 해시 코드에 의존해서는 안됩니다. 동일한 실행 내에서 일관된 결과를 반환합니다. API 문서는 다음과 같이 말합니다.

hashCode의 일반적인 계약은 다음과 같습니다.

  • Java 응용 프로그램을 실행하는 동안 동일한 오브젝트에서 두 번 이상 호출 될 때마다 hashCode 메소드는 동일한 정수를 일관되게 리턴해야합니다. 이 정수는 응용 프로그램의 한 실행에서 동일한 응용 프로그램의 다른 실행까지 일관성을 유지할 필요가 없습니다.

편집 String.hashCode ()에 대한 javadoc은 문자열의 해시 코드 계산 방법을 지정하므로이를 위반하면 공개 API 사양을 위반하게됩니다.


1
귀하의 답변은 유효하지만 특정 질문에 대해서는 답변하지 않습니다.
knorv 2009

6
이것이 일반적인 해시 코드 계약이지만 String에 대한 특정 계약은 알고리즘에 대한 세부 정보를 제공하며이 일반 계약 IMO를 효과적으로 무시합니다.
Jon Skeet

4

위에서 말했듯이 일반적으로 동일하게 유지되는 클래스의 해시 코드에 의존해서는 안됩니다. 동일한 VM 에서 동일한 응용 프로그램 을 계속 실행하더라도 다른 해시 값이 생성 될 수 있습니다. AFAIK Sun JVM의 해시 함수는 모든 실행에서 동일한 해시를 계산하지만 보장되지는 않습니다.

이것은 이론적이지 않습니다. java.lang.String의 해시 함수 는 JDK1.2 에서 변경 되었습니다 (이전 해시는 URL이나 파일 이름과 같은 계층 적 문자열에 문제가있었습니다. 끝에는 다른 문자열에 대해 동일한 해시를 생성하는 경향이있었습니다).

java.lang.String은 hashCode ()의 알고리즘이 문서화되어 있기 때문에 특별한 경우이므로 신뢰할 수 있습니다. 나는 아직도 그것이 나쁜 습관이라고 생각합니다. 특수하고 문서화 된 속성이있는 해시 알고리즘이 필요한 경우 하나만 작성하십시오.


4
그러나 JDK 1.2 이전의 문서에 알고리즘이 지정 되었습니까? 그렇지 않은 경우 다른 상황입니다. 이 알고리즘은 이제 문서에 포함되어 있으므로 변경하면 공개 계약의 주요 변경 사항이됩니다.
Jon Skeet

(1.1으로 기억합니다.) 원래 (가난한) 알고리즘이 문서화되었습니다. 틀리게. 문서화 된 알고리즘은 실제로 ArrayIndexOutOfBoundsException을 발생 시켰습니다.
Tom Hawtin-tackline

@ Jon Skeet : 아, String.hashCode () 알고리즘이 문서화되어 있다는 것을 몰랐습니다. 물론 그것은 변화합니다. 내 의견을 업데이트했습니다.
sleske

3

걱정해야 할 또 다른 (!) 문제는 초기 / 늦은 Java 버전간에 구현이 변경 될 수 있다는 것입니다. 구현 세부 사항이 제대로 설정되어 있다고 생각하지 않으므로 향후 Java 버전으로 업그레이드 하면 문제가 발생할 수 있습니다.

결론은의 구현에 의존하지 않을 것입니다 hashCode().

이 메커니즘을 사용하여 실제로 해결하려는 문제를 강조 할 수 있으며보다 적합한 방법이 강조 될 수 있습니다.


1
답변 주셔서 감사합니다. "This is a Java string".hashCode ()! = 586653468의 구체적인 예를 들어 줄 수 있습니까?
knorv 2009

1
아뇨 내 요점은 테스트 한 모든 것이 원하는 방식으로 작동 할 수 있다는 것입니다. 그러나 그것은 여전히 ​​보장되지 않습니다. 따라서 VM 등을 제어 할 수있는 단기 프로젝트를 수행하는 경우 위의 방법이 효과적 일 수 있습니다. 그러나 당신은 더 넓은 세상에서 그것에 의존 할 수 없습니다.
Brian Agnew

2
"향후 Java 버전으로 업그레이드하면 문제가 발생할 수 있습니다". 향후 Java 버전으로 업그레이드하면 hashCode 메소드가 완전히 제거 될 수 있습니다. 또는 문자열에 대해 항상 0을 반환하도록하십시오. 나중에 호환되지 않는 변경 사항입니다. 문제는 Sun ^ HOracle ^ H JCP가이를 근본적인 변화로 간주하여 피할 가치가 있는지 여부입니다. 알고리즘이 계약에 포함되어 있기 때문에 알고리즘을 원합니다.
Steve Jessop

@SteveJessop switch은 문자열에 대한 문장이 특정 고정 해시 코드에 의존하는 코드로 컴파일 되기 때문에 String해시 코드 알고리즘을 변경하면 기존 코드가 손상 될 수 있습니다.
Holger

3

질문에 대답하고 토론을 계속하지 마십시오. Apache Harmony JDK 구현은 다른 알고리즘을 사용하는 것 같습니다. 적어도 완전히 다르게 보입니다.

일 JDK

public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

        for (int i = 0; i < len; i++) {
            h = 31*h + val[off++];
        }
        hash = h;
    }
    return h;
}

아파치 하모니

public int hashCode() {
    if (hashCode == 0) {
        int hash = 0, multiplier = 1;
        for (int i = offset + count - 1; i >= offset; i--) {
            hash += value[i] * multiplier;
            int shifted = multiplier << 5;
            multiplier = shifted - multiplier;
        }
        hashCode = hash;
    }
    return hashCode;
}

직접 확인하십시오 ...


23
나는 그들이 단지 시원하고 최적화하고 있다고 생각합니다. :) "(배율 << 5) - 승수"단지 31 * 곱셈기 결국 ... 인
풀림

좋아, 그것을 확인하기에는 너무 게으르다. 감사!
ReneS 2009

1
그러나 내 편에서 분명히하기 위해 ... 해시 코드는 내부에 있기 때문에 해시 코드에 의존하지 마십시오.
ReneS 2009

1
"offset", "count"및 "hashCode"의 변수는 무엇을 의미합니까? 나중에 계산을 피하기 위해 "hashcode"를 캐시 된 값으로 사용한다고 가정하고 "count"는 문자 수이지만 "offset"은 무엇입니까? 이 코드를 사용하여 문자열이 일관성이 유지되도록하려면 어떻게해야합니까?
안드로이드 개발자

1
@androiddeveloper 이제 흥미로운 질문입니다. 사용자 이름에 따라 추측해야했지만. 로부터 안드로이드 문서 는 계약과 같은 동일합니다 : s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]내가 잘못 생각하지 않는 안드로이드는 변경없이 String 객체의 일의 구현을 사용하기 때문에,이입니다.
Kartik Chugh

2

변경 사항과 호환되지 않는 VM이 ​​걱정되는 경우 기존 해시 코드 구현을 고유 한 유틸리티 클래스에 복사하여 해시 코드를 생성하십시오.


나는 이것을 말하려고했다. 다른 답변은 질문에 대답하지만 별도의 hashCode 함수를 작성하는 것이 knorv의 문제에 대한 적절한 솔루션 일 것입니다.
Nick

1

해시 코드는 문자열에있는 문자의 ASCII 값을 기반으로 계산됩니다.

이것은 String Class에서 다음과 같이 구현됩니다.

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        hash = h = isLatin1() ? StringLatin1.hashCode(value)
                              : StringUTF16.hashCode(value);
    }
    return h;
}

해시 코드의 충돌은 피할 수 없습니다. 예를 들어 문자열 "Ea"및 "FB"는 2236과 동일한 해시 코드를 제공합니다.

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