Javaのドキュメントによると、String
name__オブジェクトの ハッシュコード は次のように計算されます。
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
int
name__算術演算を使用します。ここで、s[i]
は 私n
name__は文字列の長さ、^
はべき乗を表します。
なぜ乗数として31が使われるのですか?
乗数は比較的大きい素数であるべきだと私は理解しています。それでは、なぜ29、37、97でさえないのでしょうか。
Joshua Blochの Effective Java (十分に推薦できない本、そしてstackoverflowについての継続的な言及のおかげで私が買った本によると):
値31は奇数の素数であるために選択されました。偶数で乗算がオーバーフローした場合、2による乗算はシフトと同等であるため、情報は失われます。素数を使用する利点はあまり明確ではありませんが、それは伝統的です。 31のNiceプロパティは、乗算をシフトと減算で置き換えることでパフォーマンスを向上させることができるということです。
31 * i == (i << 5) - i
。最近のVMはこの種の最適化を自動的に行います。
(第3章、第9項:equalsをオーバーライドするときは常にハッシュコードをオーバーライドする(48ページ))
As Goodrich and Tamassia 指摘するように、もしあなたが50,000を超える英単語(Unixの二つの変種で提供されるWordリストの和集合として形成される)を使うなら、定数31、33、37、39、そして41の場合、衝突は7回未満です。これを知って、多くのJava実装がこれらの定数のうちの1つを選ぶのは当然のことです。
偶然にも、私がこの質問を見たとき私はセクション「多項式ハッシュコード」を読んでいる最中でした。
編集:ここで私は上記を参照している〜10メガバイトPDF本へのリンクです。 Javaのデータ構造体とアルゴリズム 10.2ハッシュテーブル(427ページ)を参照
(大部分の)古いプロセッサでは、31を掛けるのは比較的安価です。例えばARMでは、これは1命令にすぎません。
RSB r1, r0, r0, ASL #5 ; r1 := - r0 + (r0<<5)
他のほとんどのプロセッサは、別々のシフト命令と減算命令を必要とします。しかし、あなたの乗数が遅い場合、これはまだ勝利です。現代のプロセッサは高速な乗数を持つ傾向があるので、32が正しい側に行けばそれほど大きな違いはありません。
これは素晴らしいハッシュアルゴリズムではありませんが、1.0コードよりも十分に優れています(そして1.0仕様よりもはるかに優れています)。
乗算することで、ビットは左にシフトします。これはハッシュコードの利用可能なスペースをより多く使用し、衝突を減らします。
2のべき乗を使用しないことで、下位の右端のビットも生成され、ハッシュに入る次のデータと混ざります。
式n * 31
は(n << 5) - n
と同等です。
Blochの最初の推論は、 http://bugs.Java.com/bugdatabase/view_bug.do?bug_id=4045622 の「コメント」の下にあります。彼は、ハッシュテーブルで結果として得られる「平均チェーンサイズ」に関して、さまざまなハッシュ関数のパフォーマンスを調べました。 P(31)
はその間の彼がK&Rの本で見つけた共通の機能の1つでした(しかしKernighanとRitchieでさえそれがどこから来たのか思い出せませんでした)。結局、彼は基本的に1つを選ばなければなりませんでした、そしてそれは十分に実行するように思われたので彼はP(31)
を使いました。 P(33)
はそれほど悪くなく、33での乗算も同じくらい高速に計算できます(5のシフトと加算)。ただし、33は素数ではないので、彼は31を選びました。
残りの4つのうち、(31は2の2乗の差であるため)RISCマシンで計算するのが最も安いので、私はおそらくP(31)を選択します。 P(33)は、計算するのと同じくらい安価ですが、パフォーマンスはわずかに悪く、33はコンポジットであるため、少々緊張します。
そのため、ここでの多くの答えが暗示しているように思えるほど合理的ではありませんでした。しかし、私たちは皆、直感的な決定の後に合理的な理由を考え出すことに長けています(そして、Blochでさえそれに傾向があるかもしれません)。
実際には、37はかなりうまくいくでしょう! z:= 37 * xはy := x + 8 * x; z := x + 4 * y
として計算できます。どちらのステップも1つのLEA x86命令に対応するため、これは非常に高速です。
実際、さらに大きい素数73との乗算は、y := x + 8 * x; z := x + 8 * y
を設定することによって同じ速度で実行できます。
より密度の高いコードになるため、73または37(31ではなく)を使用する方が良い場合があります。ここで使用されている3引数のLEA命令は、IntelのSandy Bridgeアーキテクチャでは遅くなり、レイテンシは3サイクル増加しました。
また、 7 はSheldon Cooperのお気に入りの番号です。
Neil Coffey 説明 なぜ31がの下で使われているのかバイアスを取り除いてください。
基本的に31を使用すると、ハッシュ関数のセットビット確率分布がより均等になります。
Joshua Blochがその特定の(新しい)String.hashCode()
実装が選ばれた理由を説明している JDK-4045622 から
以下の表は、3つのデータセットについて、上記のさまざまなハッシュ関数のパフォーマンスをまとめたものです。
1)Merriam-Websterの2nd Int'l Unabridged Dictionary(311,141文字列、平均長10文字)にエントリを含むすべての単語とフレーズ。
2)/ bin /、/ usr/bin /、/ usr/lib /のすべての文字列/ usr/ucb /および/ usr/openwin/bin/*(66,304文字列、平均長21文字)。
3)昨夜数時間にわたって実行されたWebクローラーによって収集された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関数と2つの壊れたバージョンのWeinbergerの関数を除くすべての関数が、優れた、ほぼ見分けがつかないパフォーマンスを提供することは明らかです。私はこの性能が本質的に「理論的理想」であると強く予想します、それはあなたがハッシュ関数の代わりに本当の乱数発生器を使ったならばあなたが得るものです。
その仕様には乱数のページが含まれているので、私はWAIS関数を除外します、そしてそのパフォーマンスははるかに単純な関数のどれよりも良くありません。残りの6つの機能はどれも優れた選択のように見えますが、1つ選択する必要があります。私は、Voの変種とWeinbergerの機能を、それらの複雑さが増したために、軽微ではあるが除外したいと思う。残りの4つのうち、(31は2の2乗の差であるため)RISCマシンで計算するのが最も安いので、私はおそらくP(31)を選択します。 P(33)は、計算するのと同じくらい安価ですが、パフォーマンスはわずかに悪く、33はコンポジットであるため、少々緊張します。
ジョシュ
Blochはこれに完全には入りません、しかし私がいつも聞いた/信じた論理的根拠はこれが基本的な代数であるということです。ハッシュは乗算とモジュラス演算にまとめられています。つまり、手助けができるのであれば、一般的な要因を持つ数値を使用したくないということです。言い換えれば、比較的素数は答えの均等な分布を提供します。
ハッシュを使用して構成されている数は通常次のとおりです。
あなたは本当にこれらの値のうちのいくつかを制御するようになるだけなので、少し余分な注意が必要です。
確信はありませんが、彼らは素数のサンプルをテストしたところ、31が可能な文字列のサンプルに対して最良の分布を与えることがわかったと思います。
JDKの最新バージョンでは、31がまだ使用されています。 https://docs.Oracle.com/en/Java/javase/11/docs/api/Java.base/Java/lang/String.html#hashCode()
ハッシュ文字列の目的は
^
を見てください、それは一意に役立ちます)31は最大値を8ビット(= 1バイト)レジスタに入れることができます。 1バイトのレジスタに入れることができる最大の素数は奇数です。
掛け算31は<< 5です。それから自分自身を引くので、安価なリソースが必要です。
これは、31にはNiceプロパティがあるためです。その乗算は、標準の乗算よりも高速なビット単位のシフトに置き換えることができます。
31 * i == (i << 5) - i