JDK8源码解读:HashMap

Posted 开源java学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK8源码解读:HashMap相关的知识,希望对你有一定的参考价值。

在使用HashMap操作自如后,对:hashCode、hashMap结构:数组列表、散列冲突、装填因子有了一定的概念,通过分析HashMap源码中put()方法,深入理解HashMap的工作原理及应用范围。



HashMap中的map.put()



public V put(K key, V value) {

   return putVal(hash(key), key, value, false, true);

}

put方法会return一个putVal方法,方法包含5个参数,其中前三个:

hash(key):是根据传入的key键来计算出一个int型的数字

key:要放入hashMap中的【键】

value:和键相对应的【值】


h = key.hashCode()这里h是这个key通过hashCode()方法计算得到的hashCode值。得到h后:h ^ (h >>> 16) 即表示h会进一步和 (h >>> 16)进行异或运算并return最终的结果。这里细心留意下会发现:h是个32位的int型,h >>> 16表示:原先位于右半部分的低16位全被清空,原先位于左半部分的高16位移到了现在的右半部分,左边空位用0补齐。


然后得到的数再和h进行异或,那么最后的结果相当于保留了原先h的高16位部分,而低16位部分则相当于用原高16位和低16位异或。


hash(key)计算出键的hash值后,put方法return一个putVal()方法:

return putVal(hash(key), key, value, false, true);



根据key求HashMap中的索引的问题

putVal方法初始化了几个变量:


Node<K,V>[] tab; Node<K,V> p; int n, i;

看到Node,进去看一下是如何定义的:


static class Node<K,V> 

               implements Map.Entry<K,V> {

       final int hash;

       final K key;

       V value;

       Node<K,V> next;


      Node(int hash, K key, V value, 

                 Node<K,V> next) {

            this.hash = hash;

            this.key = key;

            this.value = value;

            this.next = next;

}

可以看到,和LinkedList中的Node类似,HashMap中也定义了内部类:Node<K,V>,不过有点不同的是Node节点类有4个成员变量:


final int hash;

final K key;

V value;

Node<K,V> next;


每个Node节点都可以存一个int类型的hash值,key,value,和指向另一个Node节点的引用next,现在让我们回到putVal中看一下:


Node<K,V>[] tab; Node<K,V> p; int n, i;

这个tab是个Node节点数组,里面肯定有联系,带着疑问我们接着往下看:


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

      n = (tab = resize()).length;

越看越晕啊,table是什么?

transient Node<K,V>[] table;


这个table是个Node节点数组,HashMap的底层是链表数组,这个Node<K,V>[] table就是HashMap所谓的【链表数组】


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

      n = (tab = resize()).length;

将table赋给tab,若tab为空或tab.length == 0,则执行n = (tab = resize()).length;


如果初始的HashMap为空则tab == null则进入初始化过程,初始化主要是通过resize()执行。


resize()后的数组列表赋给tab完成初始化。resize方法,看名字就知道是用于扩容的方法,现在知道resize()方法是用来给HashMap扩容的。经过resize()方法初始化赋值后tab成为了一个长度为16,阈值为12的数组列表(初始容量默认为16,装填因子默认为0.75,阈值 = 容量 * 装填因子)


if ((p = tab[i = (n - 1) & hash]) == null)

      tab[i] = newNode(hash, key, value, null);

如果tab[i]对象为空,则通过newNode()方法构造一个新节点,然后插入tab[i]中。注意,此处索引

i = (n - 1) & hash

是hashMap中的核心知识点。对于任意一个新的等待添加的元素,是如何计算其插入位置索引的呢?就是通过(n - 1) & hash.hash是之前通过hash(key)计算出来的int型的hash值,n为table的容量 = table.length。


结合上面int型的hash值的算法:

h = key.hashCode()) ^ (h >>> 16)

说明这里的hash值h是和hashCode相关的,但是不等价,且计算方式比较独特。这是因为在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。


这个方法计算非常巧妙,它通过

h & (table.length-1)

来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,因为&比%具有更高的效率!

JDK8源码解读:HashMap



HashMap中的哈希碰撞问题


首先来定义哈希碰撞问题:

table[i]处原先没有对象时可以通过

tab[i] = newNode(hash, key, value, null);

插入新对象。要是tab[i]处不为空该怎么办呢?

此处就是hash碰撞:即不同元素的key通过hash(key)散列出的索引i相等,导致这些元素都会插入至table[i],也就是产生了哈希碰撞。


JDK8源码解读:HashMap
                           
 这里分几种情况:


//如果当前待插入对象的hash值和table[i]对象的hash值相同,且key值相同,则用新value覆盖掉原value

if (p.hash == hash &&((k = p.key) == key

      || (key != null && key.equals(k)))){

    e = p;

}


//若key不相同,且table[i]为红黑树,则将节点放入树中

else if (p instanceof TreeNode)

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

//若key不相同,且table[i]为链表,则进入for循环

    else {...}

1.若当前对象的hash值和tab[i]对象的hash值相同,且key键也相等,则直接用当前对象的新value值覆盖原value。


2.若hash值相同但key不同,则将当前对象插入到table[i]中的对象中去,此对象可能是链表也可能是红黑树。若table[i]对象为红黑树,则将当前对象插入树中。


3.若table[i]对象为链表,则插入之前需要依次遍历每个链表节点,寻找插入位置。

JDK8源码解读:HashMap

简单来说,如果table[i]为链表,那么会通过一个无限for循环遍历此链表来寻找插入位。正常情况下会循环到链表尾,停止循环,将待插入的key和value通过newNode()方法放入新构造的Node节点中,将此Node节点置于链表尾。或者在循环过程中发生key值冲突,则会提前break出for循环。这时,最后如果e != null,则表示发生了key值冲突,则会用新value值覆盖掉原节点的value值,并return出去。

点击关注,学习更多java源码知识!

以上是关于JDK8源码解读:HashMap的主要内容,如果未能解决你的问题,请参考以下文章

源码解读:ArrayList源码解析(JDK8)

HashMap源码解读(中篇)

HashMap 源码解读

HashMap 源码解读

JDK8,AQS源码解读

深入理解JAVA集合系列:HashMap源码解读