集合底层源码分析之HashMap《上》(三)

  • 前言
    • 源码分析
    • HashMap主要属性及构造方法分析
      • tableSizeFor()方法源码分析
      • Node类源码分析
      • TreeNode类源码分析
      • rotateLeft()方法源码分析(左旋)
        • 案例讲解
      • rotateRight()方法源码分析(右旋)
        • 案例讲解
        • 左旋和右旋方法源码:小结
      • treeifyBin()方法源码分析
      • resize()方法源码分析
      • treeify()方法源码分析
      • moveRootToFront()方法源码分析
      • checkInvariants()方法源码分析
      • root()方法源码分析
      • find()方法源码分析
      • put()方法源码分析
        • put()方法案例讲解
          • putVal()方法:小结
        • resize()方法案例讲解
          • resize()方法源码:小结
        • resize()方法案例讲解
          • treeifyBin()方法:小结
        • treeify()方法案例讲解
          • treeify()方法源码:小结
      • balanceInsertion()方法源码分析
        • 案例讲解
        • balanceInsertion()方法源码:小结
      • putTreeVal()方法源码分析
        • 案例讲解
        • putTreeVal()方法源码分析:小结
  • 章节目录

前言

HashMap继承于AbstractMap,实现了Map, Cloneable,Serializable接口。HashMap 允许key、value为null,不保证插入顺序。

源码分析

建议本内容从头到尾看,先看源码再看流程图,否则你会看的很懵逼。

HashMap主要属性及构造方法分析

主要介绍HashMap主要属性及hash值的计算方法、构造函数等

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> , Cloneable, Serializable{//默认初始化容器大小:1<<4=00001=10000=16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16    //最大容器大小:1<<30=1073741824static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子:0.75static final float DEFAULT_LOAD_FACTOR = 0.75f; //链表转化成红黑树的阈值:8static final int TREEIFY_THRESHOLD = 8; //红黑树转化成链表的阈值:6static final int UNTREEIFY_THRESHOLD = 6; //红黑树最小容器大小:64static final int MIN_TREEIFY_CAPACITY = 64;//节点数组transient Node<K, V>[] table;//HashMap映射的Set视图,可用于遍历操作transient Set<Map.Entry<K,V>> entrySet;//HashMap元素大小transient int size;//操作次数transient int modCount;//实际扩容阈值:阈值= 容器大小 * 加载因子int threshold;//实际加载因子final float loadFactor;//计算hash值static final int hash(Object key) {int h;//允许key值为空,返回0//计算hash值(h = key.hashCode()) ^ (h >>> 16)//比如 key=abc//"abc".hashCode() = 96354 =    0000 0000 0000 0001 0111 1000 0110 0010//^ 异或运算相同则0不同则1//"abc".hashCode() >>> 16 = 1 = 0000 0000 0000 0000 0000 0000 0000 0001//结果:96355            =       0000 0000 0000 0001 0111 1000 0110 0011return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}//有参构造,传入指定容器容量及加载因子public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: "+initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: "+loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}//有参构造,传入指定容器容量,但使用默认加载因子public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}//无参构造,使用默认加载因子及容器容量public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}}//有参构造,传入其它map对象,使用默认加载因子public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);}
}

tableSizeFor()方法源码分析

保证数组的长度永远是2次幂

    //返回给定目标容量大小的2次方(通过位移和或运算计算出阈值,基本位移2位或4位时基本已经确定大小了)。static final int tableSizeFor(int cap) {//如:cap = 18//n = 17 = 10001    int n = cap - 1;//01000 | 10001 = 11001 = 25    n |= n >>> 1;//00110 | 11001 = 11111 = 31    n |= n >>> 2;//00001 | 11111 = 11111 = 31    n |= n >>> 4;//0000 0000 | 0001 1111 = 0001 1111 = 31    n |= n >>> 8;//0000 0000 0000 0000 | 0000 0000 0001 1111 = 0000 0000 0001 1111 = 31    n |= n >>> 16;//如果n小于0返回1,否则大于最大容器容量返回最大容器容量,否则返回n+1    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

Node类源码分析

链表结构,HashMap的原始结构

/** node单向链表数据结构*/
static class Node <K,V> implements Map.Entry <K,V>{final int hash; //hash值final K key; //键V value; //值Node<K, V> next;//下一个节点/*** 有参构造函数, 创建节点及关系** @param hash* @param key* @param value* @param next*/Node(int hash, K key, V value, Node<K, V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}/*** 获取键** @return*/public final K getKey() {return key;}/** 获取值*/public final V getValue() {return value;}/** 拼接键+值字符串*/public final String toString() {return key + "=" + value;}/** hashCode值计算*/public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}/** 将键对应值替换*/public final V setValue(V newValue) {V oldValue = value;value = newValue; //将当前的值替换为传进来的值return oldValue; //返回之前的值}/*** 比较两个对象是否相等* @param o* @return*/public final boolean equals(Object o) {if (o == this)//如果当前对象相等return true; //直接返回trueif (o instanceof Map.Entry) {//判断对象类型是不是Map.EntryMap.Entry<?, ?> e = (Map.Entry<?, ?>) o;//强转为Map.Entryif (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))//如果两个键及两个值相等返回truereturn true;}return false;//返回false}
}

TreeNode类源码分析

    /*** 创建树形节点(内容过多,省略部分代码,后续讲解)*/static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; //父节点    TreeNode<K,V> left; //左子节点    TreeNode<K,V> right; //右子节点    TreeNode<K,V> prev; //需要删除的下一个节点    boolean red; //颜色:true红色 false黑色/*** 创建新节点.*/TreeNode(int hash, K key, V val, Node<K,V> next) {super(hash, key, val, next);}/*** 返回根节点.*/final TreeNode<K,V> root() {//从当前节点开始遍历        for (TreeNode<K, V> r = this, p; ; ) {//当前遍历节点的父节点为空,返回此节点            if ((p = r.parent) == null) return r;//每次遍历从父节点开始            r = p;}}}

