Java文字列でのhashCode()の一貫性


134

Java StringのhashCode値は(String.hashCode())として計算されます。

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

次の式がfalseと評価される状況(JVMバージョン、ベンダーなど)はありますか?

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

更新#1:「はい、そのような状況があります」と答えた場合は、「これはJava文字列です」という具体的な例を挙げてください。hashCode()!=586653468。具体的/具体的になるようにしてください。できるだけ。

アップデート#2:一般に、hashCode()の実装の詳細に依存することは悪いことです。ただし、私は特にString.hashCode()について話しているので、答えをString.hashCode()に集中させてください。Object.hashCode()は、この質問のコンテキストではまったく無関係です。


2
この機能が実際に必要ですか?正確な値が必要なのはなぜですか?
ブライアンアグニュー

26
@ブライアン:私はString.hashCode()のコントラクトを理解しようとしています。
knorv 2009

3
@Knorvそれがどのように機能するかを正確に理解する必要はありません-契約とその潜在的な意味を理解することがより重要です。
mP。

45
@mP:入力ありがとうございますが、それは私が決めることだと思います。
knorv 2009

なぜ彼らは最初のキャラクターに最大の力を与えたのですか?追加の計算を保持するために速度を最適化する場合は、前の計算のパワーを保存しますが、前の計算は最後の文字から最初の文字までです。つまり、キャッシュミスも発生します。s [0] + s [1] * 31 + s [2] * 31 ^ 2 + ... + s [n-1] * 31 ^ [n-1のアルゴリズムを使用する方が効率的ではありませんか]?
Android開発者

回答:


101

私はそのドキュメントをJava 1.2までさかのぼって見ることができます。

一般的に、ハッシュコードの実装が同じであることに依存すべきではないのは事実です、これはの動作が文書化されているためjava.lang.String、変更すると既存の規約に違反すると見なされます。

可能な限り、バージョン間で同じであるハッシュコードに依存するべきではありません。ただしjava.lang.String、アルゴリズム指定されいるというだけの理由で、私の考えは特別なケースです...以前のリリースとの互換性を放棄するつもりであればもちろん、アルゴリズムが指定されました。


7
文書化されているStringの動作は、Java 1.2以降のAPIのv1.1で指定されています。ハッシュコードの計算は、Stringクラスには指定されていません。
マーティンOConnor、2009

この場合、独自のハッシュコードを書く方がいいでしょう。
Felype 2013年

@Felype:ここで何を言おうとしているのか本当にわかりません。
Jon Skeet 2013年

@JonSkeetつまり、この場合、独自のコードを記述して独自のハッシュを生成し、移植性を付与する可能性があります。それは...ですか?
Felype 2013年

@Felype:どのような移植性について話しているのか、実際に「この場合」が何を意味しているのかは明確ではありません-特定のシナリオでは?新しい質問をするべきだと思います。
Jon Skeet 2013年

18

JDK 1.0と1.1および> = 1.2について何かを見つけました。

JDK 1.0.xおよび1.1.xでは、長い文字列のhashCode関数は、n番目ごとの文字をサンプリングすることで機能していました。これにより、多くの文字列が同じ値にハッシュされるため、Hashtableルックアップが遅くなります。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異なるハッシュ値を生成することができます。申し訳ありませんが、Sun JVMのハッシュ関数は実行ごとに同じハッシュを計算しますが、これは保証されていません。

これは理論的なものではないことに注意してください。java.lang.Stringのハッシュ関数はJDK1.2 で変更されました(古いハッシュは、URLやファイル名などの階層文字列に問題がありました。これは、末尾のみが異なる文字列に対して同じハッシュを生成する傾向があったためです)。

java.lang.Stringは特殊なケースです。そのhashCode()のアルゴリズムは(現在)文書化されているため、おそらくこれに依存できます。私はそれを悪い習慣だと考えています。ドキュメント化された特別なプロパティを持つハッシュアルゴリズムが必要な場合は、1つだけ記述してください:-)。


