当前位置: 首页 > news >正文

【Java集合夜话】第9篇下:深入剖析TreeMap源码:红黑树实现原理与面试总结(建议收藏)

🔥 本文深入剖析Java集合框架中的TreeMap源码实现,从红黑树原理到面试重点,带你透彻理解TreeMap的底层机制。本文是TreeMap系列的下篇,主要关注源码分析与面试题解。

📚 系列专栏推荐:

  • JAVA集合专栏 【夜话集】
  • JVM知识专栏
  • 数据库sql理论与实战【博主踩坑之道】
  • 小游戏开发【博主强推 匠心之作 拿来即用无门槛】

我的java

文章目录

    • 一、红黑树核心原理
      • 1. 红黑树的5个基本特性
      • 2. 红黑树的平衡过程
        • 2.1 什么时候需要平衡?
        • 2.2 怎么恢复平衡?
      • 3. 时间复杂度分析
        • 3.1 基本操作的时间复杂度
        • 3.2 为什么是对数复杂度?
      • 4. 与AVL树的对比
        • 4.1 平衡度对比
        • 4.2 应用场景对比
        • 4.3 为什么TreeMap选择红黑树?
    • 二、TreeMap源码精读
      • 1. 核心属性与内部类
        • 1.1 核心属性
        • 2.3 图解插入过程
      • 3. remove()方法源码解析
        • 3.1 删除操作概述
        • 3.2 源码实现
        • 3.3 图解删除过程
      • 4. 红黑树平衡调整实现
        • 4.1 什么时候需要调整?
        • 4.2 平衡调整的基本操作
        • 4.3 平衡调整的核心代码
    • 三、关键算法详解
      • 1. 左旋与右旋操作
        • 1.1 左旋操作详解
        • 1.2 右旋操作详解
      • 2. 插入节点后的平衡处理
        • 2.1 插入的基本规则
        • 2.2 插入后的情况分析
      • 3. 删除节点后的平衡处理
        • 3.1 删除的基本规则
        • 3.2 删除后的情况分析
      • 4. 查找与遍历实现
        • 4.1 查找算法
        • 4.2 遍历实现
    • 四、面试重点解析
      • 1. 红黑树特性相关题
        • Q1: 红黑树的5个特性是什么?为什么要有这些特性?
        • Q2: 为什么说红黑树是"近似平衡"的?
      • 2. 源码实现考点
        • Q1: TreeMap插入节点时的颜色选择
        • Q2: TreeMap的put方法和普通HashMap的区别
      • 3. 性能分析题目
        • Q1: 什么情况下使用TreeMap比HashMap更好?
        • Q2: TreeMap的时间复杂度分析
      • 4. 实际应用场景题
        • Q1: 设计一个学生成绩管理系统
        • Q2: 实现一个日程安排系统
    • 五、实战踩坑总结
    • 写在最后

一、红黑树核心原理

1. 红黑树的5个基本特性

红黑树本质上是一种自平衡的二叉查找树。就像交通信号灯一样,通过红黑两种颜色的搭配来维持秩序。它必须遵守以下5条铁律:

1️⃣ 节点颜色特性
每个节点必须是红色或黑色,就像一个开关只有开/关两种状态:

黑节点 ●
红节点 ○

2️⃣ 根节点特性
树的老大(根节点)必须是黑色。就像公司老板必须稳重:

  ● (根是黑色)
 / \
○   ○

3️⃣ 叶子节点特性
所有末端的空节点(NIL)都是黑色的。这些NIL节点就像树的"叶子":

  ●
 / \
○   ○

/\ /
● ● ● ● (这些都是NIL节点)

4️⃣ 红节点特性
如果一个节点是红色,它的孩子必须是黑色。就像红灯亮时,下一个必须是绿灯:

正确的例子:

/
○ ○
/
● ●

错误的例子:

/
○ ○
/
○ ○ (红色节点不能相连)

5️⃣ 黑色完美平衡
从任意节点出发,到它下面的每个叶子节点,路径上的黑节点数量必须相同:

  ● (黑)
 / \
○   ● (红)(黑)

/
● ● (黑)(黑)

在上图中,所有路径都包含2个黑节点(不算NIL)

🌟 新手提示

  1. 先不要急着记住所有规则
  2. 可以把红黑树想象成一个需要遵守"红黑交替"规则的家族树
  3. 这些规则的目的就是让树保持平衡,不会长歪
  4. 在使用TreeMap时,这些规则都是自动维护的,你不需要自己实现

