web-dev-qa-db-ja.com

テキスト文字列用のJavaの優れた64ビットハッシュ関数は何ですか?

私は次のハッシュ関数を探しています:

  1. ハッシュテキスト文字列よく(例:衝突が少ない)
  2. Javaで書かれており、広く使用されています
  3. ボーナス:複数のフィールドで動作します(それらを連結して、連結された文字列にハッシュを適用する代わりに)
  4. ボーナス:128ビットのバリアントがあります。
  5. ボーナス:CPUに負荷はかかりません。
55
ripper234

デフォルトのString.hashCode()longバリアントを使用しないのはなぜですか(本当に賢い人は確かにそれを効率化することに努力を注いでいます-これをすでに見た何千もの開発者の目は言うまでもありません)コード)?

// adapted from String.hashCode()
public static long hash(String string) {
  long h = 1125899906842597L; // prime
  int len = string.length();

  for (int i = 0; i < len; i++) {
    h = 31*h + string.charAt(i);
  }
  return h;
}

さらに多くのビットを探している場合は、おそらくBigIntegerを使用できます 編集:

@brianeggeの答えに対するコメントで述べたように、32ビットを超えるハッシュのユースケースはあまりなく、64ビットを超えるハッシュのユースケースはほとんどありません。

おそらく数百億のマッピングを保存する数十のサーバーに分散した巨大なハッシュテーブルを想像できます。このようなシナリオの場合、@ brianeggeにはまだ有効なポイントがあります。32ビットでは2 ^ 32(約43億)の異なるハッシュキーを使用できます。強力なアルゴリズムを想定しても、衝突はほとんどありません。 64ビット(18,446,744,073億個の異なるキー)を使用すると、必要なクレイジーなシナリオに関係なく、確実に節約できます。ただし、128ビットキー(340,282,366,920,938,463,463,374,607,431億キー)のユースケースを考えることはほとんど不可能です。

複数のフィールドのハッシュを結合するには、単に xORを行う 1つを素数で乗算し、それらを追加します。

long hash = MyHash.hash(string1) * 31 + MyHash.hash(string2);

切り替えられた値のハッシュコードが等しくなるのを避けるために小さな素数があります。つまり、{'foo'、 'bar'}と{'bar'、 'foo'}は等しくないため、異なるハッシュコードを使用する必要があります。 XORは、両方の値が等しい場合に0を返すため不良です。したがって、{'foo'、 'foo'}と{'bar'、 'bar'}は同じハッシュコードになります。

64
sfussenegger

SHA-1ハッシュを作成 してから、最低64ビットをマスクします。

4
Aaron Digulla
long hash = string.hashCode();

はい、上位32ビットは0になりますが、ハッシュ衝突の問題が発生する前にハードウェアリソースが不足する可能性があります。 StringのhashCodeは非常に効率的で、十分にテストされています。

更新上記はおそらく動作する可能性のある最も単純なものを満たしていると思いますが、@ sfusseneggerの拡張アイデアに同意既存のString hashCode。

Stringに適切なhashCodeを設定することに加えて、実装でハッシュコードを再ハッシュすることを検討することもできます。ストレージが他の開発者によって使用されている場合、または他のタイプで使用されている場合、これはキーの配布に役立ちます。たとえば、JavaのHashMapは2の累乗の長さのハッシュテーブルに基づいているため、この関数を追加して下位ビットが十分に分散されるようにします。

    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
3
brianegge

CRC64多項式を使用しない理由。これらは合理的に効率的であり、すべてのビットが確実にカウントされ、結果空間に広がるように最適化されています。

「CRC64 Java」をグーグルで検索すると、ネット上で多くの実装を利用できます。

2
Peter Tillemans

文字列を逆にして別の32ビットハッシュコードを取得し、2つを結合します。

_String s = "astring";
long upper = ( (long) s.hashCode() ) << 32;
long lower = ( (long) s.reverse().hashCode() ) - ( (long) Integer.MIN_VALUE );
long hash64 = upper + lower;
_

これは擬似コードです。 String.reverse()メソッドは存在しないため、他の方法で実装する必要があります。

1
user2020240

今日(2018)の答え。 SipHash。

ここでのほとんどの回答よりもはるかに高速で、すべての回答よりもはるかに高い品質になります。

Guavaライブラリには次の1つがあります。 https://google.github.io/guava/releases/23.0/api/docs/com/google/common/hash/Hashing.html#sipHash24--

1
Scott Carey

このようなことをしてください:

_import Java.io.ByteArrayOutputStream;
import Java.io.DataOutputStream;
import Java.io.IOException;
import Java.math.BigInteger;
import Java.security.MessageDigest;
import Java.security.NoSuchAlgorithmException;

public class Test {

    public static void main(String[] args) throws NoSuchAlgorithmException,
            IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);

        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            SomeObject testObject = new SomeObject();

            dos.writeInt(testObject.count);
            dos.writeLong(testObject.product);
            dos.writeDouble(testObject.stdDev);
            dos.writeUTF(testObject.name);
            dos.writeChar(testObject.delimiter);
            dos.flush();

            byte[] hashBytes = md.digest(baos.toByteArray());
            BigInteger testObjectHash = new BigInteger(hashBytes);

            System.out.println("Hash " + testObjectHash);
        } finally {
            dos.close();
        }
    }

    private static class SomeObject {
        private int count = 200;
        private long product = 1235134123l;
        private double stdDev = 12343521.456d;
        private String name = "Test Name";
        private char delimiter = '\n';
    }
}
_

DataOutputStream を使用すると、プリミティブと文字列を記述し、それらをバイトとして出力できます。 ByteArrayOutputStream をラップすると、バイト配列に書き込むことができます。これは MessageDigest とうまく統合されます。リストされている任意のアルゴリズムから選択できます here

最後に BigInteger を使用すると、出力バイトを使いやすい数値に変換できます。 MD5アルゴリズムとSHA1アルゴリズムはどちらも128ビットのハッシュを生成するため、64が必要な場合は切り捨てることができます。

SHA1はほとんどすべてをうまくハッシュする必要があり、まれに衝突が発生します(128ビット)。これはJavaから機能しますが、どのように実装されているのかわかりません。実際にはかなり速いかもしれません。それは私の実装のいくつかのフィールドで動作します:それらをすべてDataOutputStreamにプッシュするだけでいいのです。リフレクションとアノテーションを使用してそれを行うこともできます(どのフィールドがハッシュに入れられるか、どの順序で表示されるかを示す@HashComponent(order=1))。 128ビットのバリアントがあり、思ったほど多くのCPUを使用していないことがわかると思います。

このようなコードを使用して、巨大なデータセット(おそらく数十億のオブジェクト)のハッシュを取得し、多くのバックエンドストアでそれらを分割できるようにしました。あなたがそれを必要とするものは何でも動作するはずです。 MessageDigest.getInstance()を一度だけ呼び出し、それからclone()を呼び出したいと思うかもしれないことに注意してください:IIRCクローン作成ははるかに高速です。

1
jasonmp85

Apache commons lang を見ていますか?

しかし、64ビット(および128)の場合、いくつかのトリックが必要です。JoshuaBloch著のEffective Javaの本に記載されているルールは、64ビットハッシュを簡単に作成するのに役立ちます(intではなくlongを使用してください)。 128ビットの場合、追加のハックが必要です...

0
St.Shadow