hashCodeで素数を使用する理由


173

素数がクラスのhashCode()メソッドで使用されているのはなぜですか?たとえば、Eclipseを使用してhashCode()メソッドを生成する場合、常に素数が31使用されます。

public int hashCode() {
     final int prime = 31;
     //...
}

参照:

Hashcodeの優れた入門書と、私が見つけたハッシュのしくみに関する記事(C#ですが、概念は転送可能です)です 。EricLippertのガイドラインとGetHashCode()のルール



これは多かれ少なかれ質問の重複ですstackoverflow.com/questions/1145217/…
Hans-PeterStörr2012

1
stackoverflow.com/questions/1145217/…で私の答えを確認してください。これは、(環ではなく)フィールド上の多項式のプロパティに関連しているため、素数です。
TT_ 2013年

回答:


103

直交する素因数分解を行うには、乗算する数と挿入先のバケットの数が必要なためです。

挿入するバケットが8つあるとします。乗算に使用している数が8の倍数である場合、挿入されるバケットは、最下位のエントリ(まったく乗算されないもの)によってのみ決定されます。同様のエントリが衝突します。ハッシュ関数には適していません。

31は、バケットの数が割り切れる可能性が低いほど十分に大きい素数です(実際、最新のjava HashMap実装は、バケットの数を2の累乗に保ちます)。


9
次に、31倍するハッシュ関数は最適に実行されません。ただし、乗数として31がどれほど一般的であるかを考えると、そのようなハッシュテーブルの実装は十分に設計されていないと考えます。
ILMTitan 2010

11
それで、31はハッシュテーブルで31が一般的にハッシュコードで使用されることを知っているという仮定に基づいて選択されますか?
Steve Kuo

3
31は、ほとんどの実装が比較的小さな素数の因数分解を持つという考えに基づいて選択されています。通常2秒、3秒、5秒。それは10から始まり、満杯になると3倍に成長します。サイズが完全にランダムになることはほとんどありません。そして、たとえそうであったとしても、30/31は、よく同期されたハッシュアルゴリズムを持っていることの悪いオッズではありません。他の人が述べているように計算するのも簡単かもしれません。
ILMTitan 2010

8
言い換えれば、入力値のセットとセットの規則性について知っておく必要があります。これらの規則性を取り除くように設計された関数を記述して、セット内の値が同じように衝突しないようにする必要があります。ハッシュバケット。素数による乗算/除算/モジュロ演算は、その影響をもたらします。Xアイテムを含むループがあり、ループ内でYスペースをジャンプする場合、XがYの因数になるまで同じ場所に戻ることはないためです。 。Xは2の偶数または累乗であることが多いため、Yを素数にする必要があるため、X + X + X ...はYの因数ではないため、31と言ってよいでしょう。:/
Triynko

3
@FrankQ。これはモジュラー演算の性質です。 (x*8 + y) % 8 = (x*8) % 8 + y % 8 = 0 + y % 8 = y % 8
ILMTitan 2017年

135

素数は、ハッシュバケット間でデータを最適に分散するために選択されます。入力の分布がランダムで均等に分散している場合、ハッシュコード/係数の選択は重要ではありません。入力に特定のパターンがある場合にのみ影響があります。

これは、メモリの場所を扱う場合によく見られます。たとえば、すべての32ビット整数は4で割り切れるアドレスに揃えられます。以下の表をチェックして、素数と非素数のモジュラスを使用した場合の効果を視覚化してください。

Input       Modulo 8    Modulo 7
0           0           0
4           4           4
8           0           1
12          4           5
16          0           2
20          4           6
24          0           3
28          4           0

素数係数対非素数係数を使用する場合、ほぼ完全な分布に注意してください。

ただし、上記の例は大雑把に考案されていますが、一般的な原理は、入力のパターンを処理する場合、素数モジュラスを使用すると最良の分布が得られるということです。


17
ハッシュコードをバケットに分類するために使用されるモジュロではなく、ハッシュコードを生成するために使用される乗数について話していませんか?
ILMTitan 2010

3
同じ原則。I / Oに関しては、ハッシュはハッシュテーブルのモジュロ演算にフィードされます。要点は、素数を掛けると、モジュロが問題にならないポイントに、よりランダムに分散された入力が得られるということでした。ハッシュ関数は入力の分散のスラックをより適切に検出し、入力の規則性を低くするため、バケットに配置するために使用されるモジュロに関係なく、入力が衝突する可能性は低くなります。
Triynko

9
この種の答えは、誰かを捕まえるのではなく、釣り方を教えるようなものです。これは、ハッシュに素数を使用する背後にある基本的な原理を人々が理解するのに役立ちます...これは、入力を不規則に分散して、モジュロ処理されたらバケットに均一に入るようにすることです:)
Triynko

29

価値のあるものとして、Effective Java 2nd Editionは数学の問題を回避し、31を選択する理由は次のとおりだと述べています。

  • 奇数の素数であり、素数を使うのは「伝統的」だから
  • また、2の累乗よりも1少ないため、ビットごとの最適化が可能です。

以下は、項目9hashCodeequalsからの完全な引用です。オーバーライドするときは常にオーバーライドします

奇数の素数であるため、値31が選択されました。偶数で乗算がオーバーフローした場合、2による乗算はシフトと同じであるため、情報は失われます。素数を使用する利点はあまり明確ではありませんが、伝統的なものです。

31の優れた特性は、乗算をシフト(§15.19)および減算で置き換えることにより、パフォーマンスを向上できることです。

 31 * i == (i << 5) - i

