web-dev-qa-db-ja.com

HashTablesは衝突をどのように処理しますか?

私の学位クラスでは、新しいキーエントリが別のキーエントリと衝突した場合、HashTableが「次の利用可能な」バケットに新しいエントリを配置すると聞いています。

コリジョンキーを使用してバックを呼び出したときにこのコリジョンが発生した場合、HashTableはどのようにして正しい値を返しますか?

私はKeysStringタイプであり、hashCode()がJavaによって生成されたデフォルトを返すと仮定しています。

独自のハッシュ関数を実装し、ルックアップテーブルの一部として使用する場合(つまり、HashMapまたはDictionary)、衝突に対処するための戦略はありますか?

素数に関するメモを見たことがあります! Google検索からそれほど明確ではない情報。

83
Alex

ハッシュテーブルは、2つの方法のいずれかで衝突を処理します。

オプション1:各バケットに、そのバケットにハッシュされる要素のリンクされたリストが含まれるようにします。これが、悪いハッシュ関数がハッシュテーブルの検索を非常に遅くする理由です。

オプション2:ハッシュテーブルエントリがすべていっぱいの場合、ハッシュテーブルは保有するバケットの数を増やしてから、テーブル内のすべての要素を再配布できます。ハッシュ関数は整数を返し、ハッシュテーブルはハッシュ関数の結果を取得し、バケットのサイズを取得できるようにテーブルのサイズに対して変更する必要があります。したがって、サイズを大きくすると、再ハッシュされてモジュロ計算が実行され、運がよければオブジェクトを異なるバケットに送信できます。

Javaは、ハッシュテーブルの実装でオプション1と2の両方を使用します。

86
ams

「新しいキーエントリが別のエントリと衝突した場合、ハッシュテーブルは「次に利用可能な」バケットに新しいエントリを配置します。」について話しているとき、ハッシュテーブルの衝突解決のアドレス指定戦略について話している。


ハッシュテーブルには、衝突を解決するためのいくつかの戦略があります。

第1の大きな方法では、キー(またはそれらへのポインター)を、関連する値とともにテーブルに保存する必要があります。

  • 別の連鎖

enter image description here

  • アドレス指定

enter image description here

  • 合体ハッシュ
  • カッコウハッシュ
  • ロビンフッドハッシュ
  • 2択ハッシュ
  • 石けり遊びハッシング

衝突を処理するもう1つの重要な方法は、動的サイズ変更によるもので、さらにいくつかの方法があります。

  • すべてのエントリのコピーによるサイズ変更
  • 増分サイズ変更
  • 単調なキー

EDIT:上記は wiki_hash_table から借用しています。詳細については、こちらをご覧ください。

67
herohuyongtao

衝突を処理するために利用できる複数のテクニックがあります。それらのいくつかを説明します

連鎖:連鎖では、配列インデックスを使用して値を保存します。 2番目の値のハッシュコードも同じインデックスを指す場合、そのインデックス値をリンクリストに置き換え、そのインデックスを指すすべての値はリンクリストに保存され、実際の配列インデックスはリンクリストの先頭を指します。ただし、配列のインデックスを指すハッシュコードが1つだけの場合、値はそのインデックスに直接格納されます。値を取得するときに同じロジックが適用されます。これは、衝突を避けるためにJava HashMap/Hashtableで使用されます。

線形探査:この手法は、格納する値よりも多くのインデックスがテーブルにある場合に使用されます。線形プローブ技術は、空のスロットが見つかるまで増分を続けるという概念に基づいています。擬似コードは次のようになります。

index = h(k) 

while( val(index) is occupied) 

index = (index+1) mod n

ダブルハッシュ手法:この手法では、2つのハッシュ関数h1(k)とh2(k)を使用します。 h1(k)のスロットが占有されている場合、2番目のハッシュ関数h2(k)がインデックスのインクリメントに使用されます。擬似コードは次のようになります。

index = h1(k)

while( val(index) is occupied)

index = (index + h2(k)) mod n

リニアプローブおよびダブルハッシュテクニックは、オープンアドレッシングテクニックの一部であり、使用可能なスロットが追加するアイテムの数を超える場合にのみ使用できます。ここでは余分な構造が使用されていないため、チェーンよりもメモリが少なくて済みますが、空のスロットが見つかるまで多くの動きが発生するため低速です。また、アイテムがスロットから削除されるオープンアドレッシングテクニックでは、ここからアイテムが削除されることを示すために墓石を置きます。

詳細については、 このサイト を参照してください。

19
Jatinder Pal

最近HackerNewsに掲載されたこのブログ投稿を読むことを強くお勧めします。 JavaでのHashMapの仕組み

要するに、答えは

2つの異なるHashMapキーオブジェクトに同じハッシュコードがある場合はどうなりますか?

それらは同じバケットに保存されますが、リンクリストの次のノードは保存されません。そして、keys equals()メソッドは、HashMapで正しいキーと値のペアを識別するために使用されます。

16
zengr

私の学位クラスでは、新しいキーエントリが別のキーエントリと衝突した場合、HashTableが新しいエントリを「次に利用可能な」バケットに配置すると聞いています。

これは実際には少なくともOracle JDKには当てはまりません(それはisAPIの実装ごとに異なる可能性のある実装の詳細です)。代わりに、各バケットには、Java 8より前のエントリのリンクリストと、Java 8以上のバランスツリーが含まれます。

次に、衝突キーで1つを呼び出すときにこの衝突が発生した場合、HashTableは正しい値をどのように返しますか?

equals()を使用して、実際に一致するエントリを見つけます。

独自のハッシュ関数を実装し、ルックアップテーブル(つまり、HashMapまたは辞書)の一部として使用する場合、衝突に対処するための戦略は何ですか?

