HashMapの基本原理とインタビューの回答

 

機能:追加、削除、チェックの時間の複雑さはすべてO(1)です。

ストレージKEYはNullおよび一意にすることができ、値はNULLにすることができます。容量拡張(初期サイズは16(2の累乗)、負荷係数は0.75、容量拡張は元のサイズの2倍です)。

概念:妨害関数(ハッシュ関数):ハッシュ関数を使用して、不適切なhashCode()の使用を回避し、ハッシュの衝突の発生を減らします。

 

JDK1.7

データ構造:配列とリンクリスト(一方向);スレッドの安全ではない、マルチスレッドの無限ループが発生します。

配列とリンクリストの構造:HashMapの内部エントリで構成される配列;配列の添え字の計算:自身で実装されたハッシュ()アルゴリズム、およびXOR演算を実行し、対応する配列の添え字の位置を取得するための係数を取得;ハッシュアルゴリズムの記述利点は、ハッシュ衝突の発生が減少することです。

コードは次のとおりです。キーのhashCode値、9回の摂動処理= 4回のビット演算+ 5回のXOR。

 

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

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

 リンクリストはヘッド挿入方式を採用しています。マルチスレッド同時実行の場合、展開プロセス中に、transfer()メソッドは古いリンクリストをループしてから、新しいリストヘッダーを再度ポイントします。1つのスレッドが1つのスレッドを中断して実行を続行し、最終的に2つのスレッドになりますデータポイントを相互に挿入して、循環リンクリストを形成します。配列内のデータを取得すると、無限ループが発生します。

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
        // 如多线程执行时,某一线程执行到该步骤挂起,则会导致上述所说的环形链表的产生。
            Entry<K,V> next = e.next; 
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

頭部補間法によるデッドループに加えて、リンクリスト構造には重大な問題があります。ハッシュの衝突が多数発生すると、多数のノードがエントリに格納されます。一方向リンクリストにより、データ検索(シーケンシャル検索)が検索に大きく影響します。効率。

 

JDK1.8

 

データ構造:配列、リンクリスト、赤黒木。スレッドは安全ではなく、末尾の補間は無限ループにはなりません。

キーのhashCode値、2摂動処理= 1ビット演算+ 1 XOR。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

リンクリストはテール補間を使用します。これは無限ループを引き起こしませんが、マルチスレッドの場合、データの損失を引き起こす可能性があります。

リンクリストを赤黒木に変換するためのしきい値は8です。

/**
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2 and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 */
static final int UNTREEIFY_THRESHOLD = 6;

...

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
  ...
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash); //转化为红黑树
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        ...
}

 問題は、7未満の場合はリンクリストに変換され、7を超える場合は赤黒ツリーに変換される理由です。

まず、赤黒木は必ずしもリンクリストよりも効率的ではありません。ノード数が多い場合は、赤黒ツリーの方が効率的です。6と8を選択します(リンクリストが6本以下の場合は、リンクリストに復元し、8以上でツリーに復元します)。リンクリストとツリーの頻繁な変換を効果的に防止するために、中央に7の違いがあります。ハッシュバケットに分散されるコンテナー内のノードの頻度はポアソン分布に従い、バケットの長さが8を超える確率は非常に小さくなります。

 

 

 

 

 

 

 

元の記事を27件公開 賞賛された0 9932を訪問

おすすめ

転載: blog.csdn.net/weixin_38246518/article/details/105519206