HashMap源码分析
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始容量是16 static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量是2^30 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 负载因子是0.75 static final int TREEIFY_THRESHOLD = 8; // 链表转红黑树的阈值 static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表的阈值 static final int MIN_TREEIFY_CAPACITY = 64; // 转红黑树要求的最小table数组长度
HashMap是Node节点构成的数组+链表/红黑树的结构
关于节点的定义
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
Node节点有hash、naxt、key、value四个属性,同时重写了eauqls和hashcode方法。
hashcode
可以看到node节点的hashcode是键与值的hash值相异或而得
equals
首先比较地址是否相同,其次,在是Map.Entry对象类的前提下,如果两者key与value均相同,返回true,否则返回false。
计算hash
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
key的hash计算过程:将key的hashcode与该hashcode的高16位相异或得到hash
根据hash定位索引
int index = (n - 1) & hash;
这里n是table的长度。
若是要将每个节点均匀给到数组,就能合理的避免哈希碰撞,那么使用hash对数组长度进行取模运算是一种不错的方法。接着,x mod 2^n =x & (2^n - 1) ,这里2^n也就是hashmap容量取2的n次方的缘故,所以就可以采用位运算来代替取模运算。
这里将hashcode的高16位与hashcode异或的目的是为了在table的length较小时,让高位也参与运算。
调整hashmap的容量
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
假设cap=65,n=64=1000000;n>>>1=100000;n|=n>>>1=1100000;同理推得最后n=1111111;再加1就是10000000.
这个函数的目的就是确保hashmap的size始终是2的n次方。 比如你输入18,算出来就是32,需要65,算出来就是128。
getNode
final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //tab是数组,first是hash对应处第一个节点 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //存在first if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) //first的key对上了 return first; //直接return first if ((e = first.next) != null) { //此时key有next节点 if (first instanceof TreeNode) //first是树节点 return ((TreeNode<K,V>)first).getTreeNode(hash, key); //调用树节点的getTreeNode do { //first是链表节点 if (e.hash == hash && //遍历链表找到key对应的节点 ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
getNode方法过程总结:
(1)根据hash值得到对应的节点first,若是节点不为空
(2)若是first的hash等于对应hash,且first的key与参数key相等,返回first,否则(3)
(3)否则的话,first为树节点的话,遍历树节点得到对应节点,若first是链表节点,转(4)
(4)若first为链表节点,遍历链表找到对应的节点
putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) //空数组 n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) //hash对应数组位置处赋值给p,p为空 tab[i] = newNode(hash, key, value, null); //对应处创建新节点 else { //p不为空 Node<K,V> e; K k; if (p.hash == hash && //对应处有旧值 ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //旧的节点给e else if (p instanceof TreeNode) //对应处是树节点 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { //遍历p处的链表节点 if ((e = p.next) == null) { //p的next给e,若e为null把新节点给e p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st //大于建树门槛则转为树 treeifyBin(tab, hash); break; } if (e.hash == hash && //e不为null,且key就是e处的key ((k = e.key) == key || (key != null && key.equals(k)))) break; //跳出循环 p = e; //将p指向下一个节点 } } if (e != null) { // existing mapping for key //把value给e处的value V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
putval过程总结:
(1)若tab是空数组,调用resize,新长度赋值给n
(2)根据hash得到对应数组节点,将此节点赋值给p,若是为空的话,将新节点直接赋值过来
(3)若p不为空且p的key等于参数k,将p赋值给e(此时p是数组节点)
(4)若p是树节点,调用树方法进行树节点操作
(5)若p是链表节点,遍历p找到对应节点的话,跳出循环,否则,在链表尾部创建新节点插入
(6)对对应节点覆盖原值,若是size超了,进行resize
resize
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; //pldTab为旧的数组 int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; //新容量、新门槛都为0 if (oldCap > 0) { //如果旧的容量>0 if (oldCap >= MAXIMUM_CAPACITY) { //旧容量比2^31还大,不能再变了,返回原来的 threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //新容量是旧容量的2倍且小于max且旧容量比默认容量大 oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold //新门槛是旧的门槛的2倍 } else if (oldThr > 0) // initial capacity was placed in threshold //旧容量不大于0且旧门槛大于0 newCap = oldThr; //新容量=旧门槛 else { // zero initial threshold signifies using defaults 旧容量和旧门槛都是0 newCap = DEFAULT_INITIAL_CAPACITY; //新容量=默认容量 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //新门槛=默认容量*默认负载因子 } if (newThr == 0) { //如果新门槛==0 float ft = (float)newCap * loadFactor; //ft=新容量*负载因子 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); //新门槛=ft或maxvalue } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //用新容量创建新的数组 table = newTab; if (oldTab != null) { //如果旧的数组不是null for (int j = 0; j < oldCap; ++j) { //遍历旧数组 Node<K,V> e; if ((e = oldTab[j]) != null) { //遍历到j,此处节点赋值给e oldTab[j] = null; //j节点置空 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //e没next,根据e的hash算出在新数组的位置,将e赋值给该位置节点 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //e是树节点 else { // preserve order //e在链表 /* 判断hash&oldcap,如果是0的话,那么hash&(oldcap-1)和hash&(newcap-1)结果是一样的, 比如我们要判断的hash值是1110(14),原始容量若是16,此时hash&(oldcap)==1110&10000==0, 原始index是1110&1111==1110,newcap是32,那么32-1=11111&1110=1110,即新的index和原来额index一样。 若是hash&oldcap不是0,比如我们的hash是10100,oldcap=10000,10100&10000==10000; 那我们的新的index是oldcap+oldindex oldindex=hash&(oldcap-1)==10100&1111==00100=4 newindex=hash&(newcap-1)==10100&11111==10100=20 其实很容易看出来就只是最前边多了一个1; */ Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
resize方法总结:
(1)首先如果旧容量>MAXIMUM_CAPACITY,将阈值设为Integer.MAX_VALUE,直接返回老表,否则新容量设为旧容量的2倍,转(2)
(2)如果新容量<MAXIMUM_CAPACITY,且旧容量>DEFAULT_INITIAL_CAPACITY,新的阈值设为旧的阈值的2倍
(3)若旧容量是0,且旧的阈值大于0,新表的容量就是旧表的阈值。这种情况是传了容量的new方法创建的空表,将新表的容量设置为老表的阈值(这种情况发生在新创建的HashMap第一次put时,该HashMap初始化的时候传了初始容量,由于HashMap并没有capacity变量来存放容量值,因此传进来的初始容量是存放在threshold变量上(查看HashMap(int initialCapacity, float loadFactor)方法),因此此时老表的threshold的值就是我们要新创建的HashMap的capacity,所以将新表的容量设置为老表的阈值。
(4)旧容量和旧阈值都是0的话,新容量和新阈值都是默认的。
(5)定义新表,容量就是刚计算出来的新的容量。
(6)将原数组上各个节点重新分布在新表。
treeifyBin
final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) //MIN_TREEIFY_CAPACITY是红黑树时要求数组的min长度 resize(); //此时用resize else if ((e = tab[index = (n - 1) & hash]) != null) { //hash对应处不为空 TreeNode<K,V> hd = null, tl = null; //遍历该索引处的链表 do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) //此时代表是第一次循环 hd = p; else { p.prev = tl; //当前节点prev设施为上一个节点 tl.next = p; //上个节点的next设为当前节点 } tl = p; //t1赋值为p,作为下次循环的上个节点 } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
链表转红黑树过程:
(1)tab小于64时,resize
(2)hash对应处不为空,遍历该处链表,将链表节点转为红黑树节点,将头节点赋值给pd,
(3)将table该索引位置赋为新转的hd,若该节点不为空,以该节点为根节点构红黑树
treeify
final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null; // root为根节点 for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; //next为x的下一个节点 x.left = x.right = null; //x左右子节点赋值为空 if (root == null) { //如果根节点为空,就将x设为根节点 x.parent = null; // 根节点没有父节点 x.red = false; //根节点必须为黑色 root = x; //x设为根节点 } else { //存在非空根节点 K k = x.key; int h = x.hash; Class<?> kc = null; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) //x节点hash小于p节点的hash dir = -1; // 代表向p的左边寻找 else if (ph < h) //x节点hash大于p节点的hash dir = 1; //代表向p的右边寻找 //走到这里说明x的hash与p的hash相等,接下来比较k else if ((kc == null && // 如果k没有实现Comparable接口 或者 x节点的key和p节点的key相等 (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); //使用自定义的规则来决定dir(寻找方向) /* 在这个for循环中实际上是从根节点开始找x的位置,p逐级遍历左右子树,直到是空的就找到了 */ TreeNode<K,V> xp = p; //向左或者向右找,若是空的说明该位置就是x的位置 if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; x的父节点是最后一次遍历的p节点 if (dir <= 0) xp.left = x; else xp.right = x; //红黑树的插入平衡 root = balanceInsertion(root, x); break; } } } } moveRootToFront(tab, root); // 如果root节点不在table索引位置的头结点, 则将其调整为头结点 }
构建红黑树过程:
(1)如果根节点为空,就将调用该方法的节点置为根节点,否则,根据hash大小去遍历根节点的左右子节点,不断向左向右迭代p节点
(2)若是p节点的next是null,说明找到头了,此时的p节点就是调用该方法的节点的父节点,
(3)进行红黑树的平衡,以及调整头节点位置正确。
关于上边步骤3中的调整头节点位置的方法:
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) { int n; if (root != null && tab != null && (n = tab.length) > 0) { int index = (n - 1) & root.hash; TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; //first为index处第一个节点 if (root != first) { //root不为头节点 Node<K,V> rn; tab[index] = root; //root赋值为头节点 TreeNode<K,V> rp = root.prev; if ((rn = root.next) != null) //root节点的下一个节点不为空 ((TreeNode<K,V>)rn).prev = rp; //root节点上一个节点赋值为下一个节点的prev if (rp != null) // root节点的上一个节点不为空 rp.next = rn; //root节点下一个节点赋值为上一个节点的next //上述操作移除了root节点 if (first != null) // 如果原头节点不为空, 则将原头节点的prev属性设置为root节点 first.prev = root; root.next = first; // 将root节点的next属性设置为原头节点 root.prev = null; //此时root是头节点,prev为null } assert checkInvariants(root); //检察树是否正常,满足红黑树的规则 } }
(1)首先确保根节点存在的合理性
(2)如果头节点不是数组index处存在的第一个节点,将root赋值给该第一个节点。
(3)如果root的后节点不为空,将root的前节点赋值给root后节点的prev
(4)如果前节点不为空,将root的后节点赋值给root前节点的next((3)和(4)实际上就是删除root节点的过程)
(5)数组上原来的index处所在的元素是first,若是不为空,就将root节点置为first的前节点
(6)检查树是否正常
移除节点
final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { //hash对应索引不为空 Node<K,V> node = null, e; K k; V v; if (p.hash == hash && //索引处直接对上了 ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { //索引出没直接对上 if (p instanceof TreeNode) //返回对应的树节点 node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { //此时是链表结构 do { //遍历链表找出 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) //此时是头节点 tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
(1)就是根据hash和key找出对应的节点
(2)节点若是树节点,removeTreeNode
(3)若是普通链表节点,删除链表节点
移除树节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) { int n; if (tab == null || (n = tab.length) == 0) return; //空数组直接return int index = (n - 1) & hash;//索引位置头节点给first和root TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl; TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev; // next是this.next,this即调用该方法的节点(node) if (pred == null) // node节点的prev是null tab[index] = first = succ; //将table索引处和first节点赋为succ节点 else // node节点的prev不是null pred.next = succ; // succ给pred节点的next if (succ != null) // node的next不是null succ.prev = pred; // succ的prev设为pred if (first == null) // first节点是null return; // 直接返回 if (root.parent != null) // root节点父节点不为空的话,将root节点置为根节点(索引位置头节点不一定是根节点) root = root.root(); // 通过root节点来判断此红黑树是否太小, 如果是则调用untreeify方法转为链表节点并返回 // (转链表后就无需再进行下面的红黑树处理) if (root == null || root.right == null || (rl = root.left) == null || rl.left == null) { tab[index] = first.untreeify(map); // too small return; } TreeNode<K,V> p = this, pl = left, pr = right, replacement; //p为node if (pl != null && pr != null) { // node左右子树都不为空 TreeNode<K,V> s = pr, sl; // s为右子树 while ((sl = s.left) != null) // find successor // 向左一直查找,直到叶子节点,此时s为叶子节点 s = sl; boolean c = s.red; s.red = p.red; p.red = c; // swap colors 交换p和s节点的颜色 TreeNode<K,V> sr = s.right; // s的右节点 TreeNode<K,V> pp = p.parent; // p的父节点 if (s == pr) { // p was s's direct parent //如果p的右节点是叶子结点s p.parent = s; // s赋值为p的父节点 s.right = p; // p赋值给s的右节点 } else { // p的右节点不是叶子节点s TreeNode<K,V> sp = s.parent; // sp是s的父节点 if ((p.parent = sp) != null) { // sp赋值给p的父节点,若sp不是null if (s == sp.left) // 如果s为左节点 sp.left = p; // 将sp左节点赋值p节点 else sp.right = p; // s为sp右节点的话,将p给sp的右节点 } if ((s.right = pr) != null) // p的右节点给s的右节点且不为空 pr.parent = s; // s给p节点的右节点的父节点 } p.left = null; if ((p.right = sr) != null) sr.parent = p; if ((s.left = pl) != null) pl.parent = s; if ((s.parent = pp) == null) root = s; else if (p == pp.left) pp.left = s; else pp.right = s; if (sr != null) replacement = sr; else replacement = p; } else if (pl != null) replacement = pl; else if (pr != null) replacement = pr; else replacement = p; if (replacement != p) { TreeNode<K,V> pp = replacement.parent = p.parent; if (pp == null) root = replacement; else if (p == pp.left) pp.left = replacement; else pp.right = replacement; p.left = p.right = p.parent = null; } TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement); if (replacement == p) { // detach TreeNode<K,V> pp = p.parent; p.parent = null; if (pp != null) { if (p == pp.left) pp.left = null; else if (p == pp.right) pp.right = null; } } if (movable) moveRootToFront(tab, r); }
(1)数组空的话直接返回
(2)要移除的是调用该方法的树节点node,根据node的hash算出数组中对应index处的first,succ是node的后节点,pred是node的前节点,first赋值给root节点
(3)若pred是null,说明node就在索引处,此时直接把succ赋给索引处(first)
(4)
关于split方法
该方法在前边的resize中用过,作用是将树节点移到新数组。
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this; // b为调用此方法的节点 // Relink into lo and hi lists, preserving order TreeNode<K,V> loHead = null, loTail = null; TreeNode<K,V> hiHead = null, hiTail = null; int lc = 0, hc = 0; for (TreeNode<K,V> e = b, next; e != null; e = next) { //从b节点开始遍历 next = (TreeNode<K,V>)e.next; //next为e下个节点 e.next = null; //原来的下个节点置空 if ((e.hash & bit) == 0) { //为0则扩容后索引不变 if ((e.prev = loTail) == null) loHead = e; else loTail.next = e; loTail = e; ++lc; //统计原索引处的节点个数 } else { //此时新的索引为oldcap+原来索引位置 if ((e.prev = hiTail) == null) hiHead = e; else hiTail.next = e; hiTail = e; ++hc; //统计新的索引处的节点个数 } } if (loHead != null) { if (lc <= UNTREEIFY_THRESHOLD) //如果个数不足以构造树 tab[index] = loHead.untreeify(map); //解树 else { //否则,如果hihead不是空,说明原来的红黑树被破坏,需要重新构造 tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } if (hiHead != null) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } } }
(1)b是调用此方法的节点,将b赋值给e
(2)类似于resize方法中来确定lohead/lotail之类的
(3)进行新数组中的分配,lc是原来索引处的节点个数,hc是新的索引处的节点个数
(4)如果个数不足以建树就拆成链表
(5)hiHead不为空则代表原来的红黑树(老表的红黑树由于节点被分到两个位置) 已经被改变, 需要重新构建新的红黑树,否则就lohead’完事儿了。
- HashMap.put(K key, V value)源码分析
- JDK源码学习(4)-HashMap的遍历方式,两种迭代器源码分析
- HashMap源码分析(基于JDK1.6)
- 【Java】HashMap源码分析——基本概念
- hashmap源码分析jdk8
- 【转载】HashSet与HashMap关系之源码分析
- HashMap源码分析
- java-HashMap和HashSet源码分析
- HashSet与HashMap关系之源码分析
- JDK1.8 HashMap 源码分析
- 源码分析- Java 1.8 HashMap
- HashMap源码分析
- HashMap源码分析
- 从源码的角度分析Hashtable和HashMap的区别
- JAVA集合源码分析——HashMap
- HashMap源码分析,转载请注明出处,谢谢
- DK1.8源码分析之HashMap & LinkedHashMap迭代器(三)
- java.util.concurrent 之ConcurrentHashMap 源码分析
- JDK1.7 HashMap 源码分析
- HashMap源码分析(jdk7)