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

vue diff 双端比较算法

文章目录

  • 双端指针
  • 比较策略
    • 命中策略四
    • 命中策略二
    • 命中策略三
    • 命中策略一
    • 未命中四种策略,遍历旧节点列表
    • 新增情况一
    • 新增情况二
  • 删除节点
  • 双端比较的优势

双端指针

在这里插入图片描述

  • 使用四个变量 oldStartIdx、oldEndIdx、newStartIdx 以及 newEndIdx 分别存储旧 children 和新 children 的两个端点的位置索引
let oldStartIdx = 0
let oldEndIdx = prevChildren.length - 1
let newStartIdx = 0
let newEndIdx = nextChildren.length - 1
  • 除了位置索引之外,还需要拿到四个位置索引所指向的 VNode
let oldStartVNode = prevChildren[oldStartIdx]
let oldEndVNode = prevChildren[oldEndIdx]
let newStartVNode = nextChildren[newStartIdx]
let newEndVNode = nextChildren[newEndIdx]

比较策略

  • 使用旧 children 的头一个 VNode 与新 children 的头一个 VNode 比对,即 oldStartVNode 和 newStartVNode 比较对。
  • 使用旧 children 的最后一个 VNode 与新 children 的最后一个 VNode 比对,即 oldEndVNode 和 newEndVNode 比对。
  • 使用旧 children 的头一个 VNode 与新 children 的最后一个 VNode 比对,即 oldStartVNode 和 newEndVNode 比对。
  • 使用旧 children 的最后一个 VNode 与新 children 的头一个 VNode 比对,即 oldEndVNode 和 newStartVNode 比对。
    在这里插入图片描述
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (oldStartVNode.key === newStartVNode.key) {// 步骤一:oldStartVNode 和 newStartVNode 比对} else if (oldEndVNode.key === newEndVNode.key) {// 步骤二:oldEndVNode 和 newEndVNode 比对} else if (oldStartVNode.key === newEndVNode.key) {// 步骤三:oldStartVNode 和 newEndVNode 比对} else if (oldEndVNode.key === newStartVNode.key) {// 步骤四:oldEndVNode 和 newStartVNode 比对}
}

命中策略四

  • 第一步:拿旧 children 中的 li-a 和新 children 中的 li-d 进行比对,由于二者 key 值不同,所以不可复用,什么都不做。
  • 第二步:拿旧 children 中的 li-d 和新 children 中的 li-c 进行比对,同样不可复用,什么都不做。
  • 第三步:拿旧 children 中的 li-a 和新 children 中的 li-c 进行比对,什么都不做。
  • 第四步:拿旧 children 中的 li-d 和新 children 中的 li-d 进行比对,由于这两个节点拥有相同的 key 值,所以我们在这次比对的过程中找到了可复用的节点。
    • li-d 节点所对应的真实 DOM 原本是最后一个子节点,并且更新之后它应该变成第一个子节点。所以我们需要把 li-d 所对应的真实 DOM 移动到最前方即可:
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (oldStartVNode.key === newStartVNode.key) {// 步骤一:oldStartVNode 和 newStartVNode 比对} else if (oldEndVNode.key === newEndVNode.key) {// 步骤二:oldEndVNode 和 newEndVNode 比对} else if (oldStartVNode.key === newEndVNode.key) {// 步骤三:oldStartVNode 和 newEndVNode 比对} else if (oldEndVNode.key === newStartVNode.key) {// 步骤四:oldEndVNode 和 newStartVNode 比对// 先调用 patch 函数完成更新patch(oldEndVNode, newStartVNode, container)// 更新完成后,将容器中最后一个子节点移动到最前面,使其成为第一个子节点container.insertBefore(oldEndVNode.el, oldStartVNode.el)// 更新索引,指向下一个位置oldEndVNode = prevChildren[--oldEndIdx]newStartVNode = nextChildren[++newStartIdx]}
}

