JDK1.8---HashMap的get(Object)方法源码详解

JDK1.8---HashMap的get(Object)方法源码详解

HashMap是我们常用的集合之一,本片针对HashMap中的get()方法底层源码讲解。
之后还会有对HashMap的putremove、还有扩容方法resize以及链表扭转为红黑树进行分析

下面针对HashMap从get()方法开始以及涉及到的调用方法讲解:

先看一下get()方法源码:

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

这个方法没有什么要说的,主要是getNode()方法才是HashMap通过K查找V的灵魂所在,getNode()方法的第一个参数是根据要查找的K获取K的hash值(注意:在通过K查找V时就是通过该K的hash值和HashMap的桶进行了一个&运算定位到该K-V所在的桶后面会讲到),下面来详细看看getNode()方法是如何实现的:

为了详细理解到HashMap,首先看看获取K的hash方法吧:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

该方法是对K的hashCode所得到的值进行一个波动处理,是为了防止一些实现比较差的hashCode方法造成碰撞太多(没有均匀分布在各个桶中)。(为何波动处理之后就可以均匀分布在各个桶中原理我也不了解,希望知道的留言指教)

接下来看看getNode()方法是如何实现的吧:

1 final Node<K,V> getNode(int hash, Object key) {
2        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
3        if ((tab = table) != null && (n = tab.length) > 0 &&
4            (first = tab[(n - 1) & hash]) != null) {
5            if (first.hash == hash && // always check first node
6                ((k = first.key) == key || (key != null && key.equals(k))))
7                return first;
8            if ((e = first.next) != null) {
9                if (first instanceof TreeNode)
10                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
11                do {
12                    if (e.hash == hash &&
13                        ((k = e.key) == key || (key != null && key.equals(k))))
14                        return e;
15                } while ((e = e.next) != null);
16            }
17        }
18        return null;
19    }

中所周知HashMap是用过数组+链表实现的,数组就相当于一个个桶,桶中放着链表。

[1] 要想通过K查找V,首先需要定位到该K所在的桶(哪一个数组下表中),也就是在第4行进行定位的,hash & (n - 1) ] n为桶数也就是数组大小,定位的时候我们首先想到的就是hash%n,但是这种取余操作相对于&操作效率太低了,而要使hash&(n-1) == hash%n这两个公式相等有一个前提条件就是n必须为2次幂,这也就解释了HashMap的大小为什么是2的n次幂, 每当定位到K所在的桶就会获取桶中链表的头结点,先判断头结点,如果相同就返回头结点,否则继续向下查找。

这里需要注意的是查找时,先通过该K的hash值与链表中各个结点的K的hash值进行对比,如果不同继续对比下一个结点,如果相同,该通过 equals() 方法进行对比判断是否真的是同一个K,若果相同就返回该结点。

[2] 当第一个头结点对比完了,若果不同就需要对比第二个结点,在这里也有一个需要注意的地方,也是接下来要讲的主要内容,从JDK1.8开始对HashMap的链表进行了一个优化,当链表中Node结点数量大于等于8的时候,会把链表扭转为一棵红黑树,所以在判断第二个结点之前就需要判断该桶中是链表,还是红黑树,在第9行进行判断的,如果是链表就一直向下迭代判断知道该链表结束。

[3] 在步骤[2]中如果桶中存放的是红黑树,就要通过红黑树的方法进行搜索判断,这就是下面的重点,通过红黑树搜索是通过第10行的getTreeNode()方法进行搜索的,首先看下源码:

final TreeNode<K,V> getTreeNode(int h, Object k) {
            return ((parent != null) ? root() : this).find(h, k, null);
        }

通过红黑树查找,首先要保证从根节点进行查找,这里通过一个三目运算符进行保证的,如果parent==null也就意味着从根节点开始搜索的,如果不为null就要通过root()方法返回根节点,源码如下:

final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }

当有了根节点也就要开始进行所搜了也就是根节点调用的find()方法,首先看下源码:

1 final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
2            TreeNode<K,V> p = this;
3            do {
4                int ph, dir; K pk;
5                TreeNode<K,V> pl = p.left, pr = p.right, q;
6                if ((ph = p.hash) > h)
7                    p = pl;
8                else if (ph < h)
9                    p = pr;
10                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
11                    return p;
12                else if (pl == null)
13                    p = pr;
14                else if (pr == null)
15                   p = pl;
16                else if ((kc != null ||
17                          (kc = comparableClassFor(k)) != null) &&
18                         (dir = compareComparables(kc, k, pk)) != 0)
19                    p = (dir < 0) ? pl : pr;
20                else if ((q = pr.find(h, k, kc)) != null)
21                    return q;
22                else
23                    p = pl;
24            } while (p != null);
25            return null;
26        }

所搜时是通过K的hash值进行搜索步骤如下:
[1] 如果根结点的K的hash值>要查找的K的hash值就走根节点的左子树进行搜索,反之进行右子树搜索;
[2]根据[1]得到的结点,结点的K通过equals方法进行比较K结果相等就返回该结点;
[3] 如果hash值相等但是通过equals比较K的结果不相等就继续向下比较,再比较时先判断该结点下面是否还有左右子树,如果没有了就返回null,也就是HashMap中不存在对应该K的K-V键值对;

(注意以下步骤是在hash值相等但是K不相等的时候进行的)

[4] 如果该节点仅有左子树或者右子树其中之一,则就根据剩余的那个子树走;
[5] 如果左右子树都有,这时候在这里进行了一个方法的递归调用(也就是左右两边都走了一遍),也就是在上面源代码的第20行 ,先通过右子树进行了方法的递归调用,在向下面进行了左子树的搜索。

从根节点到叶子节点循环上面5个过程,如果到叶子结点还没有找到,返回null。

以上是我通过对HashMap的get方法源码的理解,如果那里不对,希望留言批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值