记住:这些规则看起来复杂,但它们就像交通规则一样,都是为了维持秩序。在实际使用TreeMap时,这些规则都是自动维护的,你只需要理解基本原理就可以了。

2. 红黑树的平衡过程

2.1 什么时候需要平衡?

想象你在搭积木,有时候添加或移除一块积木会让整个结构变得不稳定。红黑树也是一样,在以下情况需要调整:

  1. 添加新节点时:
    比如这样的情况(不允许连续的红节点):

    ● 黑节点
    /
    ○ 红节点
    /
    ○ 红节点 (糟糕!出现连续的红节点了)

  2. 删除节点时:
    比如这样的情况(左右两边黑节点数量要相等):

    ● 黑节点
    /
    ● ○ (删除右边后,左右两边黑节点数量不同了)
    /

  3. 特殊情况:
    根节点必须是黑色:

○ (根节点变红了,这是不允许的)
/
● ●

2.2 怎么恢复平衡?

就像你整理歪掉的积木一样,红黑树有三种基本动作来恢复平衡:

1️⃣ 左旋:向左倒
想象一个节点向左倾倒的过程:

A                B
 \              /
  B     →     A
   \            \
    C            C

2️⃣ 右旋:向右倒
想象一个节点向右倾倒的过程:

  C            B
 /            / \
B     →     A   C

/
A

3️⃣ 变色:换颜色
就像给积木重新上色:

●(黑)         ○(红)

/ \ → /
○ ○ ● ●
(红) (红) (黑) (黑)

🌟 通俗理解

  • 左旋就像跳舞时向左转,右边的节点上位
  • 右旋就像跳舞时向右转,左边的节点上位
  • 变色就像给积木换个颜色,保持红黑规则

💡 新手提示

  1. 不用记住具体的旋转步骤
  2. 理解这些操作就是为了保持树的平衡
  3. TreeMap会自动帮我们处理这些操作
  4. 就像开车不需要懂发动机原理一样,使用TreeMap时不需要会写这些平衡操作

记住:这些平衡操作就像是树的"瑜伽动作",目的是保持树的"身材"不走样。在实际使用TreeMap时,这些动作都是自动完成的,你不需要亲自动手。

3. 时间复杂度分析

3.1 基本操作的时间复杂度
  • 查找:O(log n)
  • 插入:O(log n)
  • 删除:O(log n)
  • 遍历:O(n)
3.2 为什么是对数复杂度?
  • 红黑树保证了从根到叶子的最长路径不超过最短路径的2倍
  • 黑色完美平衡特性确保了树的高度为O(log n)
  • 所有基本操作都与树的高度相关

4. 与AVL树的对比

4.1 平衡度对比
  • AVL树:严格平衡,任意节点的左右子树高度差不超过1
  • 红黑树:黑色节点平衡,允许一定程度的不平衡
4.2 应用场景对比
特性红黑树AVL树
平衡条件较为宽松严格平衡
插入性能较好一般
删除性能较好一般
查询性能极好
空间开销每个节点增加1位每个节点增加平衡因子
应用场景增删较多的场景查询密集型场景
4.3 为什么TreeMap选择红黑树?
  • 红黑树的平衡条件较为宽松,在插入和删除时需要的旋转操作更少
  • Java中的TreeMap需要同时兼顾查询和增删性能
  • 红黑树的实现相对简单,代码维护成本较低

💡 小贴士:理解红黑树的核心在于掌握其5个基本特性,以及如何通过旋转和变色来维护这些特性。在实际应用中,我们不需要手动处理这些平衡操作,TreeMap已经为我们实现了这些复杂的逻辑。

二、TreeMap源码精读

1. 核心属性与内部类

让我们先了解TreeMap的"零件",就像认识一辆自行车的各个部分:

1.1 核心属性