命中策略二

  • 上一步更新完成之后,新的索引关系可以用下图来表示:
    在这里插入图片描述
  • 第一步:拿旧 children 中的 li-a 和新 children 中的 li-b 进行比对,由于二者 key 值不同,所以不可复用,什么都不做。
  • 第二步:拿旧 children 中的 li-c 和新 children 中的 li-c 进行比对,此时,由于二者拥有相同的 key,所以是可复用的节点,但是由于二者在新旧 children 中都是最末尾的一个节点,所以是不需要进行移动操作的,只需要调用 patch 函数更新即可,同时将相应的索引前移一位
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (oldStartVNode.key === newStartVNode.key) {// 步骤一:oldStartVNode 和 newStartVNode 比对} else if (oldEndVNode.key === newEndVNode.key) {// 步骤二:oldEndVNode 和 newEndVNode 比对// 调用 patch 函数更新patch(oldEndVNode, newEndVNode, container)// 更新索引,指向下一个位置oldEndVNode = prevChildren[--oldEndIdx]newEndVNode = nextChildren[--newEndIdx]} else if (oldStartVNode.key === newEndVNode.key) {// 步骤三:oldStartVNode 和 newEndVNode 比对} else if (oldEndVNode.key === newStartVNode.key) {// 步骤四:oldEndVNode 和 newStartVNode 比对// 先调用 patch 函数完成更新patch(oldEndVNode, newStartVNode, container)// 更新完成后,将容器中最后一个子节点移动到最前面,使其成为第一个子节点container.insertBefore(oldEndVNode.el, oldStartVNode.el)// 更新索引,指向下一个位置oldEndVNode = prevChildren[--oldEndIdx]newStartVNode = nextChildren[++newStartIdx]}
}

命中策略三

  • 上一步更新完成之后,新的索引关系可以用下图来表示:
    在这里插入图片描述
  • 第一步:拿旧 children 中的 li-a 和新 children 中的 li-b 进行比对,由于二者 key 值不同,所以不可复用,什么都不做。
  • 第二步:拿旧 children 中的 li-b 和新 children 中的 li-a 进行比对,不可复用,什么都不做。
  • 第三步:拿旧 children 中的 li-a 和新 children 中的 li-a 进行比对,此时,我们找到了可复用的节点。
    • 这一次满足的条件是:oldStartVNode.key === newEndVNode.key,这说明:li-a 节点所对应的真实 DOM 原本是第一个子节点,但现在变成了“最后”一个子节点,这里的“最后”并不是指真正意义上的最后一个节点,而是指当前索引范围内的最后一个节点。所以移动操作也是比较明显的,我们将 oldStartVNode 对应的真实 DOM 移动到 oldEndVNode 所对应真实 DOM 的后面即可
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (oldStartVNode.key === newStartVNode.key) {// 步骤一:oldStartVNode 和 newStartVNode 比对} else if (oldEndVNode.key === newEndVNode.key) {// 步骤二:oldEndVNode 和 newEndVNode 比对// 调用 patch 函数更新patch(oldEndVNode, newEndVNode, container)// 更新索引,指向下一个位置oldEndVNode = prevChildren[--oldEndIdx]newEndVNode = newEndVNode[--newEndIdx]} else if (oldStartVNode.key === newEndVNode.key) {// 步骤三:oldStartVNode 和 newEndVNode 比对// 调用 patch 函数更新patch(oldStartVNode, newEndVNode, container)// 将 oldStartVNode.el 移动到 oldEndVNode.el 的后面,也就是 oldEndVNode.el.nextSibling 的前面container.insertBefore(oldStartVNode.el,oldEndVNode.el.nextSibling)// 更新索引,指向下一个位置oldStartVNode = prevChildren[++oldStartIdx]newEndVNode = nextChildren[--newEndIdx]} else if (oldEndVNode.key === newStartVNode.key) {// 步骤四:oldEndVNode 和 newStartVNode 比对// 先调用 patch 函数完成更新patch(oldEndVNode, newStartVNode, container)// 更新完成后,将容器中最后一个子节点移动到最前面,使其成为第一个子节点container.insertBefore(oldEndVNode.el, oldStartVNode.el)// 更新索引,指向下一个位置oldEndVNode = prevChildren[--oldEndIdx]newStartVNode = nextChildren[++newStartIdx]}
}

