HashMap源码分析

jdk版本1.8

主要是了解下其内部原理

一、简介(粗略翻译)

  1. HashMap实现了Map接口。HashMap非线程安全,并且HashMap的Key和Value都可以是null值。其它方面HashMap和HashTable比较像。HashMap不保证元素的顺序。
  2. 如果哈希方法适当的话,其get与put方法是固定的时间复杂度。迭代的时间复杂度是bucket数目加上桶的大小。所以如果对迭代的性能有很高的要求话,则不适合对HashMap赋予很大的存储,或者很低的load factor(装载因子),如果load factor很低,则要么capacity很大跟前面那个一样,相当于对每一个key都做一个哈希,那么当然遍历时间长了;要么size和capactiy都很低,那么就会频繁的扩容,同样也影响性能。
  3. 影响HashMap性能有两个主要的因素,一个是bucket数目,另一个是load factor(装载因子)。load factor用来衡量是否HashMap需要进行扩容。如果Entry的数量超过了 load factor * 当前容量,那么HashMap会进行扩容,有两个步骤一个是rehash,还有个是扩充容量,容量大约是原始容量的两倍。
  4. 一个通用的设定,load factor 为0.75。因为当load factor设定为0.75时,很好的权衡了时间和空间复杂度。更高的load factor减少空间开销但是增加时间复杂度(体现在很多基本方法中,其中包括get 和 put)。为了最小话rehash数量,当设置初始容量时,需要把entry数量和load factor考虑在内。如果capacity > entry_size / load factor, 则不会rehash。
  5. 如果在一个HashMap中有很多映射的时候,相比自动出发rehash扩容HashMap,初始化很大的capacity会有更好的性能表现,因为rehash一次就有一次性能开销,很多key拥有一样的hashcode当然会拉低HashMap的性能表现,要不然HashMap不就和链表一样了嘛?并且为了改善性能,消除打结,当key实现了Comparable,则HashMap能够利用比较来消除打结。
  6. HashMap并非线程安全。如果多线程对同一个HashMap进行结构化操作(增加或者删除映射,改变value不算)那么一定要在外部对HashMap进行同步。经常发生在对Map进行压缩。
  7. 迭代器有fast fail的特性。当一个Map在创建了Iterator之后被结构改变了,迭代器会抛出ConcurrentModificationException。因此当对HashMap进行并发的结构改变时,迭代器会快速无污染的抛出异常,因为在做改变之前就会fail。
  8. 由于fast fail的机制不能被保证在任何情况下都会出现,因此用这种机制写项目是错误的。正确的做法是用此机制来debug。

二、主要结构分析

(1)Node。其基本的结构,是一个静态内部类。被很多种entry都用到。注意其hashCode方法并非直接返回Hash。eqauls方法是key 和 value都相等。

 1 static class Node<K,V> implements Map.Entry<K,V> {
 2         final int hash;
 3         final K key;
 4         V value;
 5         Node<K,V> next;
 6 
 7         Node(int hash, K key, V value, Node<K,V> next) {
 8             this.hash = hash;
 9             this.key = key;
10             this.value = value;
11             this.next = next;
12         }
13 
14         public final K getKey()        { return key; }
15         public final V getValue()      { return value; }
16         public final String toString() { return key + "=" + value; }
17 
18         public final int hashCode() {
19            // the hash code is hashcode of key xor hash code of value. Not only hash.
20             return Objects.hashCode(key) ^ Objects.hashCode(value);
21         }
22 
23         public final V setValue(V newValue) {
24             V oldValue = value;
25             value = newValue;
26             return oldValue;
27         }
28 
29         //key and value are all the same then , the result is true.
30         public final boolean equals(Object o) {
31             if (o == this)
32                 return true;
33             if (o instanceof Map.Entry) {
34                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
35                 if (Objects.equals(key, e.getKey()) &&
36                     Objects.equals(value, e.getValue()))
37                     return true;
38             }
39             return false;
40         }
41     }

  

(2)Function hash,也称为扰动函数

顺便说一下,HashMap的capicity必须是2的n次方。

1、返回Node的hash值。

2、并非直接返回key的hash值,因为key的hash值太多了。

3、计算方法为key的低16位与高16位异或,高16位不变,有人做过实验,这种做法可以减少碰撞而且高效。

4、为什么HashMap的capitity为2的幂,因为在求桶的位置时,要用长度作为掩码,即index = (n-1)&hash。因此减一就可以获得掩码,即减1位全为1。

