HashMap 源码分析 基于1.8

1、个人总结及想法:

(1)1.8相比较于1.7的变化?

HashMap的底层数据结构大家应该都比较清楚了,就是数组+链表,链表主要用来解决hash冲突,使用了链地址法的方式来解决,1.8的改动主要就是hash冲突时候,一是在进行链表插入时由1.7的头插法变成了尾插法,第二个原来链表是一个单链表,但是现在超过红黑树的阀值过后就会自动升级为红黑树,阀值是链表节点超过8个节点(建立在数组已经扩容到64,否则优先选择扩容来解决冲突)。当链表节点少于6个时红黑树会退化成普通链表。

(2)1.8会出现1.7之前并发扩容节点转移时出现死循环的过程吗?

不会,因为1.8在扩容时不再是像原来一样转移,而是分成了两个链表,然后移动到新数组的原位置和原位置+length处,这样最多可能出现重复扫描,但不会出现死循环的情况。但是依然是线程不安全的。

   (3)  为什么hashMap的容量总是2的倍数?

我觉得简单点的说的话因为2的倍数在插入操作时hash更均匀,降低hash冲突,还有就是在扩容节点转移过程中,2的倍数能降低重复计算节点在新数组中位置的作用,减少了性能消耗

 

2、源码分析:

(1)、重要属性:

 1  private static final long serialVersionUID = 362498820763181265L;
 2     //默认值
 3     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 4 
 5     //最大值
 6     static final int MAXIMUM_CAPACITY = 1 << 30;
 7 
 8     //负载因子
 9     static final float DEFAULT_LOAD_FACTOR = 0.75f;