最近のVMでは、この種の最適化が自動的に行われます。


このアイテムのレシピは適度に優れたハッシュ関数を生成しますが、最先端のハッシュ関数を生成せず、Javaプラットフォームライブラリはリリース1.6のようなハッシュ関数を提供しません。そのようなハッシュ関数を書くことは研究テーマであり、数学者や理論的なコンピュータ科学者に任せるのが最善です。

おそらく、プラットフォームの今後のリリースでは、そのクラスとユーティリティメソッドに最新のハッシュ関数を提供し、平均的なプログラマがそのようなハッシュ関数を構築できるようにする予定です。それまでの間、この項目で説明する手法は、ほとんどのアプリケーションに適しています。

むしろ単純化すると、多数の除数を持つ乗数を使用すると、より多くのハッシュ衝突が発生すると言えますます。効果的なハッシュのためには、衝突の数を最小限に抑えたいので、除数がより少ない乗数を使用しようとします。定義により、素数には正確に2つの明確な正の約数があります。

関連する質問


4
ええ、でも2 ^ n + 1(いわゆるフェルマー素数)、または2 ^ n-1メルセンヌ素数)の適切な素数はたくさんあります。ただし、(とはいえ)選択されています。3, 5, 17, 257, 655373, 7, 31, 127, 8191, 131071, 524287, 214748364731127
Dmitry Bychenko

4
「奇妙な素数だから」 ... 偶数素数は1つだけです:P
マーティンシュナイダー

「Effective Java」の「明確ではないが伝統的」という表現は好きではありません。彼が数学の詳細に進みたくない場合は、代わりに「数学的な理由がある[似ている]」のようなものを書く必要があります。彼の書き方は、歴史的な背景しかなかったようです:(
Qw3ry 2017

5

コンパイラーが乗算を5ビット左シフトして値を減算するように最適化できるように、31が選択されたと聞きました。


コンパイラはどのようにその方法を最適化できますか?x * 31 == x * 32-1は、結局すべてのxに当てはまるわけではありません。あなたが意味したのは、左シフト5(32を掛けたものに等しい)で、元の値(この例ではx)を差し引くことです。これは乗算よりも高速かもしれませんが(おそらく、最近のCPUプロセッサには適していない可能性があります)、ハッシュコードの乗算を選択するときに考慮すべきより重要な要素があります(バケットへの入力値の均等配分が頭に浮かびます)
グリズリー

少し検索してください。これはかなり一般的な意見です。
Steve Kuo

4
世論は無関係です。
フラクター

1
@グリズリー、それ乗算よりも高速です。IMul​​は、最新のCPUでは3サイクルの最小レイテンシを持っています。(agner fogのマニュアルを参照)mov reg1, reg2-shl reg1,5-sub reg1,reg2は2サイクルで実行できます。(movは単なる名前変更であり、0サイクルかかります)。
ヨハン

3

これが引用ですもう少し近くソースに。

要約すると:

  • 31は素数で、衝突を減らします
  • 31は、良い分布を生成します。
  • 速度の妥当なトレードオフ

3

まず、2 ^ 32を法とするハッシュ値( int)ので、2 ^ 32に比較的素数のものが必要です(比較的素数は、一般的な除数がないことを意味します)。奇数はそのためです。

次に、特定のハッシュテーブルのインデックスは通常、ハッシュテーブルのサイズを法とするハッシュ値から計算されるため、ハッシュテーブルのサイズに対して比較的素数のあるものが必要です。そのため、ハッシュテーブルのサイズが素数として選択されることがよくあります。Javaの場合、Sunの実装はサイズが常に2の累乗であることを確認しているため、ここでも奇数で十分です。衝突をさらに制限するために、ハッシュキーの追加のマッサージもいくつかあります。

ハッシュテーブルと乗数に共通の要素があった場合の悪影響 nがあるは、特定の状況ではハッシュテーブルの1 / nエントリのみが使用されることである可能性があります。


2

素数が使用される理由は、データが特定のパターンを示すときの衝突を最小限にするためです。

まず最初に:データがランダムである場合、素数の必要はありません。任意の数に対してmod操作を実行でき、係数の可能​​な値ごとに同じ数の衝突が発生します。

しかし、データがランダムでない場合、奇妙なことが起こります。たとえば、常に10の倍数である数値データについて考えます。

mod 4を使用すると、次のことがわかります。

10 mod 4 = 2

20 mod 4 = 0

30 mod 4 = 2

40 mod 4 = 0

50 mod 4 = 2

したがって、係数の可能​​な3つの値(0、1、2、3)から、0と2だけが衝突することになりますが、これは悪いことです。

7のような素数を使用する場合:

10 mod 7 = 3

20 mod 7 = 6

30 mod 7 = 2

40 mod 7 = 4

50 mod 7 = 1

また、5は良い選択ではありませんが、5は素数です。理由は、すべてのキーが5の倍数であるためです。つまり、キーを分割しない素数を選択する必要があります。大きな素数を選択すると、通常は十分です。

したがって、素数が使用される理由を繰り返しているという誤解は、ハッシュ関数の衝突の分布におけるキーのパターンの影響を中和することです。


1

31は、ハッシュデータ型としてintを使用するJava HashMapにも固有です。したがって、最大容量は2 ^ 32です。より大きなフェルマーまたはメルセンヌ素数を使用しても意味がありません。


0

一般的に、特に低エントロピーキーの場合、ハッシュバケット間でデータをより均等に分散させるのに役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.