4
しかし、アルゴリズムはJDK 1.2以前のドキュメントで指定されていましたか?そうでなければ、それは別の状況です。アルゴリズムはドキュメントに記載されているため、変更すると、パブリックコントラクトに重大な変更が加えられます。
ジョンスキート、

(私はそれを1.1と覚えています。)元の(貧しい)アルゴリズムが文書化されました。不正解です。文書化されたアルゴリズムは、実際にはArrayIndexOutOfBoundsExceptionをスローしました。
トムホーティン-タックライン2009

@ジョンスキート:ああ、String.hashCode()のアルゴリズムが文書化されていることを知りませんでした。もちろんそれは状況を変えます。コメントを更新しました。
sleske 2009

3

心配すべきもう1つの問題(!)は、Javaの初期バージョンと最新バージョンの間で実装が変更される可能性があることです。実装の詳細が確定しているとは思わないので、将来の Javaバージョンにアップグレードすると問題が発生する可能性があります。

結論は、私はの実装に依存しません hashCode()

おそらく、このメカニズムを使用して実際に解決しようとしている問題を強調表示できます。これにより、より適切なアプローチが強調表示されます。


1
ご回答有難うございます。「これはJava文字列です」.hashCode()!= 586653468の具体例を教えてください。
knorv 2009

1
いいえ、申し訳ありません。私の要点は、テストしたすべてのものが期待どおりに機能する可能性があるということです。しかし、それはまだ保証ではありません。したがって、VMなどを制御している(たとえば)短期間のプロジェクトに取り組んでいる場合は、上記でうまくいく可能性があります。しかし、より広い世界でそれを信頼することはできません。
ブライアンアグニュー

2
「将来のJavaバージョンにアップグレードすると問題が発生する可能性があります」。将来のJavaバージョンにアップグレードすると、hashCodeメソッドが完全に削除される可能性があります。または、文字列に対して常に0を返すようにします。これは互換性のない変更です。問題は、Sun ^ HOracle ^ HThe JCPがそれを重大な変更と見なすかどうか、したがって回避する価値があるかどうかです。アルゴリズムは契約に含まれているので、期待通りです。
スティーブジェソップ

@SteveJessopよく、switch文字列のステートメントは特定の固定ハッシュコードに依存するコードにコンパイルされるため、Stringのハッシュコードアルゴリズムを変更すると、既存のコードは確実に破壊されます…
Holger

3

あなたの質問に答えるだけで、議論を続けるのではありません。Apache Harmony JDK実装は異なるアルゴリズムを使用しているようですが、少なくとも完全に異なるように見えます。

Sun 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 *乗数、結局のところ...
巻き

OK、それをチェックするのが面倒だった。ありがとう!
ReneS 2009

1
しかし、私の側からそれを明確にするために...ハッシュコードは内部的なものなので、ハッシュコードに依存しないでください。
ReneS 2009

1
「オフセット」、「カウント」、「ハッシュコード」の変数はどういう意味ですか?今後の計算を避けるために、「ハッシュコード」がキャッシュ値として使用され、「カウント」は文字数ですが、「オフセット」とは何ですか?このコードを使用して一貫性を保つには、文字列が与えられた場合、どうすればよいですか?
Android開発者

1
@androiddeveloperこれは興味深い質問です-私は推測したはずですが、ユーザー名に基づいています。Androidのドキュメントから、コントラクトは同じであるように見えます。s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]私が誤解しない限り、これはAndroidがSunのStringオブジェクトの実装を変更なしで使用しているためです。
Kartik Chugh

2

変更や、互換性のないVMが心配な場合は、既存のハッシュコード実装を独自のユーティリティクラスにコピーし、それを使用してハッシュコードを生成します。


私はこれを言うつもりでした。他の答えは質問に答えますが、別のhashCode関数を書くことはおそらくknorvの問題に対する適切な解決策です。
Nick

1

ハッシュコードは、文字列内の文字のASCII値に基づいて計算されます。

これは文字列クラスの実装です。

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.