TreeMap有四个最重要的属性,就像自行车的核心部件:

    private Entry<K,V> root;         // 整个树的根节点,就像自行车的车架
    private final Comparator<? super K> comparator;  // 比较器,就像码表
    private int size = 0;            // 树的节点数量,就像零件数
    private int modCount = 0;        // 修改计数器,就像里程表
    ```

#### 1.2 节点结构
每个节点(Entry)就像是积木中的一块,包含以下部分:

    Entry<K,V>
    ├── K key      // 键,类似学生的学号
    ├── V value    // 值,类似学生的信息
    ├── Entry left   // 左孩子,比当前节点小的
    ├── Entry right  // 右孩子,比当前节点大的
    ├── Entry parent // 父节点,当前节点所在的层级
    └── boolean color // 颜色标记,用于保持平衡

### 2. put()方法源码解析
向TreeMap中放入数据,就像安排新同学入座,需要以下步骤:

#### 2.1 整体流程
put方法的执行流程如下:

    开始
     ↓
    是否空树? ──是→ 创建根节点
     ↓ 否               ↓
    查找位置    ←──── 返回
     ↓
    创建新节点
     ↓
    调整平衡
     ↓
    完成插入

#### 2.2 源码分析
让我们看看put方法的核心实现:

```java
    public V put(K key, V value) {
        // 1. 空树判断
        if (root == null) {
            root = new Entry<>(key, value, null);
            size = 1;
            return null;
        }
        
        // 2. 寻找插入位置
        int cmp;
        Entry<K,V> parent;
        Entry<K,V> t = root;
        
        do {
            parent = t;
            cmp = compare(key, t.key);  // 比较大小
            if (cmp < 0)
                t = t.left;    // 小于则左移
            else if (cmp > 0)
                t = t.right;   // 大于则右移
            else
                return t.setValue(value);  // 相等则更新
        } while (t != null);
        
        // 3. 创建并插入新节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
            
        // 4. 调整平衡
        fixAfterInsertion(e);
        size++;
        return null;
    }
2.3 图解插入过程

假设我们依次插入 5、3、7:

  1. 第一步:插入5

    5(黑) // 根节点必须是黑色

  2. 第二步:插入3

    5(黑)
    /
    3(红) // 新节点默认为红色

  3. 第三步:插入7

    5(黑)
    /
    3(红) 7(红) // 完美平衡的状态

💡 使用提示

  1. 实际使用时,你只需要调用put方法:
    TreeMap<Integer, String> map = new TreeMap<>();
    map.put(1001, “张三”); // 就是这么简单!

  2. 所有的平衡操作都是自动的,你不需要关心内部实现

  3. 记住:键必须是可比较的(实现Comparable或提供Comparator)

  4. 插入的时间复杂度是O(log n),非常高效

记住:虽然源码看起来复杂,但使用起来就像往抽屉里放东西一样简单。TreeMap会自动帮我们维护树的平衡,我们只需要专注于业务逻辑即可。

3. remove()方法源码解析

3.1 删除操作概述

删除节点就像班级里转学一个学生,需要:

  1. 找到要转学的学生
  2. 安排其他学生补位
  3. 调整班级座位秩序
3.2 源码实现
    public V remove(Object key) {
        // 1. 先找到要删除的节点
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;  // 没找到,直接返回
            
        V oldValue = p.value;  // 保存旧值
        deleteEntry(p);        // 执行删除
        return oldValue;       // 返回被删除的值
    }

    private void deleteEntry(Entry<K,V> p) {
        // 1. 记录删除前的颜色
        boolean color = p.color;
        
        // 2. 找到替代节点
        if (p.left == null && p.right == null) {
            // 情况1:没有子节点
            replace(p, null);
            
        } else if (p.left == null) {
            // 情况2:只有右子节点
            replace(p, p.right);
            
        } else if (p.right == null) {
            // 情况3:只有左子节点
            replace(p, p.left);
            
        } else {
            // 情况4:有两个子节点
            // 找到后继节点(右子树中最小的节点)
            Entry<K,V> successor = successor(p);
            // 用后继节点的值替换当前节点
            p.key = successor.key;
            p.value = successor.value;
            // 删除后继节点
            deleteEntry(successor);
            return;
        }
        
        // 3. 如果删除的是黑色节点,需要调整平衡
        if (color == BLACK)
            fixAfterDeletion(p);
    }
3.3 图解删除过程

让我们看几个具体的删除场景:

  1. 删除叶子节点:

    删除前: 删除后:
    5(黑) 5(黑)
    / \ /
    3(红) 7(红) 3(红)

  2. 删除有一个子节点的节点:

    删除前: 删除后:
    5(黑) 5(黑)
    / \ /
    3(红) 7(黑) 3(红)
    /
    6(红)

  3. 删除有两个子节点的节点:

    删除前: 删除后:
    5(黑) 6(黑)
    / \ /
    3(红) 7(黑) 3(红) 7(黑)
    /
    6(红)

4. 红黑树平衡调整实现

4.1 什么时候需要调整?