rotateLeft()方法源码分析(左旋)

        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) {TreeNode<K, V> r, pp, rl; //r=右子节点 pp=父节点 rl=右节点的左子节点   if (p != null && (r = p.right) != null) { //p节点不为空且p节点的右子节点不为空      if ((rl = p.right = r.left) != null) //r节点的左子节点赋值给p节点的右子节点且不为空 rl.parent = p; //将r节点的左子节点由r节点改为p节点            if ((pp = r.parent = p.parent) == null) //将p节点的父节点成为r节点的父节点且父节点为空(root = r).red = false; //根节点改为r节点颜色为黑色            else if (pp.left == p) //如果p节点是父节点的左子节点                pp.left = r; //p父节点的左子节点改为r节点            else //如果p节点是父节点的右子节点                pp.right = r; //p父节点的右子节点改为r节点            r.left = p; //r节点的左子节点改为p节点            p.parent = r; //p节点的父节点改为r节点        }return root; //返回根节点    }

案例讲解


继续讲解

rotateRight()方法源码分析(右旋)

        static <K,V> TreeNode<K, V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) {TreeNode<K, V> l, pp, lr; //l=左子节点 pp=父节点 lr=左节点的右子节点        if (p != null && (l = p.left) != null) { //p节点不为空且p节点的左子节点不为空            if ((lr = p.left = l.right) != null) //l节点的右子节点赋值给p节点的左子节点且不为空lr.parent = p; //将l节点的右子节点由l节点改为p节点            if ((pp = l.parent = p.parent) == null) //将p节点的父节点成为l节点的父节点且父节点为空(root = l).red = false; //根节点改为l节点颜色为黑色            else if (pp.right == p) //如果p节点是父节点的右子节点                pp.right = l; //p父节点的右子节点改为l节点            else //如果p节点是父节点的左子节点                pp.left = l; //p父节点的左子节点改为l节点            l.right = p; //l节点的右子节点改为p节点            p.parent = l; //p节点的父节点改为l节点        }return root; //返回根节点    }

案例讲解

    //右旋static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root, TreeNode<K, V> p) {//l=p节点的左子节点,pp=p节点的父节点,lr=l节点的右子节点    TreeNode<K, V> l, pp, lr;//判断p节点不等于null且l节点等于p节点的左子节点不等于null    if (p != null && (l = p.left) != null) {if ((lr = p.left = l.right) != null) //判断l节点的右子节点等于p节点的左子节点赋值给lr节点不等null            lr.parent = p; //lr节点的父节点等于p节点        if ((pp = l.parent = p.parent) == null) //p节点的父节点等于l节点的父节点等于pp节点            (root = l).red = false; //root节点等于l节点,设为黑色        else if (pp.right == p) //pp节点的右子节点等于p节点           pp.right = l; //pp节点的右子节点等于l节点        else //否则pp节点的右子节点不是p节点            pp.left = l; //pp节点的左子节点等于l节点        l.right = p; //l节点的右子节点等于p节点        p.parent = l; //p节点的父节点等于l节点    }return root; //返回root节点}


继续讲解

左旋和右旋方法源码:小结

  • 左旋和右旋本质上没有什么区别,无非就是右边的左旋(逆时针),左边的右旋(顺时针)。旋转分为三种情况:旋转节点的左(右)子节点不等于null、旋转节点的父节点等于null、旋转节点是父节点的左(右)子节点。
  • 旋转节点的左(右)子节点不等于null。使父节点的左(右)子节点成为旋转节点的左(右)子节点,然后旋转节点的左(右)子节点指向父节点。建立父子关系
  • 旋转节点的父节点等于null。使其旋转节点成为根节点且颜色为黑色
  • 旋转节点是父节点的左(右)子节点。将父节点的左(右)子节点重新指向旋转节点的左(右)子节点
  • 最后将旋转节点和左(右)子节点重新建立关系

treeifyBin()方法源码分析

    //对于给定的哈希,替换bin中的所有链接节点,除非表太小,在这种情况下,调整大小。final void treeifyBin(Node<K,V>[] tab, int hash) {//n=数组长度,index=计算新元素的数组位置,e=数组位置节点int n, index;Node<K,V> e;//如果数组为null或者数组长度小于最小树形化容器64if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); //调整大小//否则通过hash值计算后下标数组不等于null(当前存放下标位置),重新建立连接else if ((e = tab[index = (n - 1) & hash]) != null) {//hd=头节点,t1=存放p节点,建立上下关系TreeNode<K,V> hd = null, tl = null;do {//将节点变成树节点TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null) //如果t1等于nullhd = p; //头节点为数组的第一个节点else { //否则p.prev = tl; //p节点的上一个节点赋值为t1节点tl.next = p; //t1的下一个节点赋值为p节点,建立上下关系}tl = p; //p节点赋值给t1节点} while ((e = e.next) != null); //每次遍历e节点替换为下一个节点,直到等于null结束循环if ((tab[index] = hd) != null) //将建立好关系的hd节点重新赋值给数组位置hd.treeify(tab); //将数组进行树形化}}

resize()方法源码分析

    /*** 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容*/final Node<K, V>[] resize() {Node<K, V>[] oldTab = table; //原节点数组,第一次为nullint oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0int oldThr = threshold; //原阈值,默认0int newCap, newThr = 0; //新容器、阈值为0if (oldCap > 0) { //如果原容器容量大于0 (非第一次扩容)if (oldCap >= MAXIMUM_CAPACITY) { //如果原容器容量大于最大值1的30次幂(10个亿左右)threshold = Integer.MAX_VALUE; //阈值等于Integer最大值(20个亿左右)return oldTab; //返回原数组}//新容器容量等于2倍原容器容量且小于最大容器容量且原容器容量大于默认容器值(16)else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; //新阈值等于原阈值的2倍} else if (oldThr > 0) //原阈值大于0(初始化构造给定的阈值)newCap = oldThr; //新容器容量等于传入的阈值else { //原容器容量和原阈值都为0(无参构造,第一次进来分配默认大小)newCap = DEFAULT_INITIAL_CAPACITY; //新容器容量为16newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //新阈值为 0.75(加载因子) * 16(默认容器容量) = 12}if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE); }threshold = newThr; //阈值替换为新阈值@SuppressWarnings({" rawtypes", "unchecked"})Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组table = newTab; //table替换为新数组if (oldTab != null) { //原数组不为空(拆分链表)for (int j = 0; j < oldCap; ++j) { //通过原数组长度遍历每个节点(保证不会越界)Node<K, V> e; //定义一个变量记录每次的节点if ((e = oldTab[j]) != null) { //节点赋值给e节点如果不为nulloldTab[j] = null; //将这个节点改为nullif (e.next == null) //如果e节点的下一个节点等于null(只有一个节点)newTab[e.hash & (newCap - 1)] = e; //重新计算节点再新数组中的位置else if (e instanceof TreeNode) //如果节点是树形结构((TreeNode<K, V>) e).split(this, newTab, j, oldCap); //进行树结构切割else { //否则e节点的下一个节点不等于null且不是树形结构Node<K, V> loHead = null, loTail = null; //lo(当前)头、尾节点(保持秩序)Node<K, V> hiHead = null, hiTail = null; //hi(新)头、尾节点(保持秩序)Node<K, V> next; //下一个节点do {next = e.next; //保存每次遍历的下一个节点if ((e.hash & oldCap) == 0) { //如果等于0,说明还是再这个数组里面if (loTail == null) //尾部插入所以判断尾节点等于nullloHead = e; //头节点等于当前遍历的节点else //尾节点不为nullloTail.next = e; //尾节点的下一个节点等于当前遍历的节点loTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系} else { //否则不等于0,说明再新的数组里面if (hiTail == null) //尾部插入所以判断尾节点等于nullhiHead = e; //头节点等于当前遍历的节点else //尾节点不为nullhiTail.next = e; //尾节点的下一个节点等于当前遍历的节点hiTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系}} while ((e = next) != null); //下一个节点不为null,继续遍历if (loTail != null) { //(当前)尾节点不为空loTail.next = null; //(当前)尾节点设为nullnewTab[j] = loHead; //将重新组合号的链表节点放入当前数组下标中}if (hiTail != null) { //(新)尾节点不为空hiTail.next = null; //(新)尾节点设为null//将重新组合号的链表节点放入新数组下标(当前遍历下标+原数组容器容量,反正不会超过新数组长度(2倍扩容))中newTab[j + oldCap] = hiHead; }}}}}return newTab;}

treeify()方法源码分析

    //形成从该节点链接的节点树。final void treeify(Node<K, V>[] tab) {//root=根节点TreeNode<K, V> root = null;//从当前节点开始遍历,定义next节点遍历接收链表的下一个节点for (TreeNode<K, V> x = this, next; x != null; x = next) {next = (TreeNode<K, V>) x.next; //x节点不等null将x节点的下一个节点赋值给next节点x.left = x.right = null; //将x节点的左右子节点设为nullif (root == null) { //如果root节点等于nullx.parent = null; //x节点的父节点等于nullx.red = false; //x节点为黑色root = x; //x节点为root(根)节点} else {K k = x.key; //获取x节点的keyint h = x.hash; //获取x节点的valueClass<?> kc = null; //获取key的类似类for (TreeNode<K, V> p = root; ; ) { //从root节点开始遍历int dir, ph;K pk = p.key; //获取root节点的keyif ((ph = p.hash) > h) //如果root节点的hash值大于x节点的hash值dir = -1; //dir等于-1表示在左边子节点else if (ph < h) //否则如果小于x节点的hash值dir = 1; //dir等于1表示在右边子节点else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) //否则不满足前两种情况,kc类似类等于null(没有类似类)或者类比较等于0(两个类比较相等)(不做讲解,理解即可)dir = tieBreakOrder(k, pk); //最终还是无法分辨大小,通过系统hashcode比较得到1或-1,达到平衡TreeNode<K, V> xp = p; //将p节点赋值xp节点if ((p = (dir <= 0) ? p.left : p.right) == null) { //p节点等于p节点的左或右子节点等于nullx.parent = xp; //x节点的父节点等于xp节点if (dir <= 0) //dir小于等于0xp.left = x; //xp节点的左子节点等于x节点else //否则大于0xp.right = x; //xp节点的右子节点等于x节点root = balanceInsertion(root, x); //平衡操作break;}}}}moveRootToFront(tab, root); //移动root节点到前面}

moveRootToFront()方法源码分析

    /*** 确保给定的根是其bin的第一个节点。 (移动root节点到前面)*/static <K, V> void moveRootToFront(Node<K, V>[] tab, TreeNode<K, V> root) {int n; //数组长度    if (root != null && tab != null && (n = tab.length) > 0) { //判断root节点不等于null且数组不等于null数组长度大于0       int index = (n - 1) & root.hash; //通过root的hash值计算下标        TreeNode<K, V> first = (TreeNode<K, V>) tab[index]; //获取下标第一个节点        if (root != first) { //判断新的root节点不等于第一个节点            Node<K, V> rn; //root节点的下一个节点            tab[index] = root; //数组下标替换为root节点            TreeNode<K, V> rp = root.prev; //root节点的上一个节点            if ((rn = root.next) != null) //判断root节点的下一个节点不等于null               ((TreeNode<K, V>) rn).prev = rp; //rn节点的上一个节点等于rp节点           if (rp != null) //判断rp节点不等于null                rp.next = rn; //rp节点的下一个节点等于rn节点(建立上下关系)           if (first != null) //判断first节点不等于null                first.prev = root; //first节点的上一个节点等于root           root.next = first; //root节点的下一个节点等于first            root.prev = null; //root节点的上一个节点等于null        }assert checkInvariants(root); //检查根节点是否满足红黑树节点规则    }}

checkInvariants()方法源码分析

主要进行红黑树结构的校验,从root节点开始,到每一个节点都要进行校验,直至为null返回true,结束校验。

    /*** 检查红黑树准确性*/static <K, V> boolean checkInvariants(TreeNode<K, V> t) {//tp=t节点的父节点、tl=t节点的左子节点、tr=t节点的右子节点、tb=t节点的上一个节点、tn=t节点的下一个节点TreeNode<K, V> tp = t.parent, tl = t.left, tr = t.right,tb = t.prev, tn = (TreeNode<K, V>) t.next;//判断tb节点不等于null且下一个节点不等于t节点if (tb != null && tb.next != t) return false; //返回false//判断tn节点不等于null且tn的上一个节点不等于t节点if (tn != null && tn.prev != t) return false; //返回false//判断tp节点不等于null且t节点不等于tp的左右节点if (tp != null && t != tp.left && t != tp.right) return false; //返回false判断tl节点不等于null且tl节点的父节点不等于t节点或者tl节点的hash值大于t节点的hash值if (tl != null && (tl.parent != t || tl.hash > t.hash)) return false; //返回false//判断tr节点不等于null且tr节点的父节点不等于t节点或者tr节点的hash值小于t节点的hash值if (tr != null && (tr.parent != t || tr.hash < t.hash)) return false; //返回false//判断t节点、tl节点、tr节点不等于null且都是红色if (t.red && tl != null && tl.red && tr != null && tr.red) return false; //返回false//tl节点不等于null且从tl节点递归检测为falseif (tl != null && !checkInvariants(tl)) return false; //返回false//tr节点不等于null且从tr节点递归检测为falseif (tr != null && !checkInvariants(tr)) return false; //返回falsereturn true; //返回true}

root()方法源码分析

    //返回节点的根节点final TreeNode<K,V> root() {//从当前节点开始遍历    for (TreeNode<K,V> r = this, p; ; ) {if ((p = r.parent) == null) //p节点等于r节点的上一个节点等于null            return r; //返回r节点       r = p; //不等于null,r节点等于p节点往上寻找  }}

find()方法源码分析

    //寻找左右子节点是否有相同节点final TreeNode<K,V> find(int h, Object k, Class<?> kc) {TreeNode<K,V> p = this; //p=当前节点    do {int ph, dir;K pk; //ph=p节点的hash值、dir=存放位置遍历、p=p节点key值        TreeNode<K,V> pl = p.left, pr = p.right, q; //pl=p节点的左子节点、 pr=p节点的右子节点、q=相同节点        if ((ph = p.hash) > h) //p节点hash值大于h            p = pl; //p节点等于pl节点        else if (ph < h) // p节点hash值小于h            p = pr; // p节点等于pr节点       else if ((pk = p.key) == k || (k != null && k.equals(pk))) //key值相同           return p; //返回p节点        else if (pl == null) //pl节点等于null           p = pr; //p节点等于pr节点        else if (pr == null) //pr节点等于null           p = pl; //p节点等于pl节点     //否则不满足前几种情况,kc类似类不等于null(有类似类)或者类比较不等于0(两个类比较不相等)(不做讲解,理解即可)   else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0)  p = (dir < 0) ? pl : pr; //dir小于0从p节点等于pl节点否则等于pr节点       else if ((q = pr.find(h, k, kc)) != null) //q节点等于pr节点开始匹配且不等于null           return q; //返回q节点       else //否则          p = pl; //p节点等于pl节点    } while (p != null); //p节点不等于null,进入循环结构   return null; // p节点等于null,节点循环,返回null}

put()方法源码分析

    /*** 将指定的值与指定的键关联,如果键已存在,则替换原来的值*/public V put(K key, V value) {//调用putVal()return putVal(hash(key), key, value, false, true);}//通过key计算hash值、传入key,value、 onlyIfAbsent等于false替换重复value、 evict表示创建模式,用于平衡操作final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {//tab=数组容器,p=数组的某下标头节点,n=数组长度,i=数组某下标位Node<K, V>[] tab; Node<K, V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0) //table等于null或者长度为0n = (tab = resize()).length; //初始化数组容器容量(无参构造,第一次put()时)if ((p = tab[i = (n - 1) & hash]) == null) //如果数组计算后的下标等于nulltab[i] = newNode(hash, key, value, null); //在此下标的位置上创建第一个节点else { //else说明此下标已存在节点元素Node<K, V> e;K k; //e = 节点元素,k = 键if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) //如果hash值与key相同e = p; //e节点等于下标p节点else if (p instanceof TreeNode) //如果p节点是树形节点//调用树形putTreeVal()方法e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); else { //key不相同且非树形结构for (int binCount = 0; ; ++binCount) { //遍历每个节点if ((e = p.next) == null) { //p节点的下一个节点等于nullp.next = newNode(hash, key, value, null); //p节点的下一个节点等于创建的新节点if (binCount >= TREEIFY_THRESHOLD - 1) //遍历次数大于树形阈值7treeifyBin(tab, hash); //进行树形化break; //结束循环}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break; //遍历过程中hash值与key值相同,结束循环p = e; //p替换为下一个节点,直到节点为null}}if (e != null) { //e节点不为null,说明有相同节点V oldValue = e.value; //获取e节点的value值if (!onlyIfAbsent || oldValue == null) // onlyIfAbsent等于false或者原value为nulle.value = value; //将节点的value值替换为传入的value值afterNodeAccess(e); //平衡操作,HashMap未实现代码,LinkedHashMap讲解return oldValue; //返回原value值}}++modCount; //操作次数加1if (++size > threshold) //如果大小加1后大于阈值resize(); //重置大小afterNodeInsertion(evict); //平衡操作,HashMap未实现代码,LinkedHashMap讲解return null; //返回null}

put()方法案例讲解

//通过key计算hash值、传入key,value、 onlyIfAbsent等于false替换重复value、 evict表示创建模式,用于平衡操作final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {//tab=数组容器,p=数组的某下标头节点,n=数组长度,i=数组某下标位Node<K, V>[] tab; Node<K, V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0) //table等于null或者长度为0n = (tab = resize()).length; //初始化数组容器容量(无参构造,第一次put()时)if ((p = tab[i = (n - 1) & hash]) == null) //如果数组计算后的下标等于nulltab[i] = newNode(hash, key, value, null); //在此下标的位置上创建第一个节点//省略部分代码...++modCount; //操作次数加1if (++size > threshold) //如果大小加1后大于阈值resize(); //重置大小afterNodeInsertion(evict); //平衡操作,HashMap未实现代码,LinkedHashMap讲解return null; //返回null}
    /*** 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容*/final Node<K, V>[] resize() {Node<K, V>[] oldTab = table; //原节点数组,第一次为nullint oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0int oldThr = threshold; //原阈值,默认0int newCap, newThr = 0; //新容器、阈值为0//省略部分代码else { //原容器容量和原阈值都为0(无参构造,第一次进来分配默认大小)newCap = DEFAULT_INITIAL_CAPACITY; //新容器容量为16newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //新阈值为 0.75(加载因子) * 16(默认容器容量) = 12}if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE); }threshold = newThr; //阈值替换为新阈值@SuppressWarnings({" rawtypes", "unchecked"})Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组table = newTab; //table替换为新数组if (oldTab != null) { //原数组不为空(拆分链表)//省略部分代码...}return newTab;
}

假设:创建了new HashMap()的无参构造函数

   //通过key计算hash值、传入key,value、 onlyIfAbsent等于false替换重复value、 evict表示创建模式,用于平衡操作final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {//tab=数组容器,p=数组的某下标头节点,n=数组长度,i=数组某下标位Node<K, V>[] tab; Node<K, V> p; int n, i;//省略部分代码...else { //else说明此下标已存在节点元素Node<K, V> e;K k; //e = 节点元素,k = 键if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) //如果hash值与key相同e = p; //e节点等于下标p节点else if (p instanceof TreeNode) //如果p节点是树形节点//调用树形putTreeVal()方法e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); else { //key不相同且非树形结构for (int binCount = 0; ; ++binCount) { //遍历每个节点if ((e = p.next) == null) { //p节点的下一个节点等于nullp.next = newNode(hash, key, value, null); //p节点的下一个节点等于创建的新节点if (binCount >= TREEIFY_THRESHOLD - 1) //遍历次数大于树形阈值7treeifyBin(tab, hash); //进行树形化break; //结束循环}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break; //遍历过程中hash值与key值相同,结束循环p = e; //p替换为下一个节点,直到节点为null}}if (e != null) { //e节点不为null,说明有相同节点V oldValue = e.value; //获取e节点的value值if (!onlyIfAbsent || oldValue == null) // onlyIfAbsent等于false或者原value为nulle.value = value; //将节点的value值替换为传入的value值afterNodeAccess(e); //平衡操作,HashMap未实现代码,LinkedHashMap讲解return oldValue; //返回原value值}}++modCount; //操作次数加1if (++size > threshold) //如果大小加1后大于阈值resize(); //重置大小afterNodeInsertion(evict); //平衡操作,HashMap未实现代码,LinkedHashMap讲解return null; //返回null}

第二次put

第三次put

第四次put

putVal()方法:小结
  • 前面介绍了以Map的无参构造进行的数组为空扩容、数组下标值替换、链表节点元素追加和链表值替换。
  • 数组为空。先进行第一次扩容,扩容完毕,再计算节点所在数组的位置,当计算后数组位置没有数据时,就会在这个数组的位置上创建第一个节点。然后累计操作和元素数量累加,完成第一次put。
  • 数组下标替换。第一次扩容后不会在进行扩容且当前数组下标元素也不为空,不会再相同位置创建节点元素,然后判断数组下标第一个节点key值和当前传入的key值相同,e节点不为空,然后替换为传入的value值,返回原value值。完成数组下标替换。
  • 链表节点元素追加。从第一个节点开始遍历,判断遍历节点的下一个节点为空,然后再创建一个新节点成为遍历节点的下一个节点,再判断是否需要树形化(树形化的条件要满足链表长度为8也就是说链表长度最多不超过8且数组容器值满足最小树形化大小64才可以进行树形化,后序会讲解),结束循环,e节点为空,不进行元素替换,累计操作次数和元素数量累加,完成链表节点追加。
  • 链表值替换。从第一个节点开始遍历,判断遍历节点的下一个节点不为空,再判断当前遍历节点的下一个节点key值和传入的key值相同,结束循环。e节点不为空,然后替换为传入的value值,返回原value值。完成链表值替换。

resize()方法案例讲解

再来讲解下指定容器大小的put()方法,顺便讲解扩容操作。

    //有参构造,传入指定容器容量及加载因子public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: "+initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: "+loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}//有参构造,传入指定容器容量,但使用默认加载因子public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}
    //返回给定目标容量大小的2次方(通过位移和或运算计算出阈值,基本位移2位或4位时基本已经确定大小了)。static final int tableSizeFor(int cap) {//n = 2-1 = 1 = 0001    int n = cap - 1;//0000 | 0001 = 0001 =1   n |= n >>> 1;//0000 | 0001 = 0001 =1    n |= n >>> 2;//0000 0000 | 0000 0001 = 0001 =1    n |= n >>> 4;//0000 0000 0000 | 0000 0000 0001 = 0001 =1    n |= n >>> 8;//0000 0000 0000 0000 | 0000 0000 0000 0001 = 0001 =1   n |= n >>> 16;//如果n小于0返回1,否则大于最大容器容量返回最大容器容量,否则返回n+1=2    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

阈值计算返回容器最接近的2次幂值,这样做都是为了扩容。先将容器值减一(为了防止本身传参就是2的幂,如果本身就是2的幂,那么计算完还是本身值)。注意此方法是计算最接近2次幂的值,下面用2,3,4,5分别讲解,强化理解。注意:|表示或位运算符,二进制的两个数之间有一个为1则为1否则为0,>>>表示无符号右移,左边最高位之间补0。

    /*** 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容*/final Node<K, V>[] resize() {Node<K, V>[] oldTab = table; //原节点数组,第一次为nullint oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0int oldThr = threshold; //原阈值,默认0int newCap, newThr = 0; //新容器、阈值为0//省略部分代码else if (oldThr > 0) //原阈值大于0(初始化构造给定的阈值)newCap = oldThr; //新容器容量等于传入的阈值//省略部分代码if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE); }threshold = newThr; //阈值替换为新阈值@SuppressWarnings({" rawtypes", "unchecked"})Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组table = newTab; //table替换为新数组//省略部分代码...return newTab;}

稍微介绍有参构造函数的put操作,主要讲解扩容

    /*** 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容*/final Node<K, V>[] resize() {Node<K, V>[] oldTab = table; //原节点数组,第一次为nullint oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0int oldThr = threshold; //原阈值,默认0int newCap, newThr = 0; //新容器、阈值为0if (oldCap > 0) { //如果原容器容量大于0 (非第一次扩容)if (oldCap >= MAXIMUM_CAPACITY) { //如果原容器容量大于最大值1的30次幂(10个亿左右)threshold = Integer.MAX_VALUE; //阈值等于Integer最大值(20个亿左右)return oldTab; //返回原数组}//新容器容量等于2倍原容器容量且小于最大容器容量且原容器容量大于默认容器值(16)else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; //新阈值等于原阈值的2倍}//省略部分代码if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE); }threshold = newThr; //阈值替换为新阈值@SuppressWarnings({" rawtypes", "unchecked"})Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组table = newTab; //table替换为新数组

        if (oldTab != null) { //原数组不为空(拆分链表)for (int j = 0; j < oldCap; ++j) { //通过原数组长度遍历每个节点(保证不会越界)Node<K, V> e; //定义一个变量记录每次的节点if ((e = oldTab[j]) != null) { //节点赋值给e节点如果不为nulloldTab[j] = null; //将这个节点改为nullif (e.next == null) //如果e节点的下一个节点等于null(只有一个节点)newTab[e.hash & (newCap - 1)] = e; //重新计算节点再新数组中的位置else if (e instanceof TreeNode) //如果节点是树形结构((TreeNode<K, V>) e).split(this, newTab, j, oldCap); //进行树结构切割else { //否则e节点的下一个节点不等于null且不是树形结构Node<K, V> loHead = null, loTail = null; //lo(当前)头、尾节点(保持秩序)Node<K, V> hiHead = null, hiTail = null; //hi(新)头、尾节点(保持秩序)Node<K, V> next; //下一个节点do {next = e.next; //保存每次遍历的下一个节点if ((e.hash & oldCap) == 0) { //如果等于0,说明还是再这个数组里面if (loTail == null) //尾部插入所以判断尾节点等于nullloHead = e; //头节点等于当前遍历的节点else //尾节点不为nullloTail.next = e; //尾节点的下一个节点等于当前遍历的节点loTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系} else { //否则不等于0,说明再新的数组里面if (hiTail == null) //尾部插入所以判断尾节点等于nullhiHead = e; //头节点等于当前遍历的节点else //尾节点不为nullhiTail.next = e; //尾节点的下一个节点等于当前遍历的节点hiTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系}} while ((e = next) != null); //下一个节点不为null,继续遍历if (loTail != null) { //(当前)尾节点不为空loTail.next = null; //(当前)尾节点设为nullnewTab[j] = loHead; //将重新组合号的链表节点放入当前数组下标中}if (hiTail != null) { //(新)尾节点不为空hiTail.next = null; //(新)尾节点设为nullnewTab[j + oldCap] = hiHead; //将重新组合号的链表节点放入新数组下标(当前遍历下标+原数组容器容量,反正不会超过新数组长度(2倍扩容))中}}}}}return newTab;}



    //对于给定的哈希,替换bin中的所有链接节点,除非表太小,在这种情况下,调整大小。final void treeifyBin(Node<K,V>[] tab, int hash) {//n=数组长度,index=计算新元素的数组位置,e=数组位置节点int n, index;Node<K,V> e;//如果数组为null或者数组长度小于最小树形化容器64if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); //调整大小//省略部分代码}







resize()方法源码:小结
  • Map的扩容一共分为三种情况:原容器值大于0、原阈值大于0、原容器值和阈值等于0。
  • 原容器值大于0。说明数组不为null且已分配容器大小、有元素的时候;如果原容器值大于最大容器值,阈值直接为Integer的最大值。返回原数组,此时不可能再扩容;否则新容器值等于原数组容器扩容两倍后小于最大容器值且原数组容器大于等于默认容器大小,那么新阈值为原阈值的两倍,如果不满足默认初始化容器大小就会进入另外一种情况用新容器值和加载因子重新计算新阈值。
  • 原阈值大于0。说明已分配数组的阈值但未分配容器大小,也就是初始化时定义好的初始容器值的情况,这种情况新容器大小等于阈值。
  • 原容器值和阈值等于0。说明不满足前面两种条件,无参构造,第一次put,未分配初始化阈值,使用默认的参数定义和计算新容器值和阈值。
  • 阈值是用来做数组扩容的条件,数组容器是存放元素的空间,阈值比数组容器小(如果加载因子为0.75,也就是数组长度的3/4)。
  • 如果原数组不为空,遍历原数组将每个下标的元素拆分重组后存放新数组中,也有三种情况:下标元素没有下一个节点、此下标节点为树形化节点、下标元素的下一个节点不为null且非树形化节点。
  • 下标元素没有下一个节点。如果下标元素的下一个节点等于null,重新计算元素存放再新数组的位置。
  • 此下标节点为树形化节点。如果是树形化节点就会进行树形化切割。
  • 下标元素的下一个节点不为null且非树形化节点。遍历下标的每一个节点,并且通过计算将节点位置分为原位置和新位置,等于0的就会连接成一串新链表存放新数组的(当前遍历位置)原下标位置,不等于0的也会连接成一串新链表存放再新数组的当前遍历位置+原容器值的位置,这样做不会超过新数组容器值;

resize()方法案例讲解

再来完整的讲解下树形化链表的方法

    //对于给定的哈希,替换bin中的所有链接节点,除非表太小,在这种情况下,调整大小。final void treeifyBin(Node<K,V>[] tab, int hash) {//n=数组长度,index=计算新元素的数组位置,e=数组位置节点int n, index;Node<K,V> e;//如果数组为null或者数组长度小于最小树形化容器64if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); //调整大小//否则通过hash值计算后下标数组不等于null(当前存放下标位置),重新建立连接else if ((e = tab[index = (n - 1) & hash]) != null) {//hd=头节点,t1=存放p节点,建立上下关系TreeNode<K,V> hd = null, tl = null;do {//将节点变成树节点TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null) //如果t1等于nullhd = p; //头节点为数组的第一个节点else { //否则p.prev = tl; //p节点的上一个节点赋值为t1节点tl.next = p; //t1的下一个节点赋值为p节点,建立上下关系}tl = p; //p节点赋值给t1节点} while ((e = e.next) != null); //每次遍历e节点替换为下一个节点,直到等于null结束循环if ((tab[index] = hd) != null) //将建立好关系的hd节点重新赋值给数组位置hd.treeify(tab); //将数组进行树形化}}



treeifyBin()方法:小结
  • treeifyBin()方法主要是用来将节点转换为树形节点,进行树形化必须得达到链表长度大于等于8且数组长度超过最小树形化容器64,才能转换为树结构。
  • 数组长度未达到要求时,重新进行扩容操作。
  • 数组长度达到要求时,通过当前传入的hash值获取数组下标元素不等于null,将节点转换为树形化节点,然后使得上一个节点和下一个节点建立双向关系。

treeify()方法案例讲解

    //形成从该节点链接的节点树。final void treeify(Node<K, V>[] tab) {//root=根节点TreeNode<K, V> root = null;//从当前节点开始遍历,定义next节点遍历接收链表的下一个节点for (TreeNode<K, V> x = this, next; x != null; x = next) {next = (TreeNode<K, V>) x.next; //x节点不等null将x节点的下一个节点赋值给next节点x.left = x.right = null; //将x节点的左右子节点设为nullif (root == null) { //如果root节点等于nullx.parent = null; //x节点的父节点等于nullx.red = false; //x节点为黑色root = x; //x节点为root(根)节点} else {K k = x.key; //获取x节点的keyint h = x.hash; //获取x节点的valueClass<?> kc = null; //获取key的类似类for (TreeNode<K, V> p = root; ; ) { //从root节点开始遍历int dir, ph;K pk = p.key; //获取root节点的keyif ((ph = p.hash) > h) //如果root节点的hash值大于x节点的hash值dir = -1; //dir等于-1表示在左边子节点else if (ph < h) //否则如果小于x节点的hash值dir = 1; //dir等于1表示在右边子节点else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) //否则不满足前两种情况,kc类似类等于null(没有类似类)或者类比较等于0(两个类比较相等)(不做讲解,理解即可)dir = tieBreakOrder(k, pk); //最终还是无法分辨大小,通过系统hashcode比较得到1或-1,达到平衡TreeNode<K, V> xp = p; //将p节点赋值xp节点if ((p = (dir <= 0) ? p.left : p.right) == null) { //p节点等于p节点的左或右子节点等于nullx.parent = xp; //x节点的父节点等于xp节点if (dir <= 0) //dir小于等于0xp.left = x; //xp节点的左子节点等于x节点else //否则大于0xp.right = x; //xp节点的右子节点等于x节点root = balanceInsertion(root, x); //平衡操作break;}}}}moveRootToFront(tab, root); //移动root节点到前面}

treeify()方法源码:小结
  • treeify()方法主要是用来将节点顺序转换为树形节点顺序,从当前节点开始往下遍历每一个节点。主要两种情况:根节点等于null的情况、根节点不等于null的情况。
  • 当根根等于null。当前遍历节点的父节点设为null,再将颜色设为黑色,成为根节点。
  • 根节点不等于null。从根节点开始遍历,通过当前遍历节点hash值和root节点往下的每个节点的hash值进行判断,如果当前遍历节点大于根节点往下遍历的节点,就成为当前根节点往下遍历节点的右子节点,否则就是左子节点,如果左或右子节点存在节点,那么下次遍历就会从左或右子节点开始,重新进行判断直至左或右子节点等于null时,建立关系。

balanceInsertion()方法源码分析

    //平衡插入,和TreeMap的fixAfterInsertion()方法类似static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {x.red = true; //进来的x节点为红色//循环定义xp=x节点的父节点,xpp=x节点的父父(爷)节点,xppl=父父(爷)节点的左子节点, xppr=父父(爷)节点的右子节点for (TreeNode<K,V> xp, xpp, xppl, xppr; ; ) {if ((xp = x.parent) == null) { //x节点的父节点赋值给xp节点判断等于nullx.red = false; //将x节点设为黑色return x; //返回x节点//否则xp节点为黑色或者xp节点的父节点赋值给xpp节点等于null} else if (!xp.red || (xpp = xp.parent) == null) return root; //返回root节点if (xp == (xppl = xpp.left)) { //如果xp节点等于xpp节点的左子节点赋值给xppl节点//判断xpp节点的右子节点赋值给xppr节点不等于null且xppr节点为红色if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; //xppr节点设为黑色xp.red = false; //xp节点设为黑色xpp.red = true; //xpp节点设为红色x = xpp; //x节点等于xpp节点} else { //否则xppr等于null或者为黑色if (x == xp.right) { //x节点等于xp节点的右子节点root = rotateLeft(root, x = xp); //从xp节点开始左旋//xp等于x节点的父节点等于null,xpp为null否则为xp的父节点xpp = (xp = x.parent) == null ? null : xp.parent; }if (xp != null) { //判断xp节点不等于nullxp.red = false; //xp节点设为黑色if (xpp != null) { //判断xpp节点不等于nullxpp.red = true; //xpp节点设为红色root = rotateRight(root, xpp); //从xpp节点开始右旋}}}} else { //否则xp节点不等于xpp节点的左子节点//判断xpp节点的左子节点赋值给xppl节点不等于null且xppl节点为红色if (xppl != null && xppl.red) { xppl.red = false; //xppl节点设为黑色xp.red = false; //xp节点设为黑色xpp.red = true; //xpp节点设为红色x = xpp; //x节点等于xpp节点} else { //否则xppl等于null或者为黑色if (x == xp.left) { //x节点等于xp节点的左子节点root = rotateRight(root, x = xp); //从xp节点开始左旋//xp等于x节点的父节点等于null,xpp为null否则为xp的父节点xpp = (xp = x.parent) == null ? null : xp.parent; }if (xp != null) { //判断xp节点不等于nullxp.red = false; //xp节点设为黑色if (xpp != null) { //判断xpp节点不等于nullxpp.red = true; //xpp节点设为红色root = rotateLeft(root, xpp); //从xpp节点开始右旋}}}}}}

案例讲解


    //右旋static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root, TreeNode<K, V> p) {//l=p节点的左子节点,pp=p节点的父节点,lr=l节点的右子节点    TreeNode<K, V> l, pp, lr;//判断p节点不等于null且l节点等于p节点的左子节点不等于null    if (p != null && (l = p.left) != null) {if ((lr = p.left = l.right) != null) //判断l节点的右子节点等于p节点的左子节点赋值给lr节点不等null            lr.parent = p; //lr节点的父节点等于p节点        if ((pp = l.parent = p.parent) == null) //p节点的父节点等于l节点的父节点等于pp节点            (root = l).red = false; //root节点等于l节点,设为黑色        else if (pp.right == p) //pp节点的右子节点等于p节点           pp.right = l; //pp节点的右子节点等于l节点        else //否则pp节点的右子节点不是p节点            pp.left = l; //pp节点的左子节点等于l节点        l.right = p; //l节点的右子节点等于p节点        p.parent = l; //p节点的父节点等于l节点    }return root; //返回root节点}


//左旋
static <K,V> TreeNode <K,V> rotateLeft(TreeNode <K,V> root, TreeNode <K,V> p)
{//r=p节点的右子节点,pp=p节点的父节点,rl=r节点的左子节点    TreeNode <K,V> r, pp, rl;//判断p节点不等于null且r节点等于p节点的右子节点不等于null    if(p != null && (r = p.right) != null){if((rl = p.right = r.left) != null) //判断r节点的左子节点等于p节点的右子节点赋值给rl节点不等null            rl.parent = p; //rl节点的父节点等于p节点        if((pp = r.parent = p.parent) == null) //p节点的父节点等于r节点的父节点等于pp节点            (root = r).red = false; //root节点等于r节点,设为黑色        else if(pp.left == p) //pp节点的左子节点等于p节点            pp.left = r; //pp节点的左子节点等于r节点        else //否则pp节点的左子节点不是p节点            pp.right = r; //pp节点的右子节点等于r节点        r.left = p; //r节点的左子节点等于p节点        p.parent = r; //p节点的父节点等于r节点    }return root; //返回root节点
}




    /*** 确保给定的根是其bin的第一个节点。 (移动root节点到前面)*/static <K, V> void moveRootToFront(Node<K, V>[] tab, TreeNode<K, V> root) {int n; //数组长度    if (root != null && tab != null && (n = tab.length) > 0) { //判断root节点不等于null且数组不等于null数组长度大于0       int index = (n - 1) & root.hash; //通过root的hash值计算下标        TreeNode<K, V> first = (TreeNode<K, V>) tab[index]; //获取下标第一个节点        if (root != first) { //判断新的root节点不等于第一个节点            Node<K, V> rn; //root节点的下一个节点            tab[index] = root; //数组下标替换为root节点            TreeNode<K, V> rp = root.prev; //root节点的上一个节点            if ((rn = root.next) != null) //判断root节点的下一个节点不等于null               ((TreeNode<K, V>) rn).prev = rp; //rn节点的上一个节点等于rp节点           if (rp != null) //判断rp节点不等于null                rp.next = rn; //rp节点的下一个节点等于rn节点(建立上下关系)           if (first != null) //判断first节点不等于null                first.prev = root; //first节点的上一个节点等于root           root.next = first; //root节点的下一个节点等于first            root.prev = null; //root节点的上一个节点等于null        }assert checkInvariants(root); //检查根节点是否满足红黑树节点规则    }}

    /*** 检查红黑树准确性*/static <K, V> boolean checkInvariants(TreeNode<K, V> t) {//tp=t节点的父节点、tl=t节点的左子节点、tr=t节点的右子节点、tb=t节点的上一个节点、tn=t节点的下一个节点TreeNode<K, V> tp = t.parent, tl = t.left, tr = t.right,tb = t.prev, tn = (TreeNode<K, V>) t.next;//判断tb节点不等于null且下一个节点不等于t节点if (tb != null && tb.next != t) return false; //返回false//判断tn节点不等于null且tn的上一个节点不等于t节点if (tn != null && tn.prev != t) return false; //返回false//判断tp节点不等于null且t节点不等于tp的左右节点if (tp != null && t != tp.left && t != tp.right) return false; //返回false判断tl节点不等于null且tl节点的父节点不等于t节点或者tl节点的hash值大于t节点的hash值if (tl != null && (tl.parent != t || tl.hash > t.hash)) return false; //返回false//判断tr节点不等于null且tr节点的父节点不等于t节点或者tr节点的hash值小于t节点的hash值if (tr != null && (tr.parent != t || tr.hash < t.hash)) return false; //返回false//判断t节点、tl节点、tr节点不等于null且都是红色if (t.red && tl != null && tl.red && tr != null && tr.red) return false; //返回false//tl节点不等于null且从tl节点递归检测为falseif (tl != null && !checkInvariants(tl)) return false; //返回false//tr节点不等于null且从tr节点递归检测为falseif (tr != null && !checkInvariants(tr)) return false; //返回falsereturn true; //返回true}

再来讲解左节点的平衡方式

    //平衡插入,和TreeMap的fixAfterInsertion()方法类似static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {x.red = true; //进来的x节点为红色//循环定义xp=x节点的父节点,xpp=x节点的父父(爷)节点,xppl=父父(爷)节点的左子节点, xppr=父父(爷)节点的右子节点for (TreeNode<K,V> xp, xpp, xppl, xppr; ; ) {if ((xp = x.parent) == null) { //x节点的父节点赋值给xp节点判断等于nullx.red = false; //将x节点设为黑色return x; //返回x节点//否则xp节点为黑色或者xp节点的父节点赋值给xpp节点等于null} else if (!xp.red || (xpp = xp.parent) == null) return root; //返回root节点if (xp == (xppl = xpp.left)) { //如果xp节点等于xpp节点的左子节点赋值给xppl节点//判断xpp节点的右子节点赋值给xppr节点不等于null且xppr节点为红色if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; //xppr节点设为黑色xp.red = false; //xp节点设为黑色xpp.red = true; //xpp节点设为红色x = xpp; //x节点等于xpp节点} else { //否则xppr等于null或者为黑色if (x == xp.right) { //x节点等于xp节点的右子节点root = rotateLeft(root, x = xp); //从xp节点开始左旋//xp等于x节点的父节点等于null,xpp为null否则为xp的父节点xpp = (xp = x.parent) == null ? null : xp.parent; }if (xp != null) { //判断xp节点不等于nullxp.red = false; //xp节点设为黑色if (xpp != null) { //判断xpp节点不等于nullxpp.red = true; //xpp节点设为红色root = rotateRight(root, xpp); //从xpp节点开始右旋}}}}//省略部分代码...}}}




balanceInsertion()方法源码:小结

  • 插入平衡操作主要为了保持插入的每个节点,都能满足红黑树的特性,保持树形结构

putTreeVal()方法源码分析

    /*** 树版本的putVal*/final TreeNode<K, V> putTreeVal(HashMap<K, V> map, Node<K, V>[] tab, int h, K k, V v) {Class<?> kc = null; //key类似的类boolean searched = false; //搜索TreeNode<K, V> root = (parent != null) ? root() : this; //如果父节点不等于null,调用root()方法获取根节点,否则根节点等于当前节点for (TreeNode<K, V> p = root; ; ) { //从root节点开始遍历int dir, ph;K pk; //dir=存放位置变量,ph=p节点hash值、pk=p节点key值if ((ph = p.hash) > h) //如果p节点的hash值大于hdir = -1; //dir等于-1表示左边子节点else if (ph < h) //如果p节点的hash值小于hdir = 1; //dir等于1表示在右边子节点else if ((pk = p.key) == k || (k != null && k.equals(pk))) //如果key值都相等return p; //返回p节点//否则不满足前两种情况,kc类似类等于null(没有类似类)或者类比较等于0(两个类比较相等)(不做讲解,理解即可)else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) { if (!searched) { //搜索变量为false(不做讲解理解即可)TreeNode<K, V> q, ch;searched = true; //将搜索变量改为true//p节点左子节点不等于null,从左子节点开始往下寻找,p节点的右子节点不等于null,从右子节点开始往下寻找,如果匹配相同节点if (((ch = p.left) != null && (q = ch.find(h, k, kc)) != null) || ((ch = p.right) != null && (q = ch.find(h, k, kc)) != null)) return q; //返回q节点}dir = tieBreakOrder(k, pk); //最终还是无法分辨大小,通过系统hashcode比较得到1或-1,达到平衡}TreeNode<K, V> xp = p; //p节点赋值给xp节点if ((p = (dir <= 0) ? p.left : p.right) == null) { //判断dir小于等于0,p节点等于p的左子节点否则等于p的右子节点等于nullNode<K, V> xpn = xp.next; //xp节点的下一个节点TreeNode<K, V> x = map.newTreeNode(h, k, v, xpn); //创建一个新的链表节点且建立下一个节点关系if (dir <= 0) //dir节点小于等于0xp.left = x; //xp节点的左子节点等于x节点else //否则dir大于0xp.right = x; //xp节点的右子节点等于x节点xp.next = x; //xp节点的下一个节点等于x节点x.parent = x.prev = xp; //xp节点等于x节点的上一个节点等于x节点的父节点if (xpn != null) //xpn节点不等于null((TreeNode<K, V>) xpn).prev = x; //xpn的上一个节点等于x节点moveRootToFront(tab, balanceInsertion(root, x)); //平衡后移动root节点到前面return null; //返回null}}}
    //返回节点的根节点final TreeNode<K,V> root() {//从当前节点开始遍历    for (TreeNode<K,V> r = this, p; ; ) {if ((p = r.parent) == null) //p节点等于r节点的上一个节点等于null            return r; //返回r节点       r = p; //不等于null,r节点等于p节点往上寻找  }}//寻找左右子节点是否有相同节点final TreeNode<K,V> find(int h, Object k, Class<?> kc) {TreeNode<K,V> p = this; //p=当前节点    do {int ph, dir;K pk; //ph=p节点的hash值、dir=存放位置遍历、p=p节点key值        TreeNode<K,V> pl = p.left, pr = p.right, q; //pl=p节点的左子节点、 pr=p节点的右子节点、q=相同节点        if ((ph = p.hash) > h) //p节点hash值大于h            p = pl; //p节点等于pl节点        else if (ph < h) // p节点hash值小于h            p = pr; // p节点等于pr节点       else if ((pk = p.key) == k || (k != null && k.equals(pk))) //key值相同           return p; //返回p节点        else if (pl == null) //pl节点等于null           p = pr; //p节点等于pr节点        else if (pr == null) //pr节点等于null           p = pl; //p节点等于pl节点     //否则不满足前几种情况,kc类似类不等于null(有类似类)或者类比较不等于0(两个类比较不相等)(不做讲解,理解即可)   else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0)  p = (dir < 0) ? pl : pr; //dir小于0从p节点等于pl节点否则等于pr节点       else if ((q = pr.find(h, k, kc)) != null) //q节点等于pr节点开始匹配且不等于null           return q; //返回q节点       else //否则          p = pl; //p节点等于pl节点    } while (p != null); //p节点不等于null,进入循环结构   return null; // p节点等于null,节点循环,返回null}

案例讲解






putTreeVal()方法源码分析:小结

  • 树形化插入和treeify()方法有很多相似之处, treeify()方法将树形化结构定义好之后,只要按照这种顺序去插入即可,这里就不做重复的讲解了。不过每次树形化插入,都需要去维护链表的结构,再去进行树的平衡处理。

章节目录

上一篇:集合底层源码分析之TreeMap(二)
下一篇:集合底层源码分析之HashMap《下》(三)

集合底层源码分析之HashMap《上》(三)相关推荐

  1. Jaca集合(四)Vector集合底层源码分析

    Vector的基本介绍: (1)Vector类的定义说明:我们进入源码界面进行查看: public class Vector<E>extends AbstractList<E> ...

  2. 【集合框架】JDK1.8源码分析之HashMap(一)

    转载自  [集合框架]JDK1.8源码分析之HashMap(一) 一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大 ...

  3. JAVA集合专题+源码分析

    文章目录 Java集合专题 集合和数组的区别 数组 集合 区别 集合体系结构介绍 单列集合 [Collection ] Collection接口 迭代器 迭代器原理 增强for循环 List接口 对集 ...

  4. HBase源码分析之HRegion上compact流程分析(三)

    在<HBase源码分析之HRegion上compact流程分析(二)>一文中,我们没有讲解真正执行合并的CompactionContext的compact()方法.现在我们来分析下它的具体 ...

  5. ArrayList底层源码分析

    声明:本文为作者原创,请勿装载,如过转载,请注明转载地址 文章目录 ArrayList底层源码分析 1. 继承Serializable接口 2. 继承Cloneable接口 2.1 浅拷贝 2.2 深 ...

  6. 【mybatis源码】 mybatis底层源码分析

    [mybatis源码] mybatis底层源码分析 1.测试用例 2.开撸源码 2.1 SqlSessionFactory对象的创建与获取 2.2 获取SqlSession对象 2.3 获取接口的代理 ...

  7. 视频教程-Spring底层源码分析-Java

    Spring底层源码分析 鲁班学院-子路老师曾就职于谷歌.天猫电商等多家互联网公司,历任java架构师.研发经理等职位,参与并主导千万级并发电商网站与后端供应链研发体系搭建,多次参与电商大促活动技术保 ...

  8. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(一)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

  9. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(二)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

最新文章

  1. C++测试申请最大内存空间
  2. 根据姓名得到名字首字母
  3. eclipse中快捷键
  4. 箱式图 分组_小白学R(三):重复测量数据的箱式图上的p值如何改大小呢?
  5. 【leetcode记录03】动态规划
  6. adonis.js mysql_Adonis.js——数据库基本操作
  7. zynq+linux固化程序,如何在 Zynq UltraScale+ MPSoC 上实现 Linux UIO 设计
  8. LeetCode MySQL 1607. 没有卖出的卖家
  9. 主板扩展槽图解_子板:扩展到主板
  10. 软件工程系组织12级学生到工商学院参加比赛
  11. java自行车e2_摩托罗拉E2 JAVA应用程序安装指南
  12. Choerodon猪齿鱼实践之Webhook配置
  13. 佐客牛排机器人餐厅_“机器人餐厅”来了
  14. 使用微博自动记录俯卧撑个数
  15. drawArc 画扇形 画弧线
  16. openwrt 认证收费_在OpenWrt中安装Wiwiz实现portal认证
  17. 爬了3000万QQ用户数据,挖出了花千骨赵丽颖的QQ号
  18. python爬取信息案例——部分国家东京奥运奖牌
  19. BAV99的ESD保护作用解析
  20. 三种js轮播实现方式详解(看一遍就会)

热门文章

  1. 推荐几个比较好的日语翻译网站还有软件(我自己还没看过)
  2. 网线传输速度测试_如何测验网线的好坏,传输速度等
  3. 【无标题】旧金山大学算法可视化网站
  4. 牛客网-推理判断练习
  5. java hex 编码_1-Hex编码
  6. APS科普:如何缩短制造提前期?
  7. UE4 Geometry Polygon
  8. Python实现九宫格解锁
  9. 梦幻西游中心服务器,梦幻西游2服务器回忆史——追忆太和殿
  10. 运算符,++,--,*,/和%的运用和区别