Eclipse 3.5には、Java hashCode()関数を生成するための非常に優れた機能があります。たとえば、(わずかに短縮された:)生成されます。
_class HashTest {
int i;
int j;
public int hashCode() {
final int prime = 31;
int result = prime + i;
result = prime * result + j;
return result;
}
}
_
(クラスにさらに属性がある場合、result = prime * result + attribute.hashCode();
は追加の属性ごとに繰り返されます。intの場合、.hashCode()は省略できます。)
これは問題ないようですが、プライムの選択31には適しています。これはおそらく、ハードウェアマルチプライヤの導入後に久しぶりにパフォーマンス上の理由で使用された hashCode実装Java String から取得されたものです。ここに、ハッシュコードの衝突が多数ありますiとjの値が小さい場合:たとえば、(0,0)と(-1,31)は同じ値です。小さい値が頻繁に発生するため、これはBad Thing(TM)だと思います。String.hashCodeの場合、また、「Ca」や「DB」など、同じハッシュコードを持つ多くの短い文字列も検索します。大きな素数を使用する場合、素数を選択すると、この問題は解消されます。
だから私の質問:選ぶのに良い素数は何ですか?それを見つけるためにどのような基準を適用しますか?
これは一般的な質問であるため、iとjの範囲を指定したくありません。しかし、ほとんどのアプリケーションでは、大きな値よりも比較的小さな値が頻繁に発生すると思います。 (値が大きい場合、素数の選択はおそらく重要ではありません。)それは大きな違いをもたらさないかもしれませんが、より良い選択はこれを改善する簡単で明白な方法です-それでなぜそれをしないのですか? Commons lang HashCodeBuilder も奇妙に小さい値を示唆しています。
(説明:これはではありませんJavaのhashCode()を文字列で使用する理由) 31を乗数として使用しますか? 私の質問はJDKの31の履歴には関係ありませんが、同じ基本テンプレートを使用する新しいコードではどの値がより良いのかについてです。それ。)
92821の使用をお勧めします。これが理由です。
これに意味のある答えを与えるには、i
とj
の可能な値について何かを知っている必要があります。一般的に考えられる唯一のことは、多くの場合、小さな値は大きな値よりも一般的であることです。 (プログラムの値として表示される15のオッズは、たとえば438281923よりもはるかに優れています。)したがって、適切な素数を選択することにより、最小のハッシュコードの衝突をできるだけ大きくすることをお勧めします。 31の場合、これはかなり悪いです。すでに_i=-1
_および_j=31
_の場合、_i=0
_および_j=0
_の場合と同じハッシュ値が使用されます。
これは興味深いので、intの範囲全体でこの意味で最高の素数を検索する小さなプログラムを作成しました。つまり、各素数について、_i,j
_と同じハッシュコードを持つ_0,0
_のすべての値に対してMath.abs(i) + Math.abs(j)
の最小値を検索し、この最小値の素数を取得しました可能な限り大きい。
Drumroll:この意味で最高の素数は486187739です(最小の衝突は_i=-25486, j=67194
_です)。 92821とほぼ同じくらいよく覚えやすく、最小の衝突は_i=-46272 and j=46016
_です。
「小さな」別の意味を与え、衝突の最小値としてMath.sqrt(i*i+j*j)
をできるだけ大きくしたい場合、結果は少し異なります。最良の結果は_i=-6815 and j=70091
_を使用した1322837333ですが、私のお気に入りの92821(最小の衝突_-46272,46016
_)も、最高の値とほぼ同じです。
これらの計算が実際に意味があるかどうかはかなり議論の余地があることを私は認めます。ただし、正当な理由がない限り、92821を31とするのは31よりもはるかに理にかなっていると思います。
衝突はそれほど大きな問題ではないかもしれません...ハッシュの主な目的は、1対1の比較で等号を使用しないようにすることです。ハッシュが衝突したオブジェクトのequalsが「一般に」非常に安価な実装の場合、これは問題ではありません(まったく)。
結局、ハッシュの最良の方法は何を比較するかによって異なります。 (例のように)intペアの場合、(&または^を使用するように)基本的なビット演算子を使用するだけで十分です。
実際、素数を大きくしてINT_MAX
に近づくと、モジュロ演算のために同じ問題が発生します。主に長さ2の文字列をハッシュすることが予想される場合、INT_MAX
の平方根に近い素数がおそらく最適です。
Iとjの範囲を定義する必要があります。両方に素数を使用できます。
public int hashCode() {
http://primes.utm.edu/curios/ ;)
return 97654321 * i ^ 12356789 * j;
}
私は7243を選択します。小さい数値との衝突を回避するのに十分な大きさです。すぐに少数にオーバーフローしない。
ハッシュコードはプライムとは関係がないことを指摘したいだけです。 JDK実装
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
1を27に置き換えると、結果は非常に似ています。