命中策略一

  • 上一步更新完成之后,新的索引关系可以用下图来表示:
    在这里插入图片描述
  • 第一步:拿旧 children 中的 li-b 和新 children 中的 li-b 进行比对,二者拥有相同的 key,可复用
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (oldStartVNode.key === newStartVNode.key) {// 步骤一:oldStartVNode 和 newStartVNode 比对// 调用 patch 函数更新patch(oldStartVNode, newStartVNode, container)// 更新索引,指向下一个位置oldStartVNode = prevChildren[++oldStartIdx]newStartVNode = nextChildren[++newStartIdx]} else if (oldEndVNode.key === newEndVNode.key) {// 省略...} else if (oldStartVNode.key === newEndVNode.key) {// 省略...} else if (oldEndVNode.key === newStartVNode.key) {// 省略...}
}

未命中四种策略,遍历旧节点列表

在这里插入图片描述

  • 上图中 ①、②、③、④ 这四步中的每一步比对,都无法找到可复用的节点
  • 策略为:拿新 children 中的第一个节点尝试去旧 children 中寻找,试图找到拥有相同 key 值的节点
  • 如果在旧的 children 中找到了与新 children 中第一个节点拥有相同 key 值的节点,这意味着:旧 children 中的这个节点所对应的真实 DOM 在新 children 的顺序中,已经变成了第一个节点。所以我们需要把该节点所对应的真实 DOM 移动到最前头
    在这里插入图片描述
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (oldStartVNode.key === newStartVNode.key) {// 省略...} else if (oldEndVNode.key === newEndVNode.key) {// 省略...} else if (oldStartVNode.key === newEndVNode.key) {// 省略...} else if (oldEndVNode.key === newStartVNode.key) {// 省略...} else {// 遍历旧 children,试图寻找与 newStartVNode 拥有相同 key 值的元素const idxInOld = prevChildren.findIndex(node => node.key === newStartVNode.key)if (idxInOld >= 0) {// vnodeToMove 就是在旧 children 中找到的节点,该节点所对应的真实 DOM 应该被移动到最前面const vnodeToMove = prevChildren[idxInOld]// 调用 patch 函数完成更新patch(vnodeToMove, newStartVNode, container)// 把 vnodeToMove.el 移动到最前面,即 oldStartVNode.el 的前面container.insertBefore(vnodeToMove.el, oldStartVNode.el)// 由于旧 children 中该位置的节点所对应的真实 DOM 已经被移动,所以将其设置为 undefinedprevChildren[idxInOld] = undefined}// 将 newStartIdx 下移一位newStartVNode = nextChildren[++newStartIdx]}
}
  • 因为旧节点已经找到并处理过了,所以后续的双端比较需要跳过处理过的节点
  • 将旧 children 中的 li-b 节点变成 undefined,然后旧节点指针遇到时跳过
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {// undefined 跳过if (!oldStartVNode) {oldStartVNode = prevChildren[++oldStartIdx]} else if (!oldEndVNode) { // undefined 跳过oldEndVNode = prevChildren[--oldEndIdx]} else if (oldStartVNode.key === newStartVNode.key) {// 省略...} else if (oldEndVNode.key === newEndVNode.key) {// 省略...} else if (oldStartVNode.key === newEndVNode.key) {// 省略...} else if (oldEndVNode.key === newStartVNode.key) {// 省略...} else {const idxInOld = prevChildren.findIndex(node => node.key === newStartVNode.key)if (idxInOld >= 0) {const vnodeToMove = prevChildren[idxInOld]patch(vnodeToMove, newStartVNode, container)prevChildren[idxInOld] = undefinedcontainer.insertBefore(vnodeToMove.el, oldStartVNode.el)}newStartVNode = nextChildren[++newStartIdx]}
}

新增情况一

  • 节点所在的双端不满足四种策略,也不满足能找到旧节点

