HashMap源码学习:红黑树原理详解

news2024/12/23 5:59:13

前言

JDK1.8后的HashMap引入了红黑树,在学习HashMap源码之前,了解了红黑树原理,及其如何通过代码进行实现后,在整体的看HashMap的源码就会简单很多。

概述

红黑树的特性

在这里插入图片描述

  • 根节点必须是黑色节点。
  • 节点是红色或黑色。
  • 所有叶子都是黑色。(叶子是Null节点)
  • 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
  • 从任一结点到其每个叶子的所有路径都包含相同数目的黑色节点。

展示叶子节点

在这里插入图片描述

在HashMap源码中红黑树也是需要满足以上条件的,当我们在插入时可能会不满足以上特性,这时候就需要进行位置的调整了,如变色、左旋、右旋等操作来保持红黑树的特性。HashMap中红黑树每次插入节点都是红色节点,如果是插入节点是黑色的,则不满足特性。
如:不满足特性5,不同路径的黑色节点数量不一致。

在这里插入图片描述

变色平衡

在这里插入图片描述

以上结构不符合红黑树特性,插入节点为红色,且父亲节点也为红色,这时需要调整树形结构。我们可以知道在插入红色节点35之前,红黑树肯定是平衡的(不平衡则会进行平衡调节),由于插入节点的父亲是红色,可以判断其爷爷节点50肯定是黑色的,这时候我们判断叔叔节点70是红色的,如果父亲节点跟叔叔节点都是红色的,此时对父亲节点和叔叔节点进行变色调整,由黑色调整为红色,爷爷节点需要从黑色变为空色,但由于爷爷节点是根节点,必须为黑色,所以在变红色后,需要判断是否根节点,如果是根节点则变为黑色,此时红黑S树就平衡了。如果爷爷节点不是根节点,此时就还是红色,有可能爷爷的父亲节点存在红色的情况,所以我们需要将爷爷节点作为插入节点,重复进行平衡操作,直接平衡。

在这里插入图片描述

右旋平衡

在这里插入图片描述

插入红色节点34时,由于其父亲也是红色,不满足红黑树特性。这时候由于其叔叔节点为空,非红色节点,所以不能直接通过变色解决平衡问题。由于插入节点34在父节点35的左边,父亲节点35在爷爷节点40左右,这时候我们可以以爷爷节点40作为旋转点进行右旋。

在这里插入图片描述

  1. 将父节点35的父亲设置为太爷爷节点50
  2. 判断爷爷节点40是太爷爷节点50的左边还是右边,如果是左边,则将太爷爷50的左边设置为父亲节点35,右边则设置右边为父亲节点35。
  3. 将父亲节点35的右边设置为爷爷节点
  4. 将爷爷节点40的父亲设置为父亲夜店35
  5. 将爷爷节点40的左边设置为父亲节点35的右边,图中由于父亲节点35的右边是一个NULL节点,所以爷爷节点40的左边也是一个NULL节点。

通过以上步骤,我们发现树形结构还是不平衡,根节点到每个叶子节点黑色数量不是一致的。所以我们还需要调整父亲节点35的颜色为黑色,爷爷节点40颜色为红色,这样达到平衡效果。

在这里插入图片描述

左旋平衡

在这里插入图片描述

上图中存在两个连续红色的子父节点,不满足红黑色特性,需要通过平衡处理。

  1. 判断其叔叔节点是否存在且为红色,如果不满足条件,则无法直接通过变色解决平衡问题。
  2. 判断子节点34在父节点35的左边还是右边,如果在右边,则以父亲节点35作为旋转节点进行左旋。

在这里插入图片描述

  1. 将插入节点38的父亲指向爷爷节点40。
  2. 将父亲节点35的父亲指向插入节点38。
  3. 将父亲节点35的右边指向插入节点38的左边节点,这里由于插入节点的左边节点为NULL,所以父亲节点35的右边节点也会为NULL。
  4. 判断父亲节点35位于爷爷节点的左边还是右边,并将对应的指向插入节点38。

由以上步骤,可以发现此时的树形结构跟我们右旋时所遇到的树形情况一样,这时候再将父亲节点35作为插入节点进行平衡,这里参考本文章的右旋平衡章节。

正文

