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位置。
差不多就是这些了