web-dev-qa-db-ja.com

hashCode()でintにlongをどのようにマッピングすればよいですか?

GUIDのように、システム全体で特定のオブジェクトを一意に識別する値を持つlongフィールドを持つオブジェクトの範囲があります。比較のためにこのIDを使用するためにObject.equals()をオーバーライドしました。これは、オブジェクトのコピーを処理するためです。ここで、Object.hashCode()もオーバーライドしたいと思います。これは基本的に、longをいくつかのint戻り値にマッピングすることを意味します。

hashCodeの目的を正しく理解していれば、主にハッシュテーブルで使用されるため、均一な分布が望ましいでしょう。つまり、id % 2^32を返すだけで十分です。これですべてですか、それとも何か他のものに注意する必要がありますか?

45
Hanno Fietz

Java 8から使用できます

Long.hashCode(guid);

Javaの古いバージョンの場合、次を使用できます。

Long.valueOf(guid).hashCode();

このソリューションはスタック用の新しいオブジェクトを作成しますが、最初のオブジェクトは作成しないことに注意してください(ただし、Javaはオブジェクトの作成を最適化します。)

ドキュメントを見ると、どちらの方法でも次のアルゴリズムが使用されています。

(int)(this.longValue()^(this.longValue()>>>32))

これらはJavaライブラリを使用するため、適切なソリューションです-すでにテスト済みのものを活用する方が常に優れています。

85
TofuBeer

Guava を既に使用していない場合は少し些細なことですが、Guavaは これをあなたのために うまく行うことができます:

_public int hashCode() {
  return Longs.hashCode(id);
}
_

これにより、Long.valueOf(id).hashCode()と同等の機能が得られます。

_return (int) (value ^ (value >>> 32));
_

さらに、ハッシュコードの一部である他の値またはオブジェクトがある場合、次のように書くことができます。

_return Objects.hashCode(longValue, somethingElse, ...);
_

longLongに自動ボックス化されるため、全体のハッシュコードの一部として正しいハッシュコードを取得できます。

9
ColinD

hashCodeの目的を正しく理解しました。はい、均一な分布が望ましいです(ただし、実際の要件ではありません)。

((id >> 32) ^ id)

上記の式:

  • 元の値のすべてのビットを使用し、事前に情報を破棄しません。たとえば、IDの生成方法によっては、上位ビットがより頻繁に(またはその逆に)変更される可能性があります。
  • 2つの半分がOR(AND)演算で結合された場合のように、より多くの1(ゼロ)を持つ値へのバイアスを導入しません。
5
Grodriguez

Java 8は Long.hashCode(long) をJDKに追加します。

次のコードは、より高いパフォーマンスをもたらす可能性があります。このコードは、64ビットintで計算する代わりに、32ビットlongに計算を減らします。これにより、32ビット以下のアーキテクチャで違いが生じる可能性があります。 x86マシン上の32ビットプロセスは、2つのレジスタを単純にXORする単一の命令にこれを最適化できます。

return (int)(value ^ (value >>> 32));

他の回答で述べたように、これはnotに良い アバランシェ効果 を持っているため、衝突を引き起こす可能性があります。高いアバランシェ効果を確保するために、暗号化ハッシュ関数を使用できます。ただし、 Murmur Hash (more information )などの他のアルゴリズムがあり、これは非常に良好な雪崩効果を持ちますが、CPU時間をそれほど消費しません。

3
Nathan
int result = (int)((longVal >> 32) ^ longVal);

長い値の上位ビットのみが変更された場合、モジュロは異なる値を返さないため、より適切に分散されます。

1
codymanix

_(l >> 32) ^ l_は、ほとんどの場合に適切なハッシュコードです。特にlongが均一に分布している場合。

それは受け入れられた答えだったので、私はこれを投稿して、それが長い間良いハッシュコードではないときについての私のコメントのいくつかを明確にします。

私が与えた例は、次のようなPointクラスです。

_public class Point {
    private final long coords; //x in high-bits, y in low
    public int getX() {
        return (int)(coords >> 32);
    }
    public int getY() {
        return (int)coords;
    }
    public int hashCode() {
        return (int)((coords >> 32) ^ (coords));
    }
}
_

不自然に思えるかもしれませんが、複数の「フィールド」が長いフィールドに詰め込まれている場合があります。

したがって、coordsフィールドは32ビットのxと32ビットのyを表します。では、なぜこれが問題なのでしょうか?まあ、xとyのそれぞれがそれぞれの32ビットに均等に分散されているわけではありません。しかし、それは実際にはほとんどありません。より可能性が高いのは、XとYが何らかの数値で区切られていることです。 2 ^ 10なので1024としましょう。これは、各XおよびYの最大で下位10ビットが設定されることを意味します。

_00000000 00000000 000000XX XXXXXXXX 00000000 00000000 000000YY YYYYYYYY
_

2 ^ 20(1024 * 1024)の可能な組み合わせがあります。しかし、hashCodeは何をしているのでしょうか?

_  00000000 00000000 000000XX XXXXXXXX 
^ 00000000 00000000 000000YY YYYYYYYY
-------------------------------------
= 00000000 00000000 000000?? ????????
_

下位10ビットのみがゼロ以外の値になるため、最大2 ^ 10(1024)のhashCode値があります。ハッシュ値と実際の値の比率は、1024:(1024*1024)または_1:1024_です。そのため、すぐに2つの数字が同じハッシュを持つ確率は1/1024です。

誕生日の問題 から数学を適用して、衝突の確率を計算しましょう。 p(n)をn個の値で少なくとも1つの衝突が発生する確率とします。1024個の値しかないため、p(1025+)= 1であることがわかります。

_p(n) = 1 - (n! * (1024 choose n))/1024^n
_

これは次のようになります。

_n: p(n)
1: 0.00000
2: 0.00098
3: 0.00293
4: 0.00585
5: 0.00973
6: 0.01457
...
38: 0.50096
...
79: 0.95444
...
148: 0.99999
_

たった38個のアイテムで、おそらく衝突があります。 148個のアイテムでは、99.999%の確率で(少なくとも1回)衝突が発生します。 148個のアイテムでは、各アイテムが7%の確率で別のアイテムと衝突します。適切なハッシュ関数を使用して、ドメインの知識を取得すると、これらの数値は簡単に0になります。

言い換えると、ドメインと実際の状況を知ることがパフォーマンスハッシュを作成するための鍵となります。ライブラリ関数は、ドメインについて何も知らずに、できる限り良い仕事をしようとします。また、パフォーマンスを向上させるには、通常、実際には発生しないデータの分布に依存します。

1
Mark Peters