标签:pes http 的区别 ESS sam 运算 移动 because 随机
复习散列数据结构之余重新研究了一下Java中的HashMap;本文主要针对:1、HashMap的初始化;2、HashMap的插入;3:HashMap的扩容这三个方面进行总结
首先我们来看看代码:
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 } 13 14 /** 15 * 返回一个等于指定容量的2的N次方的容量 16 * Returns a power of two size for the given target capacity. 17 */ 18 static final int tableSizeFor(int cap) { 19 int n = cap - 1; 20 n |= n >>> 1; 21 n |= n >>> 2; 22 n |= n >>> 4; 23 n |= n >>> 8; 24 n |= n >>> 16; 25 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 26 }
1 方法一: 2 static final int hash(Object key) { //jdk1.8 & jdk1.7 3 int h; 4 // h = key.hashCode() 为第一步 取hashCode值 5 // h ^ (h >>> 16) 为第二步 高位参与运算 6 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 7 } 8 方法二: 9 static int indexFor(int h, int length) { 10 //jdk1.7的源码,jdk1.8没有这个方法,取而代之的是在1.8中的putVal()方法中的第3行中: 11 //if ((p = tab[i = (n - 1) & hash]) == null)) 12 //原理都是一样的,作用也是一样的,都是定位元素位置 13 return h & (length-1); //第三步 取模运算 14 },
看到这里我们自然会问:为什么hash()函数中要用对象的hashcode与自身的高16位进行异或运算(hashcode ^ (hashcode >>> 16))?
这是一个很精妙的设计:
1 public V put(K key, V value) { 2 return putVal(hash(key), key, value, false, true); 3 } 4 5 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 6 boolean evict) { 7 Node<K, V>[] tab; 8 Node<K, V> p; 9 int n, i; 10 // 如果表为空则创建,这也体现了hashmap是懒加载的,构造完hashmap之后,如果没有put操作,table是不会初始化的 11 if ((tab = table) == null || (n = tab.length) == 0) 12 n = (tab = resize()).length; 13 // 这一步是根据hash值对数组长度取模,找到元素应该存放的位置, 14 //JDk1.7中把该步骤写成另一个方法,1.8中直接写在此处 15 //如果为空则创建一个节点 16 if ((p = tab[i = (n - 1) & hash]) == null) 17 tab[i] = newNode(hash, key, value, null); 18 //不为空的情况 19 else { 20 Node<K, V> e; 21 K k; 22 // 节点已经存在,并且key一样,直接覆盖 23 if (p.hash == hash && 24 ((k = p.key) == key || (key != null && key.equals(k)))) 25 e = p; 26 //判断是否是红黑树 27 else if (p instanceof TreeNode) 28 e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); 29 //执行到这里说明该位置存放的是链表 30 else { 31 for (int binCount = 0; ; ++binCount) { 32 if ((e = p.next) == null) { 33 p.next = newNode(hash, key, value, null); 34 //链表长度大于8转换为红黑树进行处理 TREEIFY_THRESHOLD = 8 35 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 36 treeifyBin(tab, hash); 37 break; 38 } 39 // key已经存在直接覆盖value 40 if (e.hash == hash && 41 ((k = e.key) == key || (key != null && key.equals(k)))) 42 break; 43 p = e; 44 } 45 } 46 if (e != null) { // existing mapping for key 47 V oldValue = e.value; 48 if (!onlyIfAbsent || oldValue == null) 49 e.value = value; 50 afterNodeAccess(e); 51 return oldValue; 52 } 53 } 54 ++modCount; 55 // 超过最大容量threshold 就扩容 56 if (++size > threshold) 57 resize(); 58 afterNodeInsertion(evict); 59 return null; 60 }
有了前面的铺垫,下面理解HashMap的扩容应该不会有太大的困难了:
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() {...}
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 if (oldCap >= MAXIMUM_CAPACITY) { 8 threshold = Integer.MAX_VALUE; 9 return oldTab; 10 } 11 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 12 oldCap >= DEFAULT_INITIAL_CAPACITY) 13 newThr = oldThr << 1; // double threshold 14 } 15 else if (oldThr > 0) // initial capacity was placed in threshold 16 newCap = oldThr; 17 else { // zero initial threshold signifies using defaults 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 } 21 if (newThr == 0) { 22 float ft = (float)newCap * loadFactor; 23 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 24 (int)ft : Integer.MAX_VALUE); 25 } 26 threshold = newThr; 27 @SuppressWarnings({"rawtypes","unchecked"}) 28 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 29 table = newTab; 30 //移动数据 31 if (oldTab != null) { 32 for (int j = 0; j < oldCap; ++j) { 33 Node<K,V> e; 34 if ((e = oldTab[j]) != null) { 35 oldTab[j] = null; 36 if (e.next == null) 37 newTab[e.hash & (newCap - 1)] = e; 38 else if (e instanceof TreeNode) 39 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 40 else { // preserve order 41 Node<K,V> loHead = null, loTail = null; 42 Node<K,V> hiHead = null, hiTail = null; 43 Node<K,V> next; 44 do { 45 next = e.next; 46 //把元素的hash值与旧的容量做&运算,便可得出元素的hash值 47 //新增的参与运算的那一bit是1还是0 48 //hash&(n-1) 与 hash & n 的区别: 49 //加入n为16,则n-1为:1111 ,n为:10000 50 //n比n-1高了一bit,且因为n为2的n次幂, 51 //所以,hash&n 可以得出扩容后元素hash值多参与运算的那一bit是0还是1 52 //新增参与运算的bit是0,则位置不变 53 if ((e.hash & oldCap) == 0) { 54 if (loTail == null) 55 loHead = e; 56 else 57 loTail.next = e; 58 loTail = e; 59 } 60 //新增参与运算的bit是1,位置变为: 原索引+原数组容量 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 //位置不变 72 newTab[j] = loHead; 73 } 74 if (hiTail != null) { 75 hiTail.next = null; 76 //位置变为: 原索引+原数组容量 77 newTab[j + oldCap] = hiHead; 78 } 79 } 80 } 81 } 82 } 83 return newTab; 84 }
第一次写博客,有很多表述可能不是很清楚,望谅解。
标签:pes http 的区别 ESS sam 运算 移动 because 随机
原文地址:https://www.cnblogs.com/mochenghui/p/11154547.html