10 
11     //转化为红黑树的阀值
12     static final int TREEIFY_THRESHOLD = 8;
13 
14     //红黑树退化成链表阀值
15     static final int UNTREEIFY_THRESHOLD = 6;
16 
17    //转化为红黑树时最小的数组大小
18     static final int MIN_TREEIFY_CAPACITY = 64;
19 
20   //节点数据结构
21     static class Node<K,V> implements Map.Entry<K,V> {
22         final int hash;
23         final K key;
24         V value;
25         Node<K,V> next;
26 
27         Node(int hash, K key, V value, Node<K,V> next) {
28             this.hash = hash;
29             this.key = key;
30             this.value = value;
31             this.next = next;
32         }
33 
34         public final K getKey()        { return key; }
35         public final V getValue()      { return value; }
36         public final String toString() { return key + "=" + value; }
37 
38         public final int hashCode() {
39             return Objects.hashCode(key) ^ Objects.hashCode(value);
40         }
41 
42         public final V setValue(V newValue) {
43             V oldValue = value;
44             value = newValue;
45             return oldValue;
46         }
47 
48 //重写了equals方法
49         public final boolean equals(Object o) {
50             if (o == this)
51                 return true;
52             if (o instanceof Map.Entry) {
53                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
54                 if (Objects.equals(key, e.getKey()) &&
55                     Objects.equals(value, e.getValue()))
56                     return true;
57             }
58             return false;
59         }
60     

 

构造函数:

指定容量和负载因子:

 1 public HashMap(int initialCapacity, float loadFactor) {
 2         if (initialCapacity < 0)
 3             throw new IllegalArgumentException("Illegal initial capacity: " +
 4                                                initialCapacity);
 5         if (initialCapacity > MAXIMUM_CAPACITY)
 6             initialCapacity = MAXIMUM_CAPACITY;
 7         if (loadFactor <= 0 || Float.isNaN(loadFactor))
 8             throw new IllegalArgumentException("Illegal load factor: " +
 9                                                loadFactor);
10         this.loadFactor = loadFactor;
11         this.threshold = tableSizeFor(initialCapacity);
12     }

 

 

指定容量:

 1 public HashMap(int initialCapacity) { 2 this(initialCapacity, DEFAULT_LOAD_FACTOR); 3 } 

 

通过HashMap构造:

1 public HashMap(Map<? extends K, ? extends V> m) {
2         this.loadFactor = DEFAULT_LOAD_FACTOR;
3         putMapEntries(m, false);
4     }

 

默认构造:

 1 public HashMap() { 2 this.loadFactor = DEFAULT_LOAD_FACTOR; 

 

tableSizeFor()方法分析:
1 static final int tableSizeFor(int cap) {
2         int n = cap - 1;
3         n |= n >>> 1;
4         n |= n >>> 2;
5         n |= n >>> 4;
6         n |= n >>> 8;
7         n |= n >>> 16;
8         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
9     }

 

不多解释 就是简单的让容量为最小二次幂,这样做的原因上面阐述过了



getNode()方法分析:
 1 final Node<K,V> getNode(int hash, Object key) {
 2 
 3 //定义节点数组
 4         Node<K,V>[] tab; 
 5         Node<K,V> first, e; 
 6          int n; K k;
 7 //检查是否初始化 和数组节点是否存在
 8         if ((tab = table) != null && (n = tab.length) > 0 &&
 9             (first = tab[(n - 1) & hash]) != null) {
10         //首先是检查entry首节点
11             if (first.hash == hash && //通过equals和检查后
12                 ((k = first.key) == key || (key != null && key.equals(k))))
13 //返回
14                 return first;
15             if ((e = first.next) != null) {
16 //如果不是entry的首节点 那就判断是不是树节点
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     }

 

在找的过程中显示判断节点是否存在然后检查key是否一致,如果不一致且后继节点不为空就判断是否是红黑树节点,如果是的话就在红黑树里面找,如果不是的话就遍历链表直到找到或者链表遍历完毕返回null。

putVal()方法分析:


 1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 2                    boolean evict) {
 3 //定义一个tab数组
 4         Node<K,V>[] tab; Node<K,V> p; int n, i;
 5 //如果还没有初始化
 6         if ((tab = table) == null || (n = tab.length) == 0)
 7 //就扩容初始化
 8             n = (tab = resize()).length;
 9 //如果数组节点没有节点
10         if ((p = tab[i = (n - 1) & hash]) == null)
11 //生成一个节点防置在那
12             tab[i] = newNode(hash, key, value, null);
13         else {
14 //比较第一个节点是否是 否则就要遍历树或者链表
15             Node<K,V> e; K k;
16             if (p.hash == hash &&
17                 ((k = p.key) == key || (key != null && key.equals(k))))
18                 e = p;
19             else if (p instanceof TreeNode)
20 //以红黑树的方式添加节点
21                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
22             else {
23                 for (int binCount = 0; ; ++binCount) {
24                     if ((e = p.next) == null) {
25 //添加在尾节点
26                         p.next = newNode(hash, key, value, null);
27                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
28 //转化为红黑树
29                             treeifyBin(tab, hash);
30                         break;
31                     }
32                     if (e.hash == hash &&
33 //如果找到了就直接退出循环
34                         ((k = e.key) == key || (key != null && key.equals(k))))
35                         break;
36                     p = e;
37                 }
38             }
39             if (e != null) { // 
40                 V oldValue = e.value;
41                 if (!onlyIfAbsent || oldValue == null)
42                     e.value = value;
43 //这个回调是为LinkHashMap设置的 在这里意义不大
44                 afterNodeAccess(e);
45                 return oldValue;
46             }
47         }
48 //modCount+1  因为结构改变了
49         ++modCount;
50         if (++size > threshold)
51 //达到扩容要求 扩容
52             resize();
53         afterNodeInsertion(evict);
54 //没有找到返回null
55         return null;
56     }

 

分析:通过上面的分析我们可以简单的认为:HashMap一开始是并没有初始化的,是第一次使用的时候才会初始化,在put时显示判断数组位置有没有节点,如果没有直接新建节点添加,如果存在的话就遍历红黑树或者链表添加节点,
如果在遍历链表过程中发现已经达到了红黑树转化的阀值,就将链表转化为红黑树,如果找到了就modCount++,判断是否需要扩容,返回null。(这里要看onlyIfAbent是否为true,返回可能为旧值)

resize()方法分析:
 1 final Node<K,V>[] resize() {
 2 //保存就数组
 3         Node<K,V>[] oldTab = table;
 4 //获取旧的容量 判断是否初始化
 5         int oldCap = (oldTab == null) ? 0 : oldTab.length;
 6 //获取就得阀值并复制为信的阀值
 7         int oldThr = threshold;
 8 //定义新的容量和阀值
 9         int newCap, newThr = 0;
10 //如果旧容量大于0
11         if (oldCap > 0) {
12             if (oldCap >= MAXIMUM_CAPACITY) {
13 //如果大于最大容量  那只能让你去碰撞了 不能扩容了
14                 threshold = Integer.MAX_VALUE;
15                 return oldTab;
16             }
17 //如果扩容后不超过最大容量 且大于默认的容量大小(16)
18 //就直接扩容为2倍
19             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
20                      oldCap >= DEFAULT_INITIAL_CAPACITY)
21                 newThr = oldThr << 1; // double threshold
22         }
23 //如果旧的阀值大于0 新的容量等于旧的阀值
24         else if (oldThr > 0)
25             newCap = oldThr;
26         else {  
27 //否则就是使用默认的大小 即16的容量和0.75的扩容因子            
28             newCap = DEFAULT_INITIAL_CAPACITY;
29             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
30         }
31         if (newThr == 0) {
32 //一般只有未初始化的数组会走到这一步 
33             float ft = (float)newCap * loadFactor;
34             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
35                       (int)ft : Integer.MAX_VALUE);
36         }
37         threshold = newThr;
38         @SuppressWarnings({"rawtypes","unchecked"})
39             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
40         table = newTab;
41         if (oldTab != null) {
42             for (int j = 0; j < oldCap; ++j) {
43                 Node<K,V> e;
44                 if ((e = oldTab[j]) != null) {
45                     oldTab[j] = null;
46                     if (e.next == null)
47                         newTab[e.hash & (newCap - 1)] = e;
48                     else if (e instanceof TreeNode)
49                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
50                     else { // preserve order
51 //这里相当于是把原来的链表分为了两部分 
52                         Node<K,V> loHead = null, loTail = null;
53                         Node<K,V> hiHead = null, hiTail = null;
54                         Node<K,V> next;
55                         do {
56                             next = e.next;
57                             if ((e.hash & oldCap) == 0) {
58                                 if (loTail == null)
59                                     loHead = e;
60                                 else
61                                     loTail.next = e;
62                                 loTail = e;
63                             }
64                             else {
65                                 if (hiTail == null)
66                                     hiHead = e;
67                                 else
68                                     hiTail.next = e;
69                                 hiTail = e;
70                             }
71                         } while ((e = next) != null);
72                         if (loTail != null) {
73                             loTail.next = null;
74                             newTab[j] = loHead;
75                         }
76                         if (hiTail != null) {
77                             hiTail.next = null;
78                             newTab[j + oldCap] = hiHead;
79                         }
80                     }
81                 }
82             }
83         }
84         return newTab;
85     }

 

扩容数组节点转移1.8完成的非常巧妙,因为数组扩大二倍后,容量的二进制向左移一位,这样再与hash值相与,新增的一位是0则转移到新链表的相同位置,为1则转移到新链表的原位置+oldCap位置。

差不多就是这些了

 

posted @ 2018-02-26 16:39  Chaer  阅读(213)  评论(0编辑  收藏  举报