在这里插入图片描述

  • 在新 children 中,节点 li-d 是一个全新的节点。在这个例子中 ①、②、③、④ 这四步的比对仍然无法找到可复用节点,所以我们会尝试拿着新 children 中的 li-d 节点去旧的 children 寻找与之拥有相同 key 值的节点,结果很显然,我们无法找到这样的节点。这时说明该节点是一个全新的节点,我们应该将其挂载到容器中,由于 li-d 节点的位置索引是 newStartIdx,这说明 li-d 节点是当前这一轮比较中的“第一个”节点,所以只要把它挂载到位于 oldStartIdx 位置的节点所对应的真实 DOM 前面就可以了,即 oldStartVNode.el
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (!oldStartVNode) {oldStartVNode = prevChildren[++oldStartIdx]} else if (!oldEndVNode) {oldEndVNode = prevChildren[--oldEndIdx]} else if (oldStartVNode.key === newStartVNode.key) {// 省略...} else if (oldEndVNode.key === newEndVNode.key) {// 省略...} else if (oldStartVNode.key === newEndVNode.key) {// 省略...} else if (oldEndVNode.key === newStartVNode.key) {// 省略...} else {const idxInOld = prevChildren.findIndex(node => node.key === newStartVNode.key)if (idxInOld >= 0) {const vnodeToMove = prevChildren[idxInOld]patch(vnodeToMove, newStartVNode, container)prevChildren[idxInOld] = undefinedcontainer.insertBefore(vnodeToMove.el, oldStartVNode.el)} else {// 使用 mount 函数挂载新节点,如果传入了最后一个参数,内部也是使用 insertBeforemount(newStartVNode, container, false, oldStartVNode.el)}newStartVNode = nextChildren[++newStartIdx]}
}

新增情况二

  • 节点所在的双端优先满足了四种策略

在这里插入图片描述

  • 最终双端比较完成后结果
    在这里插入图片描述
  • oldEndIdx 将比 oldStartIdx 的值要小,对 oldEndIdx 和 oldStartIdx 的值进行检查,如果在循环结束之后 oldEndIdx 的值小于 oldStartIdx 的值则说明新的 children 中存在还没有被处理的全新节点,这时我们应该调用 mount 函数将其挂载到容器元素中,观察上图可知,我们只需要把这些全新的节点添加到 oldStartIdx 索引所指向的节点之前即可
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {// 省略...
}
if (oldEndIdx < oldStartIdx) {// 添加新节点for (let i = newStartIdx; i <= newEndIdx; i++) {mount(nextChildren[i], container, false, oldStartVNode.el)}
}

删除节点

在这里插入图片描述

  • 在进行双端比较后
    在这里插入图片描述
  • 此时 newEndIdx 的值小于 newStartIdx 的值,所以循环将终止,但是通过上图可以发现,旧 children 中的 li-b 节点没有得到被处理的机会,我们应该将其移除才行,循环结束后,一旦满足条件 newEndIdx < newStartId 则说明有元素需要被移除
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {// 省略...
}
if (oldEndIdx < oldStartIdx) {// 添加新节点for (let i = newStartIdx; i <= newEndIdx; i++) {mount(nextChildren[i], container, false, oldStartVNode.el)}
} else if (newEndIdx < newStartIdx) {// 移除操作for (let i = oldStartIdx; i <= oldEndIdx; i++) {container.removeChild(prevChildren[i].el)}
}

双端比较的优势

  • 对于如下节点情况

在这里插入图片描述

  • 如果采用 React 根据相对位置的diff 方式来对上例进行更新,则会执行两次移动操作
    • 首先会把 li-a 节点对应的真实 DOM 移动到 li-c 节点对应的真实 DOM 的后面
    • 接着再把 li-b 节点所对应的真实 DOM 移动到 li-a 节点所对应真实 DOM 的后面,即:
      在这里插入图片描述
  • 如果采用 vue 的双端比较 diff
    • 第一步:拿旧 children 中的 li-a 和新 children 中的 li-c 进行比对,由于二者 key 值不同,所以不可复用,什么都不做。
    • 第二步:拿旧 children 中的 li-c 和新 children 中的 li-b 进行比对,不可复用,什么都不做。
    • 第三步:拿旧 children 中的 li-a 和新 children 中的 li-b 进行比对,不可复用,什么都不做。
    • 第四步:拿旧 children 中的 li-c 和新 children 中的 li-c 进行比对,此时,两个节点拥有相同的 key 值,可复用。

