HashMap put、get方法源码分析
HashMap.java的实现是面试必问的问题。
JDK版本
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b15, mixed mode)
1. HashMap节点的封装
Node<K, V>
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; //..... }
hash值算法
public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); }
put null的key,计算hash值
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
ok,重点分析一下扩容
1 /** 2 * Initializes or doubles table size. If null, allocates in 3 * accord with initial capacity target held in field threshold. 4 * Otherwise, because we are using power-of-two expansion, the 5 * elements from each bin must either stay at same index, or move 6 * with a power of two offset in the new table. 7 * 8 * @return the table 9 */ 10 final Node<K,V>[] resize() { 11 Node<K,V>[] oldTab = table; 12 int oldCap = (oldTab == null) ? 0 : oldTab.length; 13 int oldThr = threshold; 14 int newCap, newThr = 0; 15 if (oldCap > 0) { 16 if (oldCap >= MAXIMUM_CAPACITY) { 17 threshold = Integer.MAX_VALUE; 18 return oldTab; 19 } 20 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 21 oldCap >= DEFAULT_INITIAL_CAPACITY) 22 newThr = oldThr << 1; // double threshold 23 } 24 else if (oldThr > 0) // initial capacity was placed in threshold 25 newCap = oldThr; 26 else { // zero initial threshold signifies using defaults 27 newCap = DEFAULT_INITIAL_CAPACITY; 28 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 29 } 30 if (newThr == 0) { 31 float ft = (float)newCap * loadFactor; 32 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 33 (int)ft : Integer.MAX_VALUE); 34 } 35 threshold = newThr; 36 @SuppressWarnings({"rawtypes","unchecked"}) 37 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 38 table = newTab; 39 if (oldTab != null) { 40 for (int j = 0; j < oldCap; ++j) { 41 Node<K,V> e; 42 if ((e = oldTab[j]) != null) { 43 oldTab[j] = null; 44 if (e.next == null) 45 newTab[e.hash & (newCap - 1)] = e; 46 else if (e instanceof TreeNode) 47 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 48 else { // preserve order 49 Node<K,V> loHead = null, loTail = null; 50 Node<K,V> hiHead = null, hiTail = null; 51 Node<K,V> next; 52 do { 53 next = e.next; 54 if ((e.hash & oldCap) == 0) { 55 if (loTail == null) 56 loHead = e; 57 else 58 loTail.next = e; 59 loTail = e; 60 } 61 else { 62 if (hiTail == null) 63 hiHead = e; 64 else 65 hiTail.next = e; 66 hiTail = e; 67 } 68 } while ((e = next) != null); 69 if (loTail != null) { 70 loTail.next = null; 71 newTab[j] = loHead; 72 } 73 if (hiTail != null) { 74 hiTail.next = null; 75 newTab[j + oldCap] = hiHead; 76 } 77 } 78 } 79 } 80 } 81 return newTab; 82 }
首先说几点
1. 找tab位置,使用的key.hash() & (capacity - 1)
注意这里用的是位运算与操作,这里与的是capacity - 1,这样结果就不可能超过capactity - 1。不仅保证了结果正确,性能也提供了很多
2. 扩容大小为原来的大小左移一位,即扩大为原来的两倍
扩容主要的移动原来的元素,到新的数组里面
这里源码使用了比较好的方法,用key的hash值与capacity进行与,注意不是capacity - 1。如果与的结果是0,说明在新数组中的位置为原来的位置,不用变。如果结果不为0,最高为1,即capacty,说明在新数组中的位置为原来位置 + 原来的capacity
以前面试也被问道过,当时自信满满的胡说。自认为了解的很清楚,这里纠正一下
1. 封装的节点为Node,不是Entity
2. 扩容为大小为原来的两倍
3. 计算在数组位置,不是取余,是采用位操作。hashcode & capactiy - 1,注意这里的 - 1操作
4. put null的key,这里是可以的,null的hash值为0
5. key的计算为object.hashcode() ^ (object.hashcode >>> 16)
先到这
Please call me JiangYouDang!