JDK8中hashMap源码解析
1、hashMap数据结构
因为主要说的是1.8版本中的实现。而1.8中HashMap是数组+链表+红黑树实现的,大概如下图所示。后面还是主要介绍Hash Map中主要的一些成员以及方法原理。
那么上述图示中的结点Node具体类型是什么,源码如下。Node是HashMap的内部类,实现了Map.Entery接口,主要就是存放我们put方法所添加的元素。其中的next就表示这可以构成一个单向链表,这主要是通过链地址法解决发生hash冲突问题。而当桶中的元素个数超过阈值的时候就换转为红黑树。
Node<K,V>
1 |
|
TreeNode<**k**,**v**>
1 | /* ------------------------------------------------------------ */ |
2、HashMap中的成员变量以及含义
1 | //默认初始化容量初始化=16 |
3、HashMap构造方法
HashMap()
1 | /** |
HashMap(int initialCapacity)
1 |
|
HashMap(int initialCapacity, float loadFactor)
1 |
|
HashMap(Map<? extends K, ? extends V> m)
1 |
|
putMapEntries(Map<? extends K, ? extends V> m, boolean evict)
1 |
|
4、put 方法
1 | /** |
putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
1 | /** |
可以看到主要逻辑在put方法中调用了putVal方法,传递的参数是调用了hash()方法计算key的hash值,主要逻辑在putVal中。可以结合注释熟悉这个方法的执行,我在这里大概总结一下这个方法的执行:
首先 (tab = table) == null || (n = tab.length) == 0这一块判断hash桶是否为null,如果为null那么会调用resize方法扩容。后面我们会说到这个方法
定位元素在桶中的位置,具体就是通过key的hash值和hash桶的长度计算得到下标i,如果计算到的位置处没有元素(null),那么就新建结点然后添加到该位置。
如果table[i]处不为null,已经有元素了,那么就表明产生hash冲突,这里可能是三种情况
①判断key是不是一样,如果key一样,那么就将新的值替换旧的值;
②如果不是因为key一样,那么需要判断当前该桶是不是已经转为了红黑树,是的话就构造一个TreeNode结点插入红黑树;
③不是红黑树,就使用链地址法处理冲突问题。这里主要就是遍历链表,如果在遍历过程中也找到了key一样的元素,那么久还是使用新值替换旧值。否则会遍历到链表结尾处,到这里就直接新添加一个Node结点插入链表,插入之后还需要判断是不是已将超过了转换为红黑树的阈值8,如果超过就会转为红黑树。
最后需要修改modCount的值。
判断插入后的size大小是不是超过了threshhold,如果超过需要进行扩容。
resize()
1 | /** |
判断当前oldTab长度是否为空,如果为空,则进行初始化桶数组,也就回答了无参构造函数初始化为什么没有对容量和阈值进行赋值,如果不为空,则进行位运算,左移一位,2倍运算扩容。
扩容,创建一个新容量的数组,遍历旧的数组:
- 如果节点为空,直接赋值插入
- 如果节点为红黑树,则需要进行进行拆分操作(个人对红黑树还没有理解,所以先不说明)
- 如果为链表,根据hash算法进行重新计算下标,将链表进行拆分分组(相信看到这里基本上也知道链表拆分的大致过程了)
5、get方法
1 | /** |
getNode(int hash, Object key)
1 | /** |