在以下情况需要进行平衡调整:

1. 插入后:新节点是红色,可能违反红色节点特性
2. 删除后:删除黑色节点,可能违反黑色平衡特性
4.2 平衡调整的基本操作
  1. 左旋操作:

    before: after:
    A B
    \ /
    B → A
    \
    C C

  2. 右旋操作:

    before: after:
    C B
    / /
    B → A C
    /
    A

  3. 变色操作:

    before: after:
    ●(黑) ○(红)
    / \ /
    ○ ○ ● ●
    (红) (红) (黑) (黑)

4.3 平衡调整的核心代码
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;  // 新插入的节点默认为红色
        
        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                // 父节点是祖父节点的左子节点
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                
                if (colorOf(y) == RED) {
                    // Case 1: 叔叔节点是红色
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    // Case 2: 叔叔节点是黑色,当前节点是右子节点
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    // Case 3: 叔叔节点是黑色,当前节点是左子节点
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                // 对称情况:父节点是祖父节点的右子节点
                // ... 对称处理 ...
            }
        }
        root.color = BLACK;  // 确保根节点为黑色
    }

💡 重要提示

  1. 删除操作比插入更复杂,因为可能会破坏黑色平衡
  2. 平衡调整的目的是维护红黑树的5个基本特性
  3. 实际使用时这些复杂操作都是自动的
  4. 理解原理有助于更好地使用TreeMap

记住:虽然平衡调整的代码看起来很复杂,但这些都是TreeMap自动帮我们处理的。我们只需要调用remove()方法就可以了,比如:

    TreeMap<Integer, String> map = new TreeMap<>();
    map.remove(1001);  // TreeMap会自动处理所有平衡操作

三、关键算法详解

1. 左旋与右旋操作

这两个操作就像跳交谊舞,是保持红黑树平衡的基本动作。

1.1 左旋操作详解

左旋就像是父子交换位置,儿子升职做了父亲:

步骤演示:
     P                  R
    / \                / \
   A   R     →       P   C
      / \           / \
     B   C         A   B

具体代码实现:
```java
private void rotateLeft(Entry<K,V> p) {
    Entry<K,V> r = p.right;
    p.right = r.left;          // P的右孩子变成R的左孩子
    if (r.left != null)
        r.left.parent = p;      // 更新父节点引用
    r.parent = p.parent;        // R继承P的父节点
    if (p.parent == null)
        root = r;               // 如果P是根,R变成新的根
    else if (p.parent.left == p)
        p.parent.left = r;      // P是左孩子,R也做左孩子
    else
        p.parent.right = r;     // P是右孩子,R也做右孩子
    r.left = p;                 // P变成R的左孩子
    p.parent = r;               // 更新P的父节点为R
}
```
1.2 右旋操作详解

右旋是左旋的镜像操作:

步骤演示:
       P                L
      / \              / \
     L   C    →      A   P
    / \                  / \
   A   B                B   C

2. 插入节点后的平衡处理

2.1 插入的基本规则
  1. 新节点总是红色的(减少调整次数)
  2. 如果父节点是黑色的,直接插入
  3. 如果父节点是红色的,需要调整
2.2 插入后的情况分析
情况1:叔叔节点是红色

初始状态:        变色后:
    G(黑)           G(红)
   /    \         /    \
 P(红)  U(红) → P(黑)  U(黑)
 /                /
N(红)           N(红)

情况2:叔叔节点是黑色(三角形)

初始状态:        左旋后:        变色+右旋:
    G(黑)          G(黑)           N(黑)
   /              /               /    \
 P(红)    →     N(红)    →     P(红)  G(红)
    \           /
    N(红)     P(红)

情况3:叔叔节点是黑色(一条线)

初始状态:        变色+右旋:
    G(黑)           P(黑)
   /               /    \
 P(红)     →     N(红)  G(红)
 /
N(红)

3. 删除节点后的平衡处理

3.1 删除的基本规则
  1. 被删节点是红色:直接删除
  2. 被删节点是黑色:需要调整平衡
3.2 删除后的情况分析
情况1:兄弟节点是红色

初始:             左旋+变色:
  P(黑)              S(黑)
 /    \            /    \
N(黑)  S(红)  →   P(红)  SR(黑)
       / \        /  \
     SL(黑) SR(黑) N(黑) SL(黑)

情况2:兄弟节点是黑色,两个子节点都是黑色

初始:             变色:
  P(任意)           P(任意)
 /    \           /    \
