标签:for cti ade mapping abstract 转换 散列 ott equals
1 public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable { 2 // map 的元素就是保存在这个table数组变量之中,也就是“数组+链表” 之中的数组 3 transient Node<K,V>[] table; 4 5 public V put(K key, V value) { 6 return putVal(hash(key), key, value, false, true); 7 } 8 // hash 算法核心,减少碰撞 9 static final int hash(Object key) { 10 int h; 11 /** 如果key是空,hash值返回0 12 否则,用key的hashcode() h变量 和 h无符号右移16位【右移一半】后的值【h是int类型32位,右移使hash值的高位变成低 位,举例1010 右移两位变成0010】作异或运算。这么做的目的是为了是哈希值更加散列【因为求key的下标是tab[i = (n - 1) & hash],用数组长度减一的值和哈希值做与运算,大多数情况下,哈希值参与运算仅为低位部分值, 13 举例:数组长度16,转换二进制:1111,哈希值为78897121,转换二进制:100101100111101111111100001与1111 运算如 下,结果是0000 0000 0000 0000 0000 0000 0000 0001,可见决定结果的是低4位,所以为了让高位也能参与到运算,减少 冲突。最后哈希值高位没变动,低位值被重新计算。】 14 */ 15 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 16 } 17 // 散列码(哈希值)的获取,依赖本地方法,通常是将对象的内存地址转换为整数。 18 public native int hashCode(); 19 // onlyIfAbsent:true 存在则不更新。evict:供LinkedHashMap 回调方法afterNodeInsertion()使用 20 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 21 boolean evict) { 22 // tab 临时变量,p是桶位上的首个node,n添加元素后的数组长度,i是目标位置下标 23 Node<K,V>[] tab; Node<K,V> p; int n, i; 24 if ((tab = table) == null || (n = tab.length) == 0) 25 n = (tab = resize()).length; 26 // 如果之前桶位上没有值,那么构造一个node 放上去 27 if ((p = tab[i = (n - 1) & hash]) == null) 28 tab[i] = newNode(hash, key, value, null); 29 else { 30 // 到这里说明桶位上有值,要么添加自己到链表/红黑树末尾,要么找到和自己key一致的node,替换node的值 31 // e 是存在的那个节点,k 是存在的节点的key 32 Node<K,V> e; K k; 33 // 如果p-原来的node的哈希值和新值的hash值一致,且内容也一样。则p就是新值要替代对象,赋给e 34 if (p.hash == hash && 35 ((k = p.key) == key || (key != null && key.equals(k)))) 36 e = p; 37 // 如果p已经不是node结构【链表结构】,而是红黑树 38 else if (p instanceof TreeNode) 39 // 调用红黑树的putTreeVal(),返回要替代的目标节点 40 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 41 else { 42 // 走到这里,说明目前还是数组+链表结构,且桶位上p并不是我们要替代的目标,所以开始遍历链表 43 for (int binCount = 0; ; ++binCount) { 44 // 如果p的下一个结点不存在,说明遍历链表完毕,那好,就构造一个新节点插入到链尾 45 if ((e = p.next) == null) { 46 p.next = newNode(hash, key, value, null); 47 // 如果目前桶位上链表数超过 树化阈值,那么就转为红黑树 48 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 49 treeifyBin(tab, hash); 50 break; 51 } 52 // 到这里说明e不为空,判断当前结点e和新值的哈希值和key内容是否相同 53 // 如果成立,好这就是要更新替代的目标节点,跳出循环,不再遍历后续链表 54 if (e.hash == hash && 55 ((k = e.key) == key || (key != null && key.equals(k)))) 56 break; 57 p = e; 58 } 59 } 60 // 如果e不为空,就返回老值,保存新值。 61 // 如果e为空,说明之前已经构造新值并插入链表末尾 62 if (e != null) { // existing mapping for key 63 V oldValue = e.value; 64 // onlyIfAbsent 方法形参,true:不能修改存在的值。控制是否可以修改值的一个开关 65 // 如果允许修改或者老值为空,则赋新值 66 if (!onlyIfAbsent || oldValue == null) 67 e.value = value; 68 afterNodeAccess(e);// 回调方法,空实现,模板模式 69 return oldValue; 70 } 71 } 72 // 修改计数器加一 73 ++modCount; 74 // 走到这一步,说明新元素已经被构造成一个Node,添加到了链尾,最后返回值为空,因为新元素是新加入,没有替代谁【本方法说的替代准确说是替代老节点的value的值,而不是老节点】。 75 // 如果hashmap添加新元素后,实际使用长度超过扩容阈值,完蛋,扩容吧。 76 if (++size > threshold) 77 resize(); 78 afterNodeInsertion(evict);// 回调方法,空实现,模板模式 79 return null; 80 } 81 // Callbacks to allow LinkedHashMap post-actions 82 void afterNodeAccess(Node<K,V> p) { } 83 void afterNodeInsertion(boolean evict) { } 84 85 // 树化阈值,如果链表结点数大于等于8,就会转为红黑树 86 static final int TREEIFY_THRESHOLD = 8; 87 // 链化阈值,如果红黑树结点数小于等于6,就会转为链表 88 static final int UNTREEIFY_THRESHOLD = 6; 89 90 91 }
1 // 扩容方法,面试重点 2 final Node<K,V>[] resize() { 3 Node<K,V>[] oldTab = table; 4 int oldCap = (oldTab == null) ? 0 : oldTab.length; 5 int oldThr = threshold; 6 int newCap, newThr = 0; 7 if (oldCap > 0) { 8 // 如果之前的容量大于等于1<<30 即1073741824;那么新扩容阈值变为2147483647,这个值要比容量大, 9 if (oldCap >= MAXIMUM_CAPACITY) { 10 threshold = Integer.MAX_VALUE; 11 return oldTab; 12 } 13 // 如果之前容量扩大一倍后赋给新容量 小于最大容量,且老容量大于等于默认初始化容量16,新扩容阈值也变大 14 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 15 oldCap >= DEFAULT_INITIAL_CAPACITY) 16 newThr = oldThr << 1; // double threshold 17 } 18 // 如果oldCap <= 0,且oldThr > 0,那么初始容量设置为阈值 19 else if (oldThr > 0) // initial capacity was placed in threshold 20 newCap = oldThr; 21 else { // zero initial threshold signifies using defaults 22 newCap = DEFAULT_INITIAL_CAPACITY; 23 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 24 } 25 // 如果新的扩容阈值等于0,重新计算扩容阅知 26 if (newThr == 0) { 27 float ft = (float)newCap * loadFactor; 28 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 29 (int)ft : Integer.MAX_VALUE); 30 } 31 threshold = newThr; 32 // 以新的容量构造一个数组 33 @SuppressWarnings({"rawtypes","unchecked"}) 34 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 35 // map的数组引用指向新数组【扩容后数组】 36 table = newTab; 37 if (oldTab != null) { 38 // 遍历原数组 39 for (int j = 0; j < oldCap; ++j) { 40 Node<K,V> e; 41 // 如果原数组桶位上 存在节点node,不为空 42 if ((e = oldTab[j]) != null) { 43 // 桶位上的node已经赋值到e,清空桶位 44 oldTab[j] = null; 45 /** 如果e的下一个节点不存在,也就是说链表只有一个节点,直接将e这个放到新数组的桶位上, 46 桶位的下标计算公式不变,唯一区别是使用新数组的容量-1来与运算。newCap是oldCap 的2倍, 47 比如 oldCap为16,oldCap-1→1111,newCap为32,newCap-1→11111。此时e.hash值的第三位决定了,新桶 位的下标,如果e.hash的第三位是0,则计算出的下标和之前一样,可以说位置不变。如果是1,新下标就会变 成11111,可分解为 原下标1111【15】+10000【16】,原下标15,新下标31。总结就是下标要么在原址, 48 要么跑到原址+oldCap【原来容量】的地址 49 */ 50 if (e.next == null) 51 newTab[e.hash & (newCap - 1)] = e; 52 else if (e instanceof TreeNode) 53 // 如果e 是红黑树节点,调用split()方法来处理 54 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 55 else { // preserve order 56 // 到这里说明,桶位上是链表,且链表结点不为1. 57 // loHead、loTail 是扩容后在落在原址的结点组成的链表的链表头尾,称之为低位链 58 // hiHead、hiTail 是扩容后到落在原址+oldCap的地址组成的链表的链表头尾,称之为高位链。 59 Node<K,V> loHead = null, loTail = null; 60 Node<K,V> hiHead = null, hiTail = null; 61 // next 临时变量,代表下一个被处理的节点 62 Node<K,V> next; 63 // 开始遍历待分配位置链表 64 do { 65 // 当前结点e的下一个结点地址赋给next 66 // ? next变量 能否省略。我的感觉是可以,退出条件改为while((e=e.next) !=null) 67 next = e.next; 68 /* 判断当前结点的哈希值和扩容前容量做与运算是否等于0,目的是为了将链表上的节点均匀的分配到低位链 高位链。它是如何做到的呢?oldCap二进制一定是100~这种形式,做与运算只需判断e.hash二进制值和 前者1所对应的位置上的数是0还是1,是0当前结点e加入低位链。是1当前节点e加入高位链。 69 注意:一个链表上的不同结点的散列码是不同的,来自不同的内存地址。 70 */ 71 if ((e.hash & oldCap) == 0) { 72 // 如果是首次向低位链添加,就把e赋值给loHead链表头 73 if (loTail == null) 74 loHead = e; 75 else 76 // 不是第一次向低位链添加结点,那么就赋值给loTail.next,链接到链表尾部【尾插法】 77 loTail.next = e; 78 // 更新低位链后,loTail重新指向链尾。【就像一个箭头下移了一位】 79 loTail = e; 80 } 81 // 执行else,说明结点e应该被添加到高位链上。 82 else { 83 // 如果是首次向高位链添加,就把e赋值给hiHead链表头 84 if (hiTail == null) 85 hiHead = e; 86 else 87 // 不是第一次向高位链添加结点,那么就赋值给hiTail.next,链接到链表尾部【尾插法】 88 hiTail.next = e; 89 // 更新高位链后,hiTail重新指向链尾。【就像一个箭头下移了一位】 90 hiTail = e; 91 } 92 // 遍历老链表,当next 为null时,退出循环 93 } while ((e = next) != null); 94 // 遍历完成后,待分配节点已经各自组成了高位链、低位链 95 if (loTail != null) { 96 // 低位链链尾指向null,把链表放置到新数组老位置 97 loTail.next = null; 98 newTab[j] = loHead; 99 } 100 if (hiTail != null) { 101 // 高位链链尾指向null,把链表放置到新数组新位置,相对老位置偏移oldCap位。 102 hiTail.next = null; 103 newTab[j + oldCap] = hiHead; 104 } 105 } 106 } 107 // 每次循环搞定一个桶位 108 } 109 } 110 // 最终返回扩容后数组 111 return newTab; 112 }
get()方法下回补充。。
标签:for cti ade mapping abstract 转换 散列 ott equals
原文地址:https://www.cnblogs.com/houj/p/14275177.html