5、如果直接用key的hash和掩码相与时,容易出现碰撞。所以用加上扰动函数产生的掩码可以减少这种情况。

1  /*
2     这是hashMap的hash方法,可以看到并不是直接拿key的hashCode
3     返回的是key的低16位hash值和高16位hash的异或,因为h右移16位后,高位是16个0,任何数和0异或都是其本身
4 
5      */
6     static final int hash(Object key) {
7         int h;
8         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
9     }

 

(3)Function resize。对HashMap重新分配空间

 

 1 /**
 2      * 初始化,要么double被大小
 3      */
 4     final Node<K,V>[] resize() {
 5         Node<K,V>[] oldTab = table;
 6         //设置上一个map的capcity,记为oldCap
 7         int oldCap = (oldTab == null) ? 0 : oldTab.length;
 8         //老的threshold. threshold = capacity * load factor, threshold是下一次resize的阈值
 9         int oldThr = threshold;
10         int newCap, newThr = 0;
11         //如果不是初始化的话
12         if (oldCap > 0) {
13           //如果老的capacity >= 最大的capactiy的话,直接把threshhold设为整数的最大值,也不resize了
14             if (oldCap >= MAXIMUM_CAPACITY) {
15                 threshold = Integer.MAX_VALUE;
16                 return oldTab;
17             }
18             //现将新的capacity记为newCap设为oldCap<<1即2*oldCap,然后如果小于最大的capacity并且 oldCap >= 默认的初始Capacity的话
19             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
20                     oldCap >= DEFAULT_INITIAL_CAPACITY)
21                 newThr = oldThr << 1; // 将threshold设为两倍
22         }
23         //如果oldCap = 0但是oldThr > 0的话,更新newCap
24         else if (oldThr > 0) // initial capacity was placed in threshold
25             newCap = oldThr;
26         //否则newCap为默认初始capacit, 更新newThread
27         else {               // zero initial threshold signifies using defaults
28             newCap = DEFAULT_INITIAL_CAPACITY;
29             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
30         }
31         //经历了上述所有步骤后,如果newThread还为0的话,更新newThre
32         if (newThr == 0) {
33             float ft = (float)newCap * loadFactor;
34             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
35                     (int)ft : Integer.MAX_VALUE);
36         }
37         //更新threshold
38         threshold = newThr;
39         @SuppressWarnings({"rawtypes","unchecked"})
40         //经历了上述很多计算后,初始化了桶。容量就是newCap了。
41         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
42         table = newTab;
43         //如果老的table不为空的话,就要进行操作了,老的元素往哪放呢?接着往下看
44         if (oldTab != null) {
45             for (int j = 0; j < oldCap; ++j) {
46                 Node<K,V> e;
47                 //先得到老的节点,如果老节点不为空
48                 if ((e = oldTab[j]) != null) {
49                     //先把老节点设为空
50                     oldTab[j] = null;
51                     //如果老节点没有next,那么把它放在新节点的位置。因为newCap变了,所以参与计算的掩码个数变了,自然结果也变了
52                     if (e.next == null)
53                         newTab[e.hash & (newCap - 1)] = e;
54                     //如果老节点为红黑树,那么树进行剪枝
55                     else if (e instanceof TreeNode)
56                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
57                     //
58                     else { // preserve order
59                         Node<K,V> loHead = null, loTail = null;
60                         Node<K,V> hiHead = null, hiTail = null;
61                         Node<K,V> next;
62                         do {
63                             next = e.next;
64                             //根剪枝的判断往哪部分分的方法一样,一部分是low, 详细说一下,如果这个表达式为0的话就,即原来就存在的桶的位置,因为原先参与计算的是(oldCap-1)&hash,即oldCap之后位的都没参加运算
65                             if ((e.hash & oldCap) == 0) {
66                                 if (loTail == null)
67                                     loHead = e;
68                                 else
69                                     loTail.next = e;
70                                 loTail = e;
71                             }
72                             //这一部分是新的位置
73                             else {
74                                 if (hiTail == null)
75                                     hiHead = e;
76                                 else
77                                     hiTail.next = e;
78                                 hiTail = e;
79                             }
80                         } while ((e = next) != null);
81                         //把low的这部分保存原位置
82                         if (loTail != null) {
83                             loTail.next = null;
84                             newTab[j] = loHead;
85                         }
86                         //把high这部分放入新位置 j+oldcap
87                         if (hiTail != null) {
88                             hiTail.next = null;
89                             newTab[j + oldCap] = hiHead;
90                         }
91                     }
92                 }
93             }
94         }
95         return newTab;
96     }
97     

 

