HashMap源码学习

Posted jiachao

tags:

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

HashMap

  • 特性:基于数组+链表+红黑树实现,当链表长度超过8(阈值)时,链表转换为红黑树;HashMap本身是非线程安全的,允许键或值为NULL;元素无序且顺序会不定时改变(因为每次resize(),都会重新计算hash值,同一链表中的元素resize后可能就不在一个桶中了);key用set存放,需要重写key的hashcode()和equals()方法(因为Object类中的hashcode()方法返回的是对象的地址值)。
  • 类成员属性   
 1     //最大容量
 2     static final int MAXIMUM_CAPACITY = 1 << 30;  
 3     //加载因子
 4     static final float DEFAULT_LOAD_FACTOR = 0.75f;
 5     //阈值,当链表超过这个长度时,转换为红黑树
 6     static final int TREEIFY_THRESHOLD = 8;
 7     //resize时,当链表小于这个长度,红黑树又退化为链表
 8     static final int UNTREEIFY_THRESHOLD = 6;
 9     //只有哈希表的容量大于这个值时,才会树化,否则进行扩容
10     static final int MIN_TREEIFY_CAPACITY = 64;
  • resize()
  1. 方法:在往HashMap中put()元素时,如果数组已使用长度超过阈值时,就需要扩容。
  2. 源码  
 1   final Node<K,V>[] resize() {
 2         Node<K,V>[] oldTab = table;//旧数组,即当前所有元素所在的数组
 3         int oldCap = (oldTab == null) ? 0 : oldTab.length;//记录旧容量是否为null或0
 4         int oldThr = threshold;//记录旧阈值
 5         int newCap, newThr = 0;//记录新容量,新阈值
 6         //如果旧容量>0,再判断是否超过最大容量;超过最大容量则修改阈值为int的最大值,这样就不再扩容,只能碰撞
 7         if (oldCap > 0) {
 8             if (oldCap >= MAXIMUM_CAPACITY) {
 9                 threshold = Integer.MAX_VALUE;
10                 return oldTab;
11             }
12             //如果旧容量的二倍<最大容量并且旧容量超过默认值16,则新阈值翻倍
13             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
14                      oldCap >= DEFAULT_INITIAL_CAPACITY)
15                 newThr = oldThr << 1; // double threshold
16         }
17         //如果旧容量和旧阈值都>0,则将旧阈值赋值给新容量
18         else if (oldThr > 0) // initial capacity was placed in threshold
19             newCap = oldThr;
20         //如果旧容量=0,即刚调用无参构造进行初始化时,则设置默认值
21         else {               // zero initial threshold signifies using defaults
22             newCap = DEFAULT_INITIAL_CAPACITY;
23             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
24         }
25         //如果新阈值=0,重新计算新阈值
26         //如果新容量和新容量*加载因子都<最大容量,则新阈值=新容量*加载因子,否则等于int的最大值
27         if (newThr == 0) {
28             float ft = (float)newCap * loadFactor;
29             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
30                       (int)ft : Integer.MAX_VALUE);
31         }
32         threshold = newThr;//设置阈值为新阈值
33         @SuppressWarnings({"rawtypes","unchecked"})
34             //初始化一个容量为newCap大小的Node类型的数组(如果旧数组长度为0,那么新数组即为初始数组;如果旧数组长度不为0,那么新数组即为扩容的数组)
35             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
36         table = newTab;
37         //如果旧数组不为空,即为扩容操作,将旧数组中的数据移到新数组中
38         if (oldTab != null) {
39             for (int j = 0; j < oldCap; ++j) {
40                 Node<K,V> e;
41                 //遍历旧数组
42                 if ((e = oldTab[j]) != null) {
43                     oldTab[j] = null;//释放引用
44                     //如果元素的 next引用为null,则不冲突
45                     if (e.next == null)
46                         //重新计算元素在新数组中的位置
47                         newTab[e.hash & (newCap - 1)] = e;
48                     //如果元素是一个TreeNode节点,则进行红黑树的rehash
49                     else if (e instanceof TreeNode)
50                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
51                     //否则即为链表,进行链表的rehash
52                     else { // preserve order
53                         Node<K,V> loHead = null, loTail = null;
54                         Node<K,V> hiHead = null, hiTail = null;
55                         Node<K,V> next;
56                         do {
57                             next = e.next;
58                             if ((e.hash & oldCap) == 0) {
59                                 if (loTail == null)
60                                     loHead = e;
61                                 else
62                                     loTail.next = e;
63                                 loTail = e;
64                             }
65                             else {
66                                 if (hiTail == null)
67                                     hiHead = e;
68                                 else
69                                     hiTail.next = e;
70                                 hiTail = e;
71                             }
72                         } while ((e = next) != null);
73                         if (loTail != null) {
74                             loTail.next = null;
75                             newTab[j] = loHead;
76                         }
77                         if (hiTail != null) {
78                             hiTail.next = null;
79                             newTab[j + oldCap] = hiHead;
80                         }
81                     }
82                 }
83             }
84         }
85         return newTab;
86     }
  • put()
  1. 方法:可单次向HashMap中添加一个键值对,添加的元素,无顺序。
  2. 原理:
    • 计算当前key的hash值
    • 通过tab[i = (n-1) & hash]计算该hash值对应的数组位置,若该位置的桶为null,则新建一个桶,即将键值对直接插入到该数组位置上
    • 若该hash值对应的桶不为null,通过key的equals()方法依次遍历桶中元素。先判断桶中的第一个元素,若hash值和key都与要插入的键值对一样,则替换value,put完成;若与桶中第一个元素不相等,再判断该节点是否是TreeNode,如果是,则以红黑树的方式插入;否则,就是链表,以链表的方式插入节点,在遍历链表时,若找到hash值和key都与要插入的键值对一样的节点,则替换value,未找到,则添加到链表的尾部,添加时,要判断链表长度是否超过阈值,超过,则转换为红黑树。
    • size记录当前数组使用大小,若超过阈值,则resize()
  3. 源码
 1   public V put(K key, V value) {
 2         return putVal(hash(key), key, value, false, true);
 3     }
 4 
 5     /**
 6      * Implements Map.put and related methods
 7      *
 8      * @param hash hash for key
 9      * @param key the key
10      * @param value the value to put
11      * @param onlyIfAbsent if true, don‘t change existing value
12      * @param evict if false, the table is in creation mode.
13      * @return previous value, or null if none
14      */
15     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
16                    boolean evict) {
17         Node<K,V>[] tab; Node<K,V> p; int n, i;
18         //如果数组为null或者数组长度=0,则进行resize
19         if ((tab = table) == null || (n = tab.length) == 0)
20             n = (tab = resize()).length;//n记录数组长度
21         //判断数组中该位置的桶是否为空,若为空,则新建一个桶,即将节点直接插在该数组位置上
22         if ((p = tab[i = (n - 1) & hash]) == null)
23             tab[i] = newNode(hash, key, value, null);
24         else {//否则,这个数组位置上有节点,即桶中有元素
25             Node<K,V> e; K k;
26             //和该数组位置上的节点(即桶中第一个元素)做比较,如果与该节点元素的hash值和key都相等,则直接覆盖value
27             if (p.hash == hash &&
28                 ((k = p.key) == key || (key != null && key.equals(k))))
29                 e = p;
30             //如果该节点是一个TreeNode,则以红黑树的方式插入节点
31             else if (p instanceof TreeNode)
32                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
33             else {//否则,即为链表
34                 for (int binCount = 0; ; ++binCount) {
35                     //如果该节点的next引用为空,则插到链表中该节点的位置后面
36                     if ((e = p.next) == null) {
37                         p.next = newNode(hash, key, value, null);
38                         //如果链表长度>=7,即要超过阈值,则转换为红黑树
39                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
40                             treeifyBin(tab, hash);
41                         break;
42                     }
43                     //遍历链表,如果与该节点元素的hash值和key都相等,则break
44                     if (e.hash == hash &&
45                         ((k = e.key) == key || (key != null && key.equals(k))))
46                         break;
47                     p = e;
48                 }
49             }
50             //如果在桶中匹配到了要插入的节点(即与该节点元素的hash值和key都相等),则覆盖value
51             if (e != null) { // existing mapping for key
52                 V oldValue = e.value;
53                 if (!onlyIfAbsent || oldValue == null)
54                     e.value = value;
55                 afterNodeAccess(e);
56                 return oldValue;//返回旧value
57             }
58         }
59         ++modCount;
60         //size记录数组当前已使用大小,如果size超过阈值,则resize
61         if (++size > threshold)
62             resize();
63         afterNodeInsertion(evict);
64         return null;
65     }
  • get()
  1. 方法:根据传入的key,返回value,不存在则返回null
  2. 原理:
    • 计算当前key的hash值
    • 通过tab[i = (n-1) & hash]计算该hash值对应的数组位置,和该位置桶中的第一个元素作比较,若该元素的hash值和key都与传入的key一样,则返回该节点;否则,继续遍历,若该节点是一个TreeNode,则遍历红黑树;否则,就是链表,遍历链表,若找到hash值和key都与传入的key一样的节点,则返回该节点,若遍历完链表还未找到,则返回null
  3. 源码
 1     public V get(Object key) {
 2         Node<K,V> e;
 3         //根据hash值和key查询节点,若不存在则返回null,若存在则返回该节点的value
 4         return (e = getNode(hash(key), key)) == null ? null : e.value;
 5     }
 6 
 7     /**
 8      * Implements Map.get and related methods
 9      *
10      * @param hash hash for key
11      * @param key the key
12      * @return the node, or null if none
13      */
14     final Node<K,V> getNode(int hash, Object key) {
15         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
16         //如果数组不为null且数组长度>0且该hash值对应的数组位置上的桶不为空时
17         if ((tab = table) != null && (n = tab.length) > 0 &&
18             (first = tab[(n - 1) & hash]) != null) {
19             //和该数组位置上的节点(即桶中第一个元素)做比较,如果与该节点元素的hash值和key都相等,则返回该节点
20             if (first.hash == hash && // always check first node
21                 ((k = first.key) == key || (key != null && key.equals(k))))
22                 return first;
23             //如果不相等且该节点的next引用不为null,则遍历
24             if ((e = first.next) != null) {
25                 //如果该节点是一个TreeNode,则遍历红黑树
26                 if (first instanceof TreeNode)
27                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
28                 do {//否则即为链表,遍历链表
29                     if (e.hash == hash &&
30                         ((k = e.key) == key || (key != null && key.equals(k))))
31                         return e;
32                 } while ((e = e.next) != null);//遍历完链表的所有节点
33             }
34         }
35         return null;
36     }
  • remove()
  1. 方法:根据传入的key值,查询元素,存在则删除该键值对并返回value,不存在则返回null
  2. 原理:
    • 计算当前key的hash值
    • 通过tab[i = (n-1) & hash]计算该hash值对应的数组位置,和该位置桶中的第一个元素作比较,若该元素的hash值和key都与传入的key一样,则该节点就是要移除的节点;否则,继续遍历桶中元素,如果该节点是一个TreeNode,则遍历红黑树,找到则以红黑树的方式移除该节点;否则,就是链表,遍历链表,找到则以链表的方式移除该节点
  3. 源码
 1     public V remove(Object key) {
 2         Node<K,V> e;
 3         //根据hash值和key查询节点,若不存在则返回null,若存在则删除该节点并返回该节点的value
 4         return (e = removeNode(hash(key), key, null, false, true)) == null ?
 5             null : e.value;
 6     }
 7 
 8     /**
 9      * Implements Map.remove and related methods
10      *
11      * @param hash hash for key
12      * @param key the key
13      * @param value the value to match if matchValue, else ignored
14      * @param matchValue if true only remove if value is equal
15      * @param movable if false do not move other nodes while removing
16      * @return the node, or null if none
17      */
18     final Node<K,V> removeNode(int hash, Object key, Object value,
19                                boolean matchValue, boolean movable) {
20         Node<K,V>[] tab; Node<K,V> p; int n, index;
21         //如果数组不为null且数组长度>0且该hash值对应的数组位置上的桶不为空时
22         if ((tab = table) != null && (n = tab.length) > 0 &&
23             (p = tab[index = (n - 1) & hash]) != null) {
24             Node<K,V> node = null, e; K k; V v;
25             //和该数组位置上的节点(即桶中第一个元素)做比较,如果与该节点元素的hash值和key都相等,则该节点就是要remove的节点,赋值给node
26             if (p.hash == hash &&
27                 ((k = p.key) == key || (key != null && key.equals(k))))
28                 node = p;
29             //如果不相等且该节点的next引用不为null,则遍历
30             else if ((e = p.next) != null) {
31                 //如果该节点是一个TreeNode,则遍历红黑树
32                 if (p instanceof TreeNode)
33                     node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
34                 else {//否则即为链表,遍历链表
35                     do {
36                         //如果与该节点元素的hash值和key都相等,则该节点就是要remove的节点,赋值给node
37                         if (e.hash == hash &&
38                             ((k = e.key) == key ||
39                              (key != null && key.equals(k)))) {
40                             node = e;
41                             break;
42                         }
43                         p = e;
44                     } while ((e = e.next) != null);//遍历完链表的所有节点
45                 }
46             }
47             //如果node不为null,则说明已匹配到要remove的节点
48             if (node != null && (!matchValue || (v = node.value) == value ||
49                                  (value != null && value.equals(v)))) {
50                 //如果该节点是一个TreeNode,则该节点存在于红黑树中,调用红黑树的removeTreeNode方法
51                 if (node instanceof TreeNode)
52                     ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
53                 //否则即为链表,以链表的方式remove该节点
54                 else if (node == p)
55                     tab[index] = node.next;
56                 else
57                     p.next = node.next;
58                 ++modCount;
59                 --size;
60                 afterNodeRemoval(node);
61                 return node;
62             }
63         }
64         return null;
65     }

 

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

HashMap源码分析 (3. 手撕源码) 学习笔记

HashMap源码学习

JDK源码学习笔记~HashMap.put()

HashMap实现原理和源码详细分析

HashMap实现原理和源码详细分析

HashMap源码学习