N(黑)  S(黑)  →  N(黑)  S(红)
       / \             / \
      B   B           B   B

4. 查找与遍历实现

4.1 查找算法

就像二分查找,每次都去一半的区域:

    private Entry<K,V> getEntry(Object key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp < 0)
                p = p.left;      // 小于则往左找
            else if (cmp > 0)
                p = p.right;     // 大于则往右找
            else
                return p;        // 找到了
        }
        return null;            // 没找到
    }
4.2 遍历实现

支持三种遍历方式:

    // 中序遍历(获得有序序列)
    private void inorderTraversal(Entry<K,V> node) {
        if (node != null) {
            inorderTraversal(node.left);
            System.out.println(node.key);
            inorderTraversal(node.right);
        }
    }

💡 算法要点

  1. 左旋右旋是基础操作,就像积木的基本移动
  2. 插入调整比删除调整简单
  3. 所有调整都是为了维护红黑树的5个特性
  4. 实际使用时这些都是自动的,不需要手动处理

记住:虽然这些算法看起来复杂,但它们都被封装在TreeMap内部。我们使用时只需要关注业务逻辑,比如:

    TreeMap<Integer, String> map = new TreeMap<>();
    map.put(1, "一");    // 内部自动处理平衡
    map.remove(1);       // 内部自动维护特性
    String value = map.get(1);  // 自动使用二分查找

四、面试重点解析

1. 红黑树特性相关题

Q1: 红黑树的5个特性是什么?为什么要有这些特性?

答:红黑树的5个特性及其目的:

1. 节点是红色或黑色
   目的:提供标记,用于平衡控制

2. 根节点是黑色
   目的:确保根到叶子的黑节点数量一致

3. 叶子节点(NIL)是黑色
   目的:统一空节点的处理方式

4. 红节点的子节点必须是黑色
   目的:防止连续的红节点,保持黑高度

5. 从根到叶子的所有路径包含相同数量的黑节点
   目的:保证树的基本平衡
Q2: 为什么说红黑树是"近似平衡"的?

答:因为:

1. 最长路径不会超过最短路径的2倍
   证明:最长路径(红黑交替)vs 最短路径(全黑)

2. 举例说明:
   平衡路径:   B → B → B  (长度3)
   较长路径:   B → R → B → R → B (长度5)
               但仍然保持了黑节点数量相等

2. 源码实现考点

Q1: TreeMap插入节点时的颜色选择

问:为什么新插入的节点默认是红色的?

答:选择红色的原因:
1. 红色节点不会影响黑色平衡
2. 如果选择黑色,则一定会破坏黑色平衡
3. 红色可能会违反特性4,但调整起来比较简单

举例:插入节点N
    原树:       插入后:
      B           B
     /           /
    B     →    B
               /
              R(N)
Q2: TreeMap的put方法和普通HashMap的区别

答:主要区别:

1. 时间复杂度:
   - TreeMap: O(log n)
   - HashMap: O(1)平均,O(n)最差

2. 有序性:
   - TreeMap: 天然有序
   - HashMap: 无序

3. 实现原理:
   - TreeMap: 红黑树
   - HashMap: 数组+链表+红黑树(JDK8)

3. 性能分析题目

Q1: 什么情况下使用TreeMap比HashMap更好?

答:以下场景适合使用TreeMap:

1. 需要按键排序的场景
   例如:成绩排名系统
   TreeMap<Integer, String> scores = new TreeMap<>(Collections.reverseOrder());
   scores.put(98, "张三");
   scores.put(95, "李四");
   // 自动按分数倒序排列

2. 需要范围查询的场景
   例如:价格区间查询
   TreeMap<Integer, Product> products = new TreeMap<>();
   products.subMap(100, 200);  // 获取100-200价格的商品

3. 数据量中等且需要保持有序的场景
   - 数据量小:LinkedHashMap更好
   - 数据量特别大:数据库更好
Q2: TreeMap的时间复杂度分析

答:各操作的复杂度:

操作          时间复杂度    原因
----------------------------------
插入(put)     O(log n)     需要查找位置+平衡
删除(remove)  O(log n)     需要查找+删除+平衡
查找(get)     O(log n)     二分查找特性
遍历(iterate) O(n)         需要访问所有节点

4. 实际应用场景题

Q1: 设计一个学生成绩管理系统

问:如何使用TreeMap实现学生成绩的排名功能?

答:实现示例:

