HashMap source Interpretation - sentence by sentence analysis to achieve get and put methods

I. Introduction

  Recent research in HashMapthe source code, after a few days of research, I HashMapunderlying implementation have a clearer understanding. Today to write a blog, I read about the band HashMap, the most important of the two methods - getand putthe code. (Note: The following code is based JDK1.8)

  If you want to understand the source of these two methods, first of all to HashMapthe underlying structure have a clear understanding, if not clear, you can see before I wrote a blog, this blog on the HashMapunderlying structure and implementation We had a more clear and comprehensive explanations, while the bottom blog attach the two architects of Ali HashMapanalysis, very well written, a good reference:


Second, resolve

 2.1 get the source code reading method

  getAction approach is that we need to get the incoming node of key, then this node valueis returned. First to affix getthe method code:

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

  We can see, getthe code method is very simple, because specific code is encapsulated in the getNodeinside of this method, getthe method but it has been called. getNodeThe method takes two parameters, the first parameter is keya hashvalue of the second parameter is keyitself. Here we take a look at getNodethe source code for the method (by commenting on the source code has been stepping interpretation):

/**
 * 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;
}

  In the HashMap, containsKey Method relies getNode implemented method:

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


 2.2 put the source code interpretation method

  After reading the getsource code for the method, let us look at putmethods. putThe method of action is to a pair of key-valueinserted HashMap, if HashMapalready present in this key, with the new valuereplace the old value.

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

  It can be seen putmethods than geteven brief methods, and getmethods, he also will implement into another method, this method is called putVal. Let's take a look at the signature of this approach:

/**
 * 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) 

 This method takes five parameters:

  1. the hash : the element to be inserted, it key's hashvalue;
  2. Key : to be inserted element keyvalue;
  3. value : to be inserted element valuevalue;
  4. onlyIfAbsent : an identifier, when its value is false, the query is repeated if the keyvalue, then a new valuereplace the original value; otherwise not replaced, leaving the old value;
  5. The evict : In HashMapthe subclass meaningless in LinkedHashMapuse;

  In addition to the above five parameters, in putValthe member variable is also used in addition, we must first clarify their meaning:

  1. TREEIFY_THRESHOLD : converting the list of red-black tree to a threshold value; in HashMap, when a number of nodes in the chain a greater than equal TREEIFY_THRESHOLD, then converts it into a red-black tree from the list, the default value 8;
  2. modCount : Record HashMapnumber of modified, modified here refers only to insert and delete; the role of this variable is for the safety of using an iterator: iterator When you create, will record this value, if an iterator use of the process in , modCountthe values do not match the iterator recorded, said after the iterator is created, the number of elements in the collection has changed, this time the iterator is no longer safe, then will throw an exception when re-use this iterator;
  3. size : HashMapin the number of nodes;
  4. threshold : HashMapin the current maximum number of nodes permitted in when it reaches this number HashMapwill expansion;

  Well, let's take a look at putValthe source code for the method:

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;
}


Third, the summary

  In order to better understand the above code I have had a very detailed notes, and I hope people read this blog can help. Because I do not know much about the red-black tree, so the two methods above, with regard to that part of the operation of the tree I did not explore in depth, after time, I would go to a special look red-black tree, which is in the collection of comparative in a data structure used in Comparative more.


Fourth, the reference

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

Guess you like

Origin www.cnblogs.com/tuyang1129/p/12364898.html