HashMap源码解析二

上篇文章由于篇幅有限,只讲了put方法以及扩容resize()方法。下面接着讲get以及remove方法。上文的链接:http://blog.csdn.net/zhujiangtaotaise/article/details/79046604

我们还是用上次的数据,即有3个数据,3个数据的key分别为 m,n,t,对应的hash值分别为5,6,9。如下图:
《HashMap源码解析二》

一 、get方法 :

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

getNode方法如下:

 final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //如果tab不为null且tab中有数据且tab相应索引的位置上的数据不为null
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
            //如果key的hash值相等且key也相等,那么直接返回这个相应的值
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //如果上面的条件不满足且first的next指向的数据不为null
            if ((e = first.next) != null) {
                //如果first是红黑树类型,则以红黑树的方式查找该节点
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //遍历查找该节点
                do {
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

上面的代码都有注释了,还是一点点的来分析下:
假设我们现在想get key为t的数据:
第3行: if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n – 1) & hash]) != null) //table中有3个数据,所以tab不为null,n是tab的长度,等于4,(n – 1) & hash = 0011&1001=1;由于tab[1]不为null,所以if条件成立,first指向tab[1],如下图:
《HashMap源码解析二》

第5行:if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
由上图可知first指向数据的hash值为0101,而我们想要get的数据的key为t,hash值为1001,所以if条件不满足,走到第8行。

第8行:if ((e = first.next) != null) // e = first.next,即e指向first为头结点的链表的下一个结点,如下图:
《HashMap源码解析二》

可知if条件成立,走到第9行,判断 first 是否是红黑树类型,显示是单链表类型,所以条件不成立,走到第11行的do…while

第12行:if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
由上图可知,e的hash值等于1001等于我们要get的数据的hash值,并且key值相等,所以条件成立,返回 key为t的数据。循环结束,如果还没找到数据的话就接着遍历该链表的下一个数据,直到找到该数据。

到这里,get的方法讲完了,我来写个我之前遇到的例子,大家运行的结果是啥?

 Map<Long, String> map1 = new HashMap<Long, String>();
        map1.put(1L, "abc");
        System.out.println(" map1 val = " + map1.get(1));

可能有的人直到答案是null了,有些人可能还不知道,一起来看看啊。
我们先来打印几个值看看:

Long a1 = 1l;
        Integer a2 = 1;
        System.out.println("a1.hashCode = " + a1.hashCode()+", a2.hashCode = "+a2.hashCode());
        System.out.println("a1.equals(a2) = "+a1.equals(a2));

结果如下:

a1.hashCode = 1, a2.hashCode = 1
a1.equals(a2) = false

我们put进去的key为Long类型,而get的时候用的却是Integer类型,从打印的结果来看,a1是Long型,a2是Integer类型,a1和a2的hashcode 是相等的,那么通过hash(key)方法获取的hash值肯定也是相等的。但是a1和a2通过equals判断的结果是false,且 由于传递进去后的key都是Object类型,1L == 1 也是false, 可以看到getNode方法的第5行;
第5行:if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) //从上面的分析可知((k = first.key) == key || (key != null && key.equals(k))是不成立的,所以返回的是null。

二、remove方法

代码如下:

public V remove(Object key) {
        Node<K,V> e;
        //如果找到要remove的数据则返回该数据的value,否则返回null
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

主要看看里面的removeNode方法:

final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        //如果tab不为null且tab中有数据且tab相应索引的位置上的数据不为null
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            //如果key的hash值相等且key也相等,那么将p赋值给node
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            //如果条件不成立,如果p是红黑树类型则通过红黑树的方式找到 node,
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                //否则以遍历链表的方式找到该node
                else {
                    do {
                        if (e.hash == hash &&
                                ((k = e.key) == key ||
                                        (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //找到待删除的结点后,执行的链表删除操作,等下会有实例分析
            if (node != null && (!matchValue || (v = node.value) == value ||
                    (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                //这个方法这hashmap的子类linkhashmap中实现了,在hashmap里是一个空方法
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

原始数据还是之前的3个,假设我想删除的数据是key为m,hash值为0101的数据。
看removeNode的第4行代码;

第4行:if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n – 1) & hash]) != null) //table中有3个数据,table的长度为4,index = (n – 1) & hash = 0011&0101=1,table[1]中存放的是key为m的数据,所以
if条件成立,p指向table[1],如下图:
《HashMap源码解析二》

接下来走到第6行,
第6行: if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) // 由上图可知p的hash值为0101,key为m,是我们想要删除的数据,所以该条件成立。走到第8行;

第8行:node = p; //node 指向结点p,如下图:
《HashMap源码解析二》

第21行: if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) // 由上图可知node指向table[1],而matchValue 是false,后面的条件可不看了,if条件成立。

第22行:if (node instanceof TreeNode) //不成立,

第24行:else if (node == p) //由上图可知 node == p成立,

第25行:tab[index] = node.next; //index上面分析了等于1,而node.next指向key为t的数据。即tab[1]存放key为t的数据,如下图:
《HashMap源码解析二》

假设我们删除的是key为 t 的数据,table中还是之前的3个数据。
前面几行的逻辑如上,但第6行条件却不成立了,
第6行: if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //此是p指向的是table[1],p的hash= 0101,而我们要删除的数据key等于t,hash值等于1001,所以if条件不成立。走到第9行;

第9行: else if ((e = p.next) != null) //e=p.next p指向table[1]的第一个数据,p.next指向的是key为t的数据,所以 e 指向key为t的数据,如下图:
《HashMap源码解析二》

该if条件成立,第10行p是否是红黑树类型,不满足;走到12行执行do…while操作,如下:

第14行 : if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // e指向的结点的key为t,hash值为1001就是我们要删除的数据,条件满足,走到下一行:
node = e; //node 结点执行e,如下图:
《HashMap源码解析二》

然后执行break;跳出循环。
接着执行21行的if语句,上面分析了,该if成立,由于 node 不是红黑树类型的结点,所以走到24行:

第24行:else if (node == p) //上图可知,node不等于p,走到下一行
第26行:p.next = node.next; //node后面没有数据,所以node.next指向为空,即将p结点的next指向为空,就变成下图的结果:
《HashMap源码解析二》

最后返回被删除的结点 node。

HashMap的主要操作就是这样的了,后续会介绍LinkHashMap,里面涉及到双向的循环链表,这个得有一定的数据结构基础才行,不然可能会有点懵逼,可能我写的不大好,欢迎指正。

    原文作者:小猪快跑22
    原文地址: https://blog.csdn.net/zhujiangtaotaise/article/details/79085847
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