HashMapのソースの解釈 - 文解析によって文がgetおよびputメソッドを達成するために

I.はじめに

  最近の研究HashMapのソースコードは、研究の数日後、私はHashMap基本的な実装は、より明確に理解しています。今日はブログを書くために、私はバンドについて読むHashMap- 、二つの方法の中で最も重要getputコード。(注:次のコードが基づいていますJDK1.8

  あなたが最初にすべての、これらの2つの方法のソースを理解したい場合はHashMap、基礎となる構造を明確に理解を持って、私はブログを書いた前に、明確でない場合は、上で、このブログを見ることができるHashMap基本的な構造と実装私たちは、より明確かつ包括的な説明を持っていた、下のブログつつ、アリの2人の建築家添付しHashMap、非常によく書かれた、優れたリファレンスを分析します:


第二に、決意

 2.1ソースコードの読み取り方法を取得

  getアクションのアプローチは、私たちが入ってくるのノードを取得する必要があるということですkey、そして、このノードがvalue返されます。固定する最初のgetメソッドのコードを:

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

  私たちは、見ることができget、特定のコードが中にカプセル化されているため、コードの方法は非常に簡単で、getNodeこのメソッドの内部に、get方法をそれが呼ばれてきました。getNodeこの方法は、2つのパラメータを取り、最初のパラメータである第二のパラメータの値は、それ自体。ここでは、見てとる(ソースコードにコメントによって解釈をステッピングされています)メソッドのソースコードを:keyhashkeygetNode

/**
 * Implements Map.get and related methods
 *
 * @param hash key到hash值
 * @param key key值
 * @return the node, or null if none
 */
final HashMap.Node<K,V> getNode(int hash, Object key) {
    HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;

    // 以下if语句中判断三个条件:
    //   1、HashMap中存储数据的数组table不为null;
    //   2、数组table不为null,且长度大于0;
    //   3、table已经创建,且通过hash值计算出的节点存放位置有节点存在;
    // 若上面三个条件都满足,才表示HashMap中可能有我们需要获取的元素
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {

        // 定位到元素在数组中的位置后,我们开始沿着这个位置的链表或者树开始遍历寻找
        // 注:JDK1.8之前,HashMap的实现是数组+链表,到1.8开始变成数组+链表+红黑树

        // 首先判断这个位置的第一个节点的key值是否与参数的key值相等,
        // 若相等,则这个节点就是我们要找的节点,将其返回
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 若上面的不满足,则判断第一个节点是否有下一个节点
        // 若有,继续判断;若没有,那表示我们要找的节点不存在
        if ((e = first.next) != null) {
            // 若第一个节点是应该树节点,则通过红黑树的查找算法进行查找
            if (first instanceof HashMap.TreeNode)
                return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
            // 若不是一个树节点,表示当前位置是一个链表,则使用do...while循环遍历查找
            do {
                // 若查找到某个节点的key值与参数的key值相等,则表示它就是我们要找的节点,将其返回
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    // 若没有找到对应的节点,返回null
    return null;
}

  ハッシュマップでは、のcontainsKeyメソッドgetNodeメソッドを実装依存しています。

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}


 2.2ソースコードの解釈方法を置きます

  読んだ後にgetメソッドのソースコードを、私たちは見てみましょうput方法。putアクションの方法は、対にあるkey-value挿入HashMap場合は、HashMapこの中にすでに存在するkey新しいと、value古いものを交換してくださいvalue

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

  それを見ることができるputよりも方法getも、簡単な方法、およびget方法を、彼はまた、このメソッドが呼び出され、別のメソッドに実装しますputValのは、このアプローチの署名を見てみましょう:

/**
 * Implements Map.put and related methods
 *
 * @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) 

 この方法では、5つのパラメータを取ります。

  1. ハッシュ:要素が挿入される、それkeyhash値。
  2. キー:要素の挿入されるkey値を、
  3. :要素の挿入されるvalue値を、
  4. onlyIfAbsent:その値がされた識別子、false場合、クエリが繰り返されるkey値は、新しいがvalue、元を置き換えvalue、そうでない場合は交換していない、古い値を残して。
  5. 追い出し:中HashMapでのサブクラスの無意味なLinkedHashMap使用。

  上記5つのパラメータに加えて、putValメンバ変数も追加して使用され、我々は最初にその意味を明確にする必要があります。

  1. TREEIFY_THRESHOLD:閾値と赤黒木のリストを変換するステップとでHashMap、チェーン内のノードの数に等しいより大きいが場合TREEIFY_THRESHOLD、その後、リストから赤黒木にデフォルト値を変換します8
  2. modCount:レコードHashMapここに変性された変性の数は、唯一の挿入、削除を指す。この変数の役割は、イテレータを使用しての安全のためです:イテレータ作成し、中のプロセスのイテレータを使用する場合、この値を記録します、modCount値は、反復子が記録と一致しない反復子が作成された後、コレクション内の要素の数は、イテレータがこのイテレータを再使用する場合、例外がスローされます、もはや安全で、この時間を変更していると述べました。
  3. サイズHashMapノードの数で、
  4. 閾値HashMapノードの現在の最大数はこの数に達したときに許容HashMap意志拡大を、

  さて、見てみましょうputValメソッドのソースコードを:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;

    // 判断当前存储数据的数组是否为null,或者大小为0,若是,则调用resize方法初始化数组
    // resize方法用来初始化HashMap中存储数据的table数组,或者给table扩容(即*2)
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;

    // 判断新值将要插入的位置是否为null,若为null,则用传入的值创建一个新的节点,并放入到这个位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 若新值将要放入的位置已经存在节点了,则进一步判断
    else {
        // 若已经存在一个节点,它的key与新值的key相等,则用变量e记录这个节点
        // e的作用就是干这个的,下面很长一段代码都是用来判断是否存在这样一个节点
        HashMap.Node<K,V> e; K k;
        // 若新值将要插入的位置已经存在的节点,它的key值与新值的key相等,
        // 则用变量e记录下它
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 若已经存在的节点是一个Tree节点,则使用树的方法将节点加入
        // 用e接收返回值,此处返回值e不为空,表示这棵树上存在与新值的key相同的节点
        else if (p instanceof HashMap.TreeNode)
            e = ((HashMap.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值与新值的key相等,则不插入新节点
                // 且此时由于上面的操作,e已经指向了这个key重复的节点,不需要继续遍历了,跳出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                // 这一步赋值没看懂意义何在
                p = e;
            }
        }

        // 判断e是否为null,若不为空,表示在原来的节点中,存在一个key值与新值的key重复的节点
        if (e != null) { // existing mapping for key
            // 记录下这个节点原来的value值
            V oldValue = e.value;
            // 若onlyIfAbsent的值为false,或者原来的value是null,则用新值替换原来的值
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // 这是一个回调函数,但是在HashMap中是一个空函数,
            // 看源码貌似是留给LinkedHashMap去扩充的,
            // 感觉这个应该属于模板方法设计模式
            afterNodeAccess(e);
            // 返回旧value,如果在这里被返回,则不会执行剩下的代码
            // 也就是说,若执行到剩下的代码,表示并不是执行修改原有值的操作,而是插入了新节点
            return oldValue;
        }
    }
    // 能运行到这里,表示这次进行的是插入操作,而不是修改
    // modCount用来记录Map(仅指插入+删除)被修改的次数
    // 此处modCount+1,因为HashMap被修改了(新插入了一个节点)
    ++modCount;
    // Map中元素的数量+1,并判断元素数量是否到达允许的最大值,若到达,则对Map进行扩容
    if (++size > threshold)
        resize();
    // 与上面的afterNodeAccess类似,同为留给LinkedHashMap编写的回调函数
    afterNodeInsertion(evict);
    // 若插入一个新节点,则返回null
    return null;
}


第三に、要約

  より良い上記のコードを理解するために、私は非常に詳細なメモを持っていた、と私は人々がこのブログ缶のヘルプを読んで願っています。比較の集まりである、私は深く探求していないツリーの動作のその部分に関しては、二つの方法上記のように、私は、ずっと赤黒木について知らないので、時間が経過した後、私は特別な外観に行くと赤黒木、以上の比較に使用されるデータ構造です。


第四に、参照

https://blog.csdn.net/qq_35321596/article/details/81117669
https://blog.csdn.net/AJ1101/article/details/79413939

おすすめ

転載: www.cnblogs.com/tuyang1129/p/12364898.html