(4)Function put. 看一下最常用的方法。put

实际上是putVal。

1     public V put(K key, V value) {
2         return putVal(hash(key), key, value, false, true);
3     }

 

(5) Function putVal.原来一个put方法这面复杂。

 

 1 /**
 2      * 传入参数是4个。hash,注意是Node的hash计算方法,然后是K,V,onlyIfAbsent(不存在时候才执行),evict(原注释写的,如果为false,那么Map为创建状态)
 3      *
 4      */
 5 
 6     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 7                    boolean evict) {
 8         Node<K,V>[] tab; Node<K,V> p; int n, i;
 9         //如果为老的table为空,则resize
10         if ((tab = table) == null || (n = tab.length) == 0)
11             n = (tab = resize()).length;
12         //找到桶的位置,如果为空的话
13         if ((p = tab[i = (n - 1) & hash]) == null)
14           //在该位置加入一个新的节点
15             tab[i] = newNode(hash, key, value, null);
16         //如果已经有节点存在了
17         else {
18             Node<K,V> e; K k;
19             //如果hash值和头节点的hash相等,并且key的地址和内容都相等,整个if,else就是找e的位置
20             if (p.hash == hash &&
21                     ((k = p.key) == key || (key != null && key.equals(k))))
22                 //e直接等于头节点
23                 e = p;
24             //如果有一样不相等并且头节点是红黑树的话,则加入红黑树中
25             else if (p instanceof TreeNode)
26                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
27             //如果有一样不相等,并且该节点不属于红黑树的话
28             else {
29                 //遍历这个链表,记录位置
30                 for (int binCount = 0; ; ++binCount) {
31                     //如果next为空
32                     if ((e = p.next) == null) {
33                       //直接加到链表的尾部
34                         p.next = newNode(hash, key, value, null);
35                         //如果链表太长了,则生成红黑树
36                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
37                             treeifyBin(tab, hash);
38                         break;
39                     }
40                     //遍历的过程也有可能遇到两个key相等,也就是找到了
41                     if (e.hash == hash &&
42                             ((k = e.key) == key || (key != null && key.equals(k))))
43                         break;
44                     p = e;
45                 }
46             }
47             //如果e不为null
48             if (e != null) { // existing mapping for key
49                 V oldValue = e.value;
50                 //改变老的oldValue为新的value
51                 if (!onlyIfAbsent || oldValue == null)
52                     e.value = value;
53                 afterNodeAccess(e);
54                 return oldValue;
55             }
56         }
57         //如果结构改变了,即增加了元素,则modCount改变,用左fast fail机制
58         ++modCount;
59         //判断是否要resize
60         if (++size > threshold)
61             resize();
62         afterNodeInsertion(evict);
63         return null;
64     }

 

(6)Function get.

1     /**
2      * 如果取不到则为null,具体实现在getNode中
3      */
4     public V get(Object key) {
5         Node<K,V> e;
6         return (e = getNode(hash(key), key)) == null ? null : e.value;
7     }

 

(7)Function getNode

 1     /**
 2      * 参数两个,一个是hash方法后的hash值,另一个是key
 3      */
 4     final Node<K,V> getNode(int hash, Object key) {
 5         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
 6 
 7         //如果table存在并且确实有映射时,并且位置(n-1) & hash 存在时
 8         if ((tab = table) != null && (n = tab.length) > 0 &&
 9             (first = tab[(n - 1) & hash]) != null) {
10             //如果第一个节点的hash值和目标的哈希值相等并且是一个key时候,返回
11             if (first.hash == hash && // always check first node
12                 ((k = first.key) == key || (key != null && key.equals(k))))
13                 return first;
14             //否则遍历链表
15             if ((e = first.next) != null) {
16                 //如果是红黑树则遍历红黑树寻找
17                 if (first instanceof TreeNode)
18                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
19                 do {
20                   //否则遍历链表查找
21                     if (e.hash == hash &&
22                         ((k = e.key) == key || (key != null && key.equals(k))))
23                         return e;
24                 } while ((e = e.next) != null);
25             }
26         }
27         return null;
28     }

 

posted @ 2018-12-13 21:06  ylxn  阅读(198)  评论(0编辑  收藏  举报