android HashMap源码添加数据的全过程解析(三)

前面讲了hashmap添加2个数据的过程,添加第一个时,数据都是空的,在putVal()方法中就会执行第一个判断:

 if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

会执行resize()初始化了成员变量table,它的长度是threshold=16;继续往下走,会触发到第二个判断:

 if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

因为n是tab的size,即n=16,然后让key的hashcode和16-1=15做与运算(两者都为1则为1,否则为0)然后得到一个小于等于15的值,即下标,这里的判断的意思可以理解为:根据传进来key的hashcode,和table长度-1做与运算,得到下标,如果tab[下标]为空,则直接调用newNode()方法新建一个链表;然后判断size++和threshold的大小,如果小于则不执行resize(),调用afterNodeInsertion();

到这里,添加数据的过程就结束;但是我们会发现,putVal()方法里还有一个判断else判断(代码内容我用3个点表示):

    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;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            ...
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

这是啥意思呢?

解释:当tab[下标]里面的值为空,我们就执行newNode(),不为空则执行else。当tab[下标]里面的值 不!为!空!,说明里面已经有对象了,那此时,我们需要将当前的值怎么存到链表里面呢?如下:

        else {
            Node<K,V> e; K k;
            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;
                    }
                    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;
            }
        }

第一个判断:

            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

这里,我们把需要的变量梳理一下,如下:

前面分析hashmap的第一篇新建的变量是:tab[11]=newNode(hash, key, value, null);hash=115864843,key="zhang",value=" ",

第二篇新建的变量为:tab[13]=newNode(3261853,"jian","",null),即:

tab=table=[ null, null, null, null, null, null, null, null, null, null, 11, null, 13 , null, null],

p=tab[下标],我们这里暂时把下标=11,即:p=tab[ 11 ],k=p.key;

下标相等,要么它们是来自于同一个对象key="zhang",就相当于场景:

params.put("zhang","jian")
params.put("zhang","weijian")

要么就是一个新的对象,它的下标计算后,正好等于11,此时,来自于同一个对象,正好就是这个判断:

     if (p.hash == hash &&
           ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

这里做了一下赋值e=p;

其他的判断就不会走了,直接到:

            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

也就是说,当我们params,put了2次之后,那么它的值,一定是等于第二次的,即:

params.put("zhang","jian")
params.put("zhang","weijian")


String value = params.get("zhang")

value打印一定是:"weijian";

然后看

         else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

即:当hash不相等,或者key的 内容不同时,如果p是一个TreeNode对象的话,

static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }

        ......
}

就赋值,我们这里才只有一个Node,肯定不是TreeNode;

继续看下面的else:

            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;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

这里是for循环,将p.next赋值给了e,因为p.next是等于null的,所以会执行:

                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

p.next=newNode(hash,key,value,null);这里就是走了newNode方法,即,又创建了一个节点,赋值给了p.next,继续往下看:

                   if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;

我们看了一下:

    static final int TREEIFY_THRESHOLD = 8;

当binCount >= 8-1时,就是说,这个for循环执行了7次,还在执行,则就执行treeifyBin()方法,应该是红黑树了,我们这才第一遍,所以不会执行,然后到break;

到这里我们知道了,当执行一下put时,添加的key计算后的下标,在table中有值,并且它们的key不是同一个时,下标中刚好只有一个节点,那么新的节点直接是上面节点的next,即原来的对象的next,正好是等于新的节点;

那么这里,我们扩展一下,如果下标中,刚好有2个节点,那么for循环的第一遍,就不走newNote,然后走break,

那么继续往下:

                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                    p = e;

这里,我们添加的是一个新内容,所以,不是同一个key,不会break,会执行p=e;

因为这里开头,e=p.next(),现在有将e赋值给p,就相当于p=p.next了,到这里,循环从头开始,我们先判断一下for的条件,因为我们就2个节点,所以到第二次循环的时候,p.next()就是null了,那么就会直接新建一个nowNote(),赋值到p的next;至此,添加的工作就完成了;

所以回到我们刚开始讨论的:

  • 当put时,计算完节点后,节点处的值不为空,先遍历node节点到最后的next处,即node.next=null,这时会执行newNode()创建节点,赋值到node.next;

  • 当 该node节点有8个数时,那么会进入红黑树的条件判断(这个我们下一节再分析)

  • 当同一个key,put多次之后,那么它的key一定是最后一次put的value,

到目前为止,还未讲到hashmap的动态扩容、红黑树的知识点;再下几节,我们将继续分析

猜你喜欢

转载自blog.csdn.net/jian11058/article/details/123053169
今日推荐