通过红黑树的特性,我们了解其原理,现在看看HashMap中的平衡是如何进行代码实现的。

红黑树平衡方法:balanceInsertion

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {

            x.red = true;
            //X为插入节点,将其颜色设置为红色
            //xp为插入节点的父亲
            //xpp为插入节点的爷爷
            //xppl、xxpl为其叔叔节点
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
            		//如果插入节点的父亲为null,证明是其跟节点,此时设置为黑色即可
                if ((xp = x.parent) == null) {
                    x.red = false;
                    return x;
                }
                //如果父亲节点为黑色或者爷爷节点为空,此时证明,父亲节点为根节点。根节点又是黑色,所以插入一个红色节点不会破坏平衡
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
                
                if (xp == (xppl = xpp.left)) {
                	//如果父亲节点是爷爷节点的左边时

					//获取位于右边的叔叔节点,如果叔叔节点不为空且是红色节点。
                    if ((xppr = xpp.right) != null && xppr.red) {
                    	//此时进行变色,将父亲跟叔叔节点变为黑色,爷爷节点变为黑色
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        //将爷爷节点设置为插入节点,因为爷爷节点变成了红色,可能会破坏平衡,所以需要重新走一边平衡流程
                        x = xpp;
                    }
                    else {
                    	//进入这边条件,则证明其右边的叔叔节点为空
                    	
                    	//判断插入节点是否是父节点的右边
                        if (x == xp.right) {
                        	//这种情况跟本文“左旋平衡”中图示情况一致
                        	//以父亲作为旋转节点进行左旋,左旋之后此时的树还不是平衡状态,此时还是存在两个连续的红色节点
                            root = rotateLeft(root, x = xp);
                           //重新定义父亲节点,并给爷爷节点赋值
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        
                        if (xp != null) {
                        	//将父亲节点设置为黑色
                            xp.red = false;
                            if (xpp != null) {
                            		//将爷爷节点设置为红色,并进行右旋,此时红黑树就平衡了。
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else {
                	//进入这里,证明父亲节点是爷爷节点的右边。这里的操作跟上面条件里面是操作是反过来的。

					//判断是否存在红色的叔叔节点
                    if (xppl != null && xppl.red) {
                    	//将父亲节点、叔叔节点设置为黑色,将爷爷节点设置为红色,此时有可能会破坏平衡,需要将爷爷节点作为插入节点,继续走平衡流程
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                    	//如果插入节点为父亲节点的左边
                        if (x == xp.left) {
                        	//先进行右旋
                            root = rotateRight(root, x = xp);
                            //重新定义父亲节点,并给爷爷节点赋值
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                        	//将父亲节点设置为黑色
                            xp.red = false;
                            if (xpp != null) {
                            //将爷爷节点设置为红色,以爷爷节点作为旋转节点进行左旋
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

rotateLeft方法,左旋见方法2详解

rotateRight方法,右旋见方法3详解

红黑树左旋方法:rotateLeft

在这里插入图片描述

上图中的旋转节点为:节点35

        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            TreeNode<K,V> r, pp, rl;
            //r为旋转节点的右边节点  简称:旋转右节点
            //pp为旋转节点的父亲节点  简称:旋转父节点
            //rl旋转右节点的左边节点  简称:旋右左节点
            
            //判断旋转节点和旋转右节点不为null,并对旋转右节点进行赋值
            if (p != null && (r = p.right) != null) {
            	//判断旋右左节点不为空,并将旋转节点的右边设置为旋右的左节点,并对旋右左节点rl进行赋值
                if ((rl = p.right = r.left) != null)
                	//将旋右左节点的父亲设置为旋转节点
                    rl.parent = p;
                //旋转右节点的父亲设置为其爷爷节点,并判断其爷爷节点是否为空
                if ((pp = r.parent = p.parent) == null)
                	//为空则证明所处位置为根节点,将其设置为黑色
                    (root = r).red = false;
                //判断旋转节点是在其父亲节点左边还是右边
                else if (pp.left == p)
                	//将旋转节点父亲的左边设置为旋转右节点
                    pp.left = r;
                else
               	 //将旋转节点父亲的右边设置为旋转右节点
                    pp.right = r;
                //将旋转右节点的左边设置为旋转节点
                r.left = p;
                //将旋转节点的父亲设置为旋转右节点
                p.parent = r;
            }
            return root;
        }

红黑树右旋方法:rotateRight

右旋跟左旋是对应反着来的。

在这里插入图片描述
上图中的旋转节点为:节点40

static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
            TreeNode<K,V> l, pp, lr;
            //l为旋转节点的右边节点  简称:旋转左节点
            //pp为旋转节点的父亲节点  简称:旋转父节点
            //lr旋转右节点的右边节点  简称:旋左右节点
            
  			//判断旋转节点和旋转左节点不为null,并对旋转左节点进行赋值
            if (p != null && (l = p.left) != null) {
          	  	//判断旋左左节点不为空,并将旋转节点的左边设置为旋左的右节点,并对旋左右节点lr进行赋值
                if ((lr = p.left = l.right) != null)
                	//将旋左右节点的父亲设置为旋转节点
                    lr.parent = p;
                //旋转左节点的父亲设置为其爷爷节点,并判断其爷爷节点是否为空
                if ((pp = l.parent = p.parent) == null)
                	//为空则证明所处位置为根节点,将其设置为黑色
                    (root = l).red = false;
                else if (pp.right == p)
                	//将旋转节点父亲的右边设置为旋转左节点
                    pp.right = l;
                else
                	//将旋转节点父亲的右边设置为旋转右节点
                    pp.left = l;
                //将旋转左节点的右边设置为旋转节点
                l.right = p;
                //将旋转节点的父亲设置为旋转左节点
                p.parent = l;
            }
            return root;
        }

红黑树添加方法:putTreeVal

上面讲解了红黑树的平衡操作,现在进入红黑树的添加节点方法中。

        final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            //获取其根节点
            TreeNode<K,V> root = (parent != null) ? root() : this;
            for (TreeNode<K,V> p = root;;) {
            	//p:根节点
            	//dir:-1代表左边节点方向 1代表右边节点方向
            	//hash:插入节点的key hash值
            	//key:插入节点的key值
            	//pk:当前所在节点的key值
                int dir, ph; K pk;
                //判断插入节点key的hash值与当前节点比较大小,确定走向
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                //判断当前节点的key是否相等,相等则查到了位置
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                //有可能存入的key实现了比较器,所以这里会尝试获取比较器来重新计算下一节点树的方向
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        //标记为已经查找过了
                        searched = true;
                        //尝试从根节点的左节点或右节点进行查询,查询到该节点时,则返回该节点
                        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;
                    }
                    dir = tieBreakOrder(k, pk);
                }

                TreeNode<K,V> xp = p;
                //判断此时的方向是往哪边查找
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                	//获取当前节点的下个节点值
                    Node<K,V> xpn = xp.next;
                    //如果不存在该相同的key信息,则创建新的节点,
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    //判断新插入节点放在左边还是右边
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    //当前节点的链表下个节点指向插入节点
                    xp.next = x;
                    //将插入节点的红黑树父节点,及其链表的上个节点指向当前节点。
                    x.parent = x.prev = xp;
                    //如果当前节点原先的链表下个节点不为空,则将其链表的上个节点指向新插入的节点。
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    //将ROOT节点的位置放到数组的第一个位置中
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

到最后这块的代码涉及了链表的赋值,有读者可能有疑问,明明是红黑树怎么又对链表进行操作了?
HashMap1.8引入了红黑树,当链表节点个人>=8个时,会转为红黑树。当节点个数<=6个时,会转为链表。所以我们在操作红黑树的插入操作时,需要记录节点的上个节点,及其下个节点的指向,以便后续转为链表。

在这里插入图片描述

我们可以看到红黑树节点都有prev属性,而TreeNode继承了Node,所以也有了next属性。

红黑树查询方法:find

 final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
 			//将红黑树所在的当前节点赋值给p
            TreeNode<K,V> p = this;
            do {
                int ph, dir; K pk;
                TreeNode<K,V> pl = p.left, pr = p.right, q;
                //比较当前节点的hash值大小,决定查找方向
                if ((ph = p.hash) > h)
                    p = pl;
                else if (ph < h)
                    p = pr;
                //能走到这里证明hash值是一样的,如果key值一样,则是查找到了
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                //能走到这里证明hash值是一样的(hash冲突情况),如果左边节点为null,则赋值为右边,从右边进行查询,否则从左边查询
                else if (pl == null)
                    p = pr;
                else if (pr == null)
                    p = pl;
                //走到这里,证明还没办法确定走向,所以尝试获取key是否实现Comparable,并重新计算出其方向
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                //递归,从右边节点进行查找,如果查不到则从左边进行查找
                else if ((q = pr.find(h, k, kc)) != null)
                    return q;
                else
                    p = pl;
            } while (p != null);
            //找不到返回null
            return null;
        }

红黑树删除方法:removeTreeNode

final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                return;
            //计算出所在的数组下标
            int index = (n - 1) & hash;
            //获取当前下标中的第一个值,也就是跟节点
            TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
            //获取当前移除节点的下个节点,及其上个节点的指向值
            TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
            //移除节点上个节点为空,则证明其排在第一位,需要将数组下标中的第一个节点重新赋值
            if (pred == null)
                tab[index] = first = succ;
            else
            	//将上个节点的next指向移除节点的下个节点
                pred.next = succ;
            //下个节点不为空,则将下个节点的prev 指向移除节点的prev节点。
            if (succ != null)
                succ.prev = pred;
            
            if (first == null)
                return;
            //如果第一个节点的父节点有值,则获取当前红黑树的根节点
            if (root.parent != null)
                root = root.root();
            //如果根节点不存在 ,节点树为0
            //movable默认为true&&根节点的右边为空,节点数小于3
            //根节点的左节点为空,节点数小于3
            //左节点的左节点为空时,节点数小于等于6
            if (root == null
                || (movable
                    && (root.right == null
                        || (rl = root.left) == null
                        || rl.left == null))) {
                //链表化,因为前面对链表节点完成了删除操作,故在这里完成之后直接返回,即可完成节点的删除
                tab[index] = first.untreeify(map);  // too small
                return;
            }
			//p为删除节点
			//pl为删除节点的左节点
			//pr为删除节点的右节点
			//replacement 为删除节点的替换节点
            TreeNode<K,V> p = this, pl = left, pr = right, replacement;
            if (pl != null && pr != null) {
            		//这里的目的是获取删除节点的右边中最小值来替换当前被删除节点,这样才能保证树形特性。也可以查询删除节点的左边中的最大值节点来替换
                TreeNode<K,V> s = pr, sl;
                while ((sl = s.left) != null) // find successor
                    s = sl;
                //获取待替换节点的颜色C,将待替换节点颜色与删除节点保持一致,将删除节点颜色替换为待替换节点颜色。这里就是将待替换节点与删除节点进行替换工作
                boolean c = s.red; s.red = p.red; p.red = c; // swap colors
                //获取待替换节点的右节点
                TreeNode<K,V> sr = s.right;
                //获取删除节点的父节点
                TreeNode<K,V> pp = p.parent;
                //如果待替换节点的父节点为删除节点的右边
                if (s == pr) { // p was s's direct parent
                	//交换两个节点的位置,父节点变子节点,子节点变父节点
                    p.parent = s;
                    s.right = p;
                }
                else {
                	//这里整块逻辑就是将删除节点与待替换节点进行互换,但是删除节点的左边节点,其父亲节点还未改变
                	//获取待替换节点的父节点
                    TreeNode<K,V> sp = s.parent;
                    //将删除节点的父节点指向待替换节点的父节点,这里将删除节点的位置替换到待替换节点中
                    if ((p.parent = sp) != null) {
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    //将删除节点的右边节点,其父节点替换为待替换节点
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                //将删除节点的左节点设置为空
                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;
            }
            //走到这里证明删除节点的右边为Null,此时其左边节点作为替换节点,否则为将其右边节点作为替换节点
            else if (pl != null)
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else
            		//到这里,证明删除节点没有子节点,所以替换节点设置为本身
                replacement = p;
            //由于替换节点是根删除节点相邻,所以将替换节点顶替其删除节点,这样删除节点就从树中被移除了,再将其属性都置为null
            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;
                }
            }
            //保持根节点处于 所在数组index位置中的第一个节点
            if (movable)
                moveRootToFront(tab, r);
        }

总结

为了保持红黑树的特性,在插入或者删除时,可能破坏其平衡结构,所以通过变色、左旋、右旋等方式来保持红黑树的平衡。

红黑树特性:

  • 根节点必须是黑色节点。
  • 结点是红色或黑色。
  • 所有叶子都是黑色。(叶子是Null节点)
  • 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
  • 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164512.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis原理

Redis内部使用的是文件事件处理器file event handler,它是单线程的,所以Redis叫做单线程模型。它采用IO多路复用机制同时监听多个socket,将产生事件的socket压入内存队列中,事件分派器根据socket上的事件类型来选择对应的事件处理器进行处理。文件事件处理器包含4个部分:多…

【Java寒假打卡】Java基础-线程池

【Java寒假打卡】Java基础-线程池概述基本使用Executors创建指定上限的线程对象线程池-ThreadPoolExecutorvolatile概述 基本使用 package com.hfut.edu.test12;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class test1 {publ…

java+springboot笔记2023003

java的版本发布&#xff1a; 编译型语言是指使用专门的编译器&#xff0c;针对特定平台&#xff08;操作系统&#xff09; 将某种高级语言源代码一次性“翻译”成可被该平台硬件执行的机器码&#xff08;包括机器指令和操作数&#xff09;&#xff0c;并包装成该平台所能识别的…

Linux下更新curl版本

一、前景 由于低版本的curl存在一定的漏洞&#xff0c;会对我们的服务器安全造成问题&#xff0c;所以&#xff0c;我们需要将curl由低版本安装到高版本。 二、步骤 1、首先检测服务器安装的curl版本 curl --version 2、查看服务器安装的curl的安装包 rpm -qa curl 3、卸载…

基于springboot+mybatis+mysql+jsp房屋租赁管理系统(含论文)

基于springbootmybatismysqljsp房屋租赁管理系统&#xff08;含论文&#xff09;一、系统介绍二、所用技术三、功能展示三、其它系统四、获取源码一、系统介绍 包括管理员、房东、租客三种角色&#xff0c;外加游客(未登录情况) 出租类型包含整租和合租 权限 游客 < 租客 …

适合编程初学者的开源项目:小游戏2048(鸿蒙ArkTS版)

目标 为编程初学者打造入门学习项目&#xff0c;使用各种主流编程语言来实现。 2048游戏规则 一共16个单元格&#xff0c;初始时由2或者4构成。 1、手指向一个方向滑动&#xff0c;所有格子会向那个方向运动。 2、相同数字的两个格子&#xff0c;相遇时数字会相加。 3、每次…

用 JavaScript 写一个新年倒计时

目录前言&#xff1a;主题&#xff1a;运行结果&#xff1a;对应素材&#xff1a;代码实现思路&#xff1a;运行代码&#xff1a;春节的由来&#xff1a;总结&#xff1a;前言&#xff1a; 在春节即将到来&#xff0c;钟声即将响起&#xff0c;焰火即将燃起的日子里&#xff0c…

Kubernetes_CRD自定义资源

系列文章目录 文章目录系列文章目录前言一、CRD操作命令1.1 定义一种资源并查看1.2 使用刚刚定义的资源二、CRD效果演示2.1 实践&#xff1a;定义一种资源并查看2.2 实践&#xff1a;使用刚刚定义的资源总结前言 CRD就是自定义资源&#xff0c;就是自定义 apiVersionKind 参考…

TreeMap 原理实现及常用方法

TreeMap概述 红黑树回顾 TreeMap构造 put方法 get 方法 remove方法 遍历 总结 一. TreeMap概述 TreeMap存储K-V键值对&#xff0c;通过红黑树&#xff08;R-B tree&#xff09;实现&#xff1b; TreeMap继承了NavigableMap接口&#xff0c;NavigableMap接口继承了Sort…

蓝桥杯STM32G431RBT6学习——LED

蓝桥杯STM32G431RBT6学习——LED 前言 LED为每年必考考点&#xff0c;也是入门的基础&#xff0c;国信长天的开发板LED硬件如下&#xff1a; 经典的锁存器控制&#xff0c;因为LED所用引脚与LCD重叠&#xff0c;因此通过锁存器进行控制其状态。当74HC573的LE引脚&#xff08…

C语言综合练习5:快译通下

1 词典文件介绍 前面建立的词典&#xff0c;只有两个单词&#xff0c;现在我们要建立一个上万个单词的词典&#xff0c;所有单词及其翻译都在一个名为dict.txt的文件&#xff08;词典文件&#xff09;中 每个单词有两行&#xff0c;其中一行是单词原文&#xff0c;下一行是对…

Redis中的事务和乐观锁实现

redis事务相关命令&#xff1a; 开启事务&#xff1a;multi 关闭事务&#xff1a;discard 提交事务&#xff1a;exec 正常执行事务情况&#xff1a; 127.0.0.1:6379> multi OK 127.0.0.1:6379> set name zhangsan QUEUED 127.0.0.1:6379> set age 20 QUEUED 127.0.0.1…

AJAX这一篇就够啦~

AJAX这一篇就够啦~AJAX1、AJAX概述1.1 AJAX简介1.2 XML简介1.3 AJAX的特点2、HTTP相关2.1 HTTP概述2.2 请求报文2.3 响应报文2.4 常见的响应状态码2.5 不同类型的请求及其作用2.6 一般http请求 与 ajax请求3、原生AJAX的使用3.1 准备工作3.2 核心对象3.3 GET请求3.4 POST请求3.…

新岁序开,和Jina AI共同码梦! (奖品攻略大揭秘)

Jina AI 北京、深圳、柏林、湾区的小伙伴给您拜年啦&#xff01; Jina AI 开源社区致力于促进 多模态 AI 技术 的应用落地以及传播&#xff0c;一直以来&#xff0c;我们都为拥有这样一个全球化、多元化和高速发展的社区而感到自豪和感激&#xff01;正值新年之际&#xff0c;我…

从C和C++内存管理来谈谈JVM的垃圾回收算法设计-下

从C和C内存管理来谈谈JVM的垃圾回收算法设计-下引言基本概念对象GC ROOTS垃圾回收常见算法标记清除优缺点引用计数优缺点部分标记清除算法优缺点复制算法优缺点多空间复制算法标记整理(标记压缩)优缺点分代设计HotSpot具体实现跨代引用并发可行性经典垃圾回收器Serial新生代垃圾…

Binding常用辅助属性、多重绑定、优先级绑定

Binding常用辅助属性、多重绑定、优先级绑定 Binding常用辅助属性 StringFormat <Window.Resources><sys:Int32 x:Key"myInt">200</sys:Int32><sys:Single x:Key"mySingle">100.123456</sys:Single> </Window.Resourc…

Linux 中断控制器(五):中断号映射

中断号分为硬件中断号(HW ID)和软件中断号(IRQ number)。 这里有两个中断控制器,处理完毕进入 CPU。外设和中断控制器连接在一起,外设给中断控制器的是硬件中断号,如果中断控制器有级联,那么硬件中断号在不同的中断控制器中可能会重复。但是到了 CPU 以后,我们需要对不…

C语言:分支语句和循环语句

往期文章 C语言&#xff1a;初识C语言 目录往期文章前言1. 什么是语句2. 分支语句&#xff08;选择结构&#xff09;2.1 if语句2.2 switch语句3. 循环语句3.1 while循环3.2 for循环3.3 do while 循环3.4 goto语句后记前言 趁热打铁啊。写完该系列第一篇博客我就来劲了&#x…

TicTacToe: 基于时序差分TD(0)算法的agent实现以及完整python实现框架

目录 1. 前言 2. TD(0) 3. 实现要点解读 3.1 Class Env 3.2 Class State 3.3 Class Agent 3.3.1 class TD0Agent(Agent) 3.3.2 class MinimaxAgent 3.3.3 class RandomAgent(Agent) 3.3.4 class HumanPlayer(Agent) 3.4 棋盘和玩家的表示 4. Utility Function 4.1…

Redis02之使用java代码操作Redis

目录 1、可视化管理工具redis-desktop-manager安装与配置 ​编辑 2、Java访问redis 注1&#xff1a;不需要记得API的方法&#xff0c;只需要查redis命令 3、web3.0设置 1、可视化管理工具redis-desktop-manager安装与配置 1.1 双击redis-desktop-manager-0.8.8.384.exe即可…