長所と短所が異なるさまざまな衝突処理戦略があります。 ハッシュテーブルに関するウィキペディアのエントリ で概要がわかります。

7

Java 8以降の更新:Java 8は、衝突処理に自己均衡ツリーを使用し、 O(n)からO(log n)までのルックアップのワーストケース。自己バランスツリーの使用は、リンクリストを使用し、最悪のケースを持つチェーン(Java 7まで使用)の改善としてJava 8で導入されました。 O(n)ルックアップ用(リストを横断する必要があるため)

質問の2番目の部分に回答するには、特定の要素をハッシュマップの基になる配列内の特定のインデックスにマッピングすることによって挿入が行われますが、衝突が発生した場合、すべての要素を保持する必要があります(セカンダリデータ構造に格納されます) 、基礎となる配列内で単に置き換えられるだけではありません)。これは通常、各配列コンポーネント(スロット)をセカンダリデータ構造(バケットとも呼ばれる)にすることで行われ、要素は特定の配列インデックスにあるバケットに追加されます(キーがバケットにまだ存在しない場合、どちらの場合も置き換えられます)。

ルックアップ中に、キーは対応するarray-indexにハッシュされ、指定されたバケット内の(正確な)キーに一致する要素に対して検索が実行されます。バケットは衝突を処理する必要がないため(キーを直接比較します)、衝突の問題は解決しますが、セカンダリデータ構造で挿入と検索を実行する必要があります。重要な点は、ハッシュマップではキーと値の両方が保存されるため、ハッシュが衝突した場合でも、キーが等しいかどうか(バケット内)が直接比較されるため、バケット内で一意に識別できることです。

衝突処理は、衝突処理が行われない場合のO(1)から挿入およびルックアップの最悪の場合のパフォーマンスを連鎖のためにO(n)にもたらします(リンクリストは、二次データ構造として使用されます)、O(log n)は自己均衡ツリーに使用されます。

参照:

Java 8には、衝突が激しい場合のHashMapオブジェクトの以下の改善/変更が含まれています。

  • Java 7で追加された代替の文字列ハッシュ関数は削除されました。

  • 多数の衝突キーを含むバケットは、特定のしきい値に達すると、リンクリストではなくバランスの取れたツリーにエントリを保存します。

上記の変更により、最悪のシナリオでO(log(n))のパフォーマンスが保証されます( https://www.nagarro.com/en/blog/post/24/performance-improvement-for -hashmap-in-Java-8

5
Daniel Valland

JavaのHashMapが(Sun/Oracle/OpenJDK実装で)使用しているアルゴリズムについて混乱があるため、関連するソースコードスニペット(OpenJDK、1.6.0_20、Ubuntuから):

/**
 * Returns the entry associated with the specified key in the
 * HashMap.  Returns null if the HashMap contains no mapping
 * for the key.
 */
final Entry<K,V> getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

このメソッド(引用は355〜371行目)は、テーブルのエントリを検索するときに呼び出されます。たとえば、get()containsKey()などです。ここでのforループは、エントリオブジェクトによって形成されたリンクリストを通過します。

ここで、エントリオブジェクトのコード(行691-705 + 759):

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

  // (methods left away, they are straight-forward implementations of Map.Entry)

}

この直後にaddEntry()メソッドがあります:

/**
 * Adds a new entry with the specified key, value and hash code to
 * the specified bucket.  It is the responsibility of this
 * method to resize the table if appropriate.
 *
 * Subclass overrides this to alter the behavior of put method.
 */
void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

これにより、バケットの前面に新しいエントリが追加され、古いfirstエントリ(または、該当するエントリがない場合はnull)へのリンクが追加されます。同様に、removeEntryForKey()メソッドはリストを調べて、1つのエントリのみを削除し、残りのリストはそのままにします。

だから、ここに各バケットのリンクされたエントリのリストがあり、これは_20から_22に変更されたことを非常に疑っています。

(このコードは(c)1997-2007 Sun Microsystemsであり、GPLで利用可能ですが、コピーするには、Sun/Oracleの各JDKのsrc.ZipおよびOpenJDKに含まれる元のファイルを使用してください。)

3
Paŭlo Ebermann

Equalsメソッドを使用して、キーが均等に存在するかどうか、特に同じバケットに複数の要素があるかどうかを確認します。

衝突解決にはさまざまな方法があります。そのうちのいくつかは、セパレートチェーン、オープンアドレッシング、ロビンフッドハッシュ、カッコウハッシュなどです。

Javaは、ハッシュテーブルでの衝突を解決するために個別のチェーンを使用します。これがどのように発生するかについての素晴らしいリンクを次に示します。 http://javapapers.com/core-Java/java-hashtable/

これは、Javaでの非常に単純なハッシュテーブルの実装です。ではput()get()のみを実装しますが、好きなものを簡単に追加できます。すべてのオブジェクトによって実装されるJavaのhashCode()メソッドに依存しています。独自のインターフェイスを簡単に作成できますが、

interface Hashable {
  int getHash();
}

必要に応じて、キーによって強制的に実装されます。

public class Hashtable<K, V> {
    private static class Entry<K,V> {
        private final K key;
        private final V val;

        Entry(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }

    private static int BUCKET_COUNT = 13;

    @SuppressWarnings("unchecked")
    private List<Entry>[] buckets = new List[BUCKET_COUNT];

    public Hashtable() {
        for (int i = 0, l = buckets.length; i < l; i++) {
            buckets[i] = new ArrayList<Entry<K,V>>();
        }
    }

    public V get(K key) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        for (Entry e: entries) {
            if (e.key.equals(key)) {
                return e.val;
            }
        }
        return null;
    }

    public void put(K key, V val) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        entries.add(new Entry<K,V>(key, val));
    }
}
1