在这里插入图片描述

  • 可以看到,我们只通过一次 DOM 移动,就使得真实 DOM 的顺序与新 children 中节点的顺序一致,后序只需要 patch 不需要移动。双端比较更加符合人类思维,在移动 DOM 方面更具有普适性,能减少因为 DOM 结构的差异而产生的影响

相关文章:

vue diff 双端比较算法

文章目录 双端指针比较策略命中策略四命中策略二命中策略三命中策略一未命中四种策略&#xff0c;遍历旧节点列表新增情况一新增情况二 删除节点双端比较的优势 双端指针 使用四个变量 oldStartIdx、oldEndIdx、newStartIdx 以及 newEndIdx 分别存储旧 children 和新 children …...

初识React: 基础(概念 特点 高效原因 虚拟DOM JSX语法 组件)

1.什么是React? React是一个由Facebook开源的JavaScript库&#xff0c;它主要用于构建用户界面。React的特点是使用组件化的思想来构建界面&#xff0c;使得代码的可复用性和可维护性大大提高。React还引入了虚拟DOM的概念&#xff0c;减少了对真实DOM的直接操作&#xff0c;…...

自监督去噪:Neighbor2Neighbor原理分析与总结

文章目录 1. 方法原理1.1 先前方法总结1.2 Noise2Noise回顾1.3 从Noise2Noise到Neighbor2Neighbor1.4 框架结构2. 实验结果3. 总结 文章链接&#xff1a;https://arxiv.org/abs/2101.02824 参考博客&#xff1a;https://arxiv.org/abs/2101.02824 1. 方法原理 1.1 先前方法总…...

简单工厂模式(Simple Factory)

简单工厂模式&#xff0c;又称为静态工厂方法(Static Factory Method)模式。在简单工厂模式中&#xff0c;可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例&#xff0c;被创建的实例通常都具有共同的父类。简单工厂模式不属于GoF的23个…...

Agent:OpenAI的下一步,亚马逊云科技站在第5层

什么是Agent&#xff1f;在大模型语境下&#xff0c;可以理解成能自主理解、规划、执行复杂任务的系统。Agent也将成为新的起点&#xff0c;成为各行各业构建新一代AI应用必不可少的组成部分。 对此&#xff0c;初创公司Seednapse AI创始人提出构建AI应用的五层基石理论&#…...

JMeter 4.x 简单使用

文章目录 前言JMeter 4.x 简单使用1. 启动2. 设置成中文3. 接口测试3.1. 设置线程组3.2. HTTP信息请求头管理器3.3. 添加HTTP请求默认值3.4. 添加HTTP cookie 管理3.5. 添加http请求3.5.1. 添加断言 3.6. 添加监听器-查看结果树3.7. 添加监听器-聚合报告 4. 测试 前言 如果您觉…...

深入NLTK:Python自然语言处理库高级教程

在前面的初级和中级教程中&#xff0c;我们了解了NLTK库中的基本和进阶功能&#xff0c;如词干提取、词形还原、n-gram模型和词云的绘制等。在本篇高级教程中&#xff0c;我们将深入探索NLTK的更多高级功能&#xff0c;包括句法解析、命名实体识别、情感分析以及文本分类。 一…...

React 用来解析html 标签的方法

在React中&#xff0c;解析HTML标签通常是使用JSX&#xff08;JavaScript XML&#xff09;语法的一部分。JSX允许您在JavaScript代码中编写类似HTML的标记&#xff0c;然后通过React进行解析和渲染。 以下是React中解析HTML标签的几种常见方式&#xff1a; 直接在JSX中使用标…...

【C++】做一个飞机空战小游戏(五)——getch()控制两个飞机图标移动(控制光标位置)

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…...

Flask 是什么?Flask框架详解及实践指南