class ScoreManager {
    // 按分数倒序排列
    private TreeMap<Double, List<Student>> scoreMap = 
        new TreeMap<>(Collections.reverseOrder());
        
    public void addScore(Student student, double score) {
        scoreMap.computeIfAbsent(score, k -> new ArrayList<>())
               .add(student);
    }
    
    public List<Student> getTopStudents(int n) {
        List<Student> result = new ArrayList<>();
        for (List<Student> students : scoreMap.values()) {
            result.addAll(students);
            if (result.size() >= n) break;
        }
        return result.subList(0, Math.min(n, result.size()));
    }
}
Q2: 实现一个日程安排系统

问:如何使用TreeMap实现时间冲突检测?

答:实现示例:

class Calendar {
    private TreeMap<LocalDateTime, Meeting> schedule = new TreeMap<>();
    
    public boolean addMeeting(LocalDateTime start, 
                            LocalDateTime end, 
                            String title) {
        // 检查是否有时间冲突
        Map.Entry<LocalDateTime, Meeting> before = 
            schedule.floorEntry(start);
        Map.Entry<LocalDateTime, Meeting> after = 
            schedule.ceilingEntry(start);
            
        if ((before != null && before.getValue().getEndTime().isAfter(start)) ||
            (after != null && end.isAfter(after.getKey()))) {
            return false; // 有冲突
        }
        
        schedule.put(start, new Meeting(start, end, title));
        return true;
    }
}

💡 面试技巧

  1. 回答问题时先说明原理,再举例说明
  2. 多准备实际应用场景的例子
  3. 要能手写关键代码
  4. 注意性能分析和优化建议

五、实战踩坑总结

  • 常见使用误区
  • 性能优化建议
  • 线程安全处理
  • 最佳实践总结

写在最后

🎉 至此,TreeMap的两篇系列文章就全部完成了。上篇我们学习了基础用法,这篇深入了源码和面试重点,希望能帮助大家彻底掌握TreeMap!

📚 推荐几篇很有趣的文章

  • DeepSeek详解:探索下一代语言模型
  • 算法模型从入门到起飞系列——递归(探索自我重复的奇妙之旅)

📚博主匠心之作,强推专栏

  • JAVA集合专栏 【夜话集】
  • JVM知识专栏
  • 数据库sql理论与实战【博主踩坑之道】
  • 小游戏开发【博主强推 匠心之作 拿来即用无门槛】

如果觉得有帮助的话,别忘了点个赞 👍 收藏 ⭐ 关注 🔖 哦!


🎯 我是果冻~,一个热爱技术、乐于分享的开发者
📚 更多精彩内容,请关注我的博客
🌟 我们下期再见!

http://www.dtcms.com/a/98767.html

相关文章:

  • day1_Flink基础
  • 【Git教程】将dev分支合并到master后,那么dev分支该如何处理
  • Promise使用
  • 【题解】AtCoder At_abc399_d [ABC399D] Switch Seats
  • .NET开发基础知识21-30
  • [GXYCTF2019]禁止套娃1 [GitHack] [无参数RCE]
  • Matplotlib基本使用
  • 数据库监控 | openGauss监控解析
  • 小程序API —— 56页面处理函数 - 下拉刷新
  • 前端常问的宏观“大”问题详解(二)
  • 编译原理课设工作日志
  • 一些练习 C 语言的小游戏
  • 探索Scala基础:融合函数式与面向对象编程的强大语言
  • 在 Unreal Engine 5 中制作类似《鬼泣5》这样的游戏时,角色在空中无法落地的问题可能由多种原因引起。
  • C++作用域辨识详解
  • 高等数学-第七版-上册 选做记录 习题7-4
  • linux基本命令(1)--linux下的打包命令 -- tar 和gzip
  • 电子电气架构 --- 域控架构下,汽车连接器的挑战和变化
  • Ethernet/IP转Modbus剖析库卡机器人同S7-1200PLC双向通讯的技术
  • OpenAI API - Realtime 实时
  • 高速电路中的存储器应用与设计四
  • 【JavaScript】合体期功法——DOM(一)
  • Python 序列构成的数组(元组不仅仅是不可变的列表)
  • 质因数个数--欧拉函数中统计纯素数
  • 直播推流全面指南
  • 【设计模式】单例模式
  • 安卓分发平台一站式APP应用内测平台
  • ros2--功能包
  • 如何备份你的 Postman 所有 Collection?
  • 0329-项目(添加 删除 修改)