HashMapののJavaソースコードの瞑想で

質問

  1. HashMapの使用シナリオ
  2. HashMapの作品
  3. HashMapのJDK7の区別とJDK8を達成
  4. HashMapのは、スレッドセーフですか?あなたが不安のいずれかの問題がある場合は、スレッドセーフな解決策があるのですか?

回答

1. HashMapの使用シナリオ

プログラムは、例えば、データ辞書をキーと値のペアの数を格納する必要がある場合、グローバル変数およびパラメータの他のタイプのデータ構造で格納されたハッシュマップオブジェクトとして使用することができます。

2. HashMapの作品

キーはすぐJava8、8つ以上のリスト要素で、O(N)、衝突が、ノードをダウン見つけるノードダウンリストを発生した場合、時間計算量はリストの長さであり、配列インデックスにHashMapのハッシュコードを見つけることができヶ月後に、リストが自動的に赤黒木に変換され、時間の複雑さは、O(logN個)、改良された検索効率となります。

まず、いくつかのより多くの重要なメンバ変数にHashMapのを見てください。

/**
 * 默认数组长度16,长度保持2^n,可扩容,扩容后数组为原来的2倍。
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * 数组最大长度2^30
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

/**
 * 默认负载因子,0.75
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * 用于判断是否需要将链表转换为红黑树的阈值.
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * 存放元素的数组,长度保持2^n,可扩容
 */
transient Node<K,V>[] table;

/**
 * HashMap中键值对的数量.
 */
transient int size;

/**
 * threshold = capacity * load factor.超过该阈值需要进行扩容
 */
int threshold;

/**
 * 负载因子
 */
final float loadFactor;

パフォーマンスに影響を与える最も重要なパラメータの1つは2つです:

  1. キャパシティ(容量):ハッシュテーブルのバケットの数は、初期容量は、ハッシュテーブルを作成する能力です。
  2. 負荷係数(負荷率):負荷率が自動的に充填されたメトリックハッシュ・テーブルの前に容量を増加させることができます。

ハッシュテーブルのエントリ数が負荷係数と現在の容量の積を超える場合、ハッシュテーブルは、ハッシュを再される(すなわち、内部データ構造の再構成)、ハッシュテーブルバレルの約二倍の数となるように。

バレルノード、ハッシュ値が定義され、キー、値と次のノード

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

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

メソッドを置く、のが最も一般的な取得を見てみましょう:

添字方法を入れて、取得を計算するとき、ハッシュメソッドを使用する必要があります。

/*
 *先获取key的hashCode,另h = key.hashCode()
 *再h进行无符号右移16位
 *将两个结果异或得到最终的key的hash值,i = (n - 1) & hash
 *作为节点下标
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

/**
 * Implements Map.get and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @return the node, or null if none
 */
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //若桶不为空,先判断第一个节点是否要取的节点
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        //若冲突了,则取链表下一个节点,通过判断key是否相等
        if ((e = first.next) != null) {
            //判断是否为红黑树节点,时间复杂度O(logn)
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                //链表节点,时间复杂度O(n)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

取得方法は、以下のように一般的な考えがあり、比較的簡単です:

  1. アレイの最初のノードを分析し、そしてキーのヒットを返す場合、ハッシュ値が等しいです
  2. 矛盾がある場合は、リストは、ノード鍵を見つけると等しい戻り、Oの時間複雑さをハッシュするためにトラバースされる場合、ノードは、リンクされたリストかどうかを判断する(N)
  3. リストがノードである場合、ノードがあるか否かを判定する赤黒木、赤黒ツリートラバーサルキーとハッシュ、ノードを見つけることに等しい、Oの時間複雑度(logN個)
  4. 全体のハッシュテーブルのミスはnullが返された場合はトラバース

のは、putメソッドを見てみましょう:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

/**
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //判断当前桶是否为空,为空需进行初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //根据hash值判断当前节点是否为空(没有碰撞),为空新建一个节点,并将key,value传进去
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    //若与当前节点碰撞,通过链表方式存放数据
    else {
        Node<K,V> e; K k;
        //根据hash和key判断当前节点是否与要新增的key值相等,若是则返回该节点
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //判断当前节点是否为红黑树节点,若是则按照红黑树方式写入数据
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        //通过新增一个链表节点,写入数据
        else {
            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;
                }
                //key相同时退出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        //覆盖原有节点值
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

この方法は、一般的なアイデアを入れて、次のとおりです。

  1. 衝突が発生していない場合は、キー値、キーのハッシュ値に基づいてノードを検索し、新しいノードを作成します
  2. 衝突した場合、および方法により、リストは、バケットに格納されます
  3. リストはTREEIFY_THRESHOLD(デフォルト8)よりも長い変換を、いっぱいにされている場合は、赤黒木のリストを置きます
  4. キーがすでに存在する場合は、古い値を置き換えます
  5. 全体バレルが一杯になった場合、負荷率が閾値閾値負荷率を超える電流容量の*現在の容量、サイズ変更の拡張の必要性()

3. HashMapのJDK7とJDK8の違いを実感

主な違いは、アレイ+ JDK7 HashMapのリンクリストの実装を使用することであり、そしてJDK8 HashMapのクエリ効率を向上させる、配列+ +リスト赤黒木の実装を要します。

Java8 HashMapの構造

4. HashMapのスレッドの安全性

ハッシュマップは、スレッドセーフでないコードは、同時治療ではなく、データの矛盾をもたらすことができるマルチスレッドで動作しているとき。コレクションは、HashMapのスレッドセーフな機能の方法をsynchronizedMap、またはのConcurrentHashMapを使用することができます。その後のフォローアップのConcurrentHashMapに関する研究を。

おすすめ

転載: www.cnblogs.com/universal/p/11128264.html