Flask 是一个轻量级的 Python Web 框架&#xff0c;它被广泛用于构建 Web 应用程序和 API。Flask 简单易用&#xff0c;具有灵活性和可扩展性&#xff0c;是许多开发者喜欢用其构建项目的原因。本文将介绍 Flask 是什么以及如何使用它来构建 Web 应用程序&#xff0c;同时提供一…...

C. Mark and His Unfinished Essay - 思维

分析&#xff1a; 直接模拟操作会mle&#xff0c;可以每次复制记录对应源字符串的下标&#xff0c;可以记录每次字符串增加的长度的左右端点下标&#xff0c;可以发现左端点与读入的l是对应的&#xff0c;因此就可以向前移到l的位置&#xff0c;这样层层递归&#xff0c;就能找…...

Java的变量与常量

目录 变量 声明变量 变量的声明类型 变量的声明方式&#xff1a;变量名 变量名的标识符 初始化变量 常量 关键字final 类常量 总结 变量和常量都是用来存储值和数据的基本数据类型存储方式&#xff0c;但二者之间有一些关键差别。 变量 在Java中&#xff0c;每个变…...

C# Blazor 学习笔记(6):热重置问题解决

文章目录 前言热重置问题描述解决方法演示 总结 前言 我最近在使用Blazor的时候&#xff0c;使用了BootstrapBlazor&#xff08;以下简称BB&#xff09;创建模板的时候&#xff0c;发现热重置无效。经过了一上午的折腾&#xff0c;我终于解决了这个问题。 热重置 问题描述 …...

一百四十六、Xmanager——Xmanager5连接Xshell7并控制服务器桌面

一、目的 由于kettle安装在Linux上&#xff0c;Xshell启动后需要Xmanager。而Xmanager7版本受限、没有免费版&#xff0c;所以就用Xmanager5去连接Xshell7 二、Xmanager5安装包来源 &#xff08;一&#xff09;注册码 注册码&#xff1a;101210-450789-147200 &#xff08…...

用Rust实现23种设计模式之 模板方法模式

关注我&#xff0c;学习Rust不迷路&#xff01;&#xff01; 模板方法模式是一种行为型设计模式&#xff0c;它定义了一个算法的骨架&#xff0c;将一些步骤的实现延迟到子类中。以下是模板方法模式的优点和使用场景&#xff1a; 优点&#xff1a; 提高代码复用性&#xff1…...

python与深度学习(十三):CNN和IKUN模型

目录 1. 说明2. IKUN模型2.1 导入相关库2.2 建立模型2.3 模型编译2.4 数据生成器2.5 模型训练2.6 模型保存2.7 模型训练结果的可视化 3. IKUN的CNN模型可视化结果图4. 完整代码 1. 说明 本篇文章是CNN的另外一个例子&#xff0c;IKUN模型&#xff0c;是自制数据集的例子。之前…...

题目:2283.判断一个数的数字计数是否等于数位的值

​​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;2283. 判断一个数的数字计数是否等于数位的值 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 两次遍历。第一次对字符串中每个出现的数字计数。第二次比较数字计数与数位的值是否相等。 解…...

任务14、无缝衔接,MidJourney瓷砖(Tile)参数制作精良贴图

14.1 任务概述 在这个实验任务中,我们将深入探索《Midjourney Ai绘画》中的Tile技术和其在艺术创作中的具有挑战性的应用。此任务将通过理论学习与实践操作相结合的方式,让参与者更好地理解Tile的核心概念,熟练掌握如何在Midjourney平台上使用Tile参数,并实际运用到AI绘画…...

【uniapp APP如何优化】

以下是一些可以进行优化的建议&#xff1a; 1. 减少网络请求次数&#xff1a;尽量避免在首页加载时请求大量数据&#xff0c;可以考虑使用分页加载&#xff0c;或者使用下拉刷新和上拉加载更多的方式。 2. 减小图片大小&#xff1a;使用压缩图片的工具&#xff0c;可以尽可能…...

uni-app——下拉框多选

一、组件components/my-selectCheckbox.vue <template><view class"uni-stat__select"><span v-if"label" class"uni-label-text">{{label &#xff1a;}}</span><view class"uni-stat-box" :class"…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...