Vue的Diff算法原理
Vue中的Diff算法(差异算法)是虚拟DOM的核心优化手段,用于对比新旧虚拟DOM树,找出最小变更,高效更新真实DOM,其设计目标是减少DOM操作次数,提升渲染性能
diff算法:
特点:
- 只比较同层的节点,不跨层级对比(若节点跨层级移动,视为销毁和新建),跨层级对比时间复杂度从O(n^3)降为O(n),极大提高效率
- 同层比较时,如果类型不同,会把该节点和该节点的所有子节点全部销毁
- 相同类型的节点(标签名和key均相同)会被复用,避免重新创建DOM元素
vue2的diff算法是双端比较,双端比较是 Vue 中 Diff 算法用于对比新旧子节点列表的一种核心策略,通过头尾交叉对比的方式,尽可能复用现有 DOM 节点,减少不必要的移动或重建。其核心思想是用四个指针(旧头、旧尾、新头、新尾)同时从新旧子节点列表的两端向中间扫描,快速匹配可复用的节点,优化性能。
patch源码
function patch(oldVnode, vnode, hydrating, removeOnly) {// 1. 判断新节点是否为空(卸载旧节点)if (isUndef(vnode)) {if (isDef(oldVnode)) invokeDestroyHook(oldVnode); // 触发旧节点销毁钩子return;}let isInitialPatch = false;const insertedVnodeQueue = []; // 收集待触发插入钩子的节点// 2. 旧节点不存在 → 直接创建新节点(首次渲染)if (isUndef(oldVnode)) {isInitialPatch = true;createElm(vnode, insertedVnodeQueue); // 创建 DOM 元素} // 3. 旧节点存在 → 对比更新else {const isRealElement = isDef(oldVnode.nodeType); // 判断旧节点是否为真实 DOM(SSR 场景)// 4. 新旧节点可复用 → 精细化更新(核心 Diff 逻辑)if (!isRealElement && sameVnode(oldVnode, vnode)) {patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);} // 5. 节点不可复用 → 替换旧节点else {// 5.1 处理服务端渲染(SSR)激活逻辑if (isRealElement) {if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {oldVnode.removeAttribute(SSR_ATTR); // 移除 SSR 标记属性hydrating = true;}// 5.2 执行客户端激活(hydrate)if (isTrue(hydrating)) {if (hydrate(oldVnode, insertedVnodeQueue)) {invokeInsertHook(vnode, insertedVnodeQueue, true); // 触发插入钩子return;}}}// 5.3 创建新节点并替换旧节点const oldElm = oldVnode.elm; // 旧节点对应的真实 DOMconst parentElm = nodeOps.parentNode(oldElm); // 父元素createElm(vnode, insertedVnodeQueue, parentElm, nodeOps.nextSibling(oldElm)); // 插入新节点// 5.4 销毁旧节点if (isDef(parentElm)) {removeVnodes(parentElm, [oldVnode], 0, 0); // 移除旧 DOM}}}// 6. 触发插入生命周期钩子(如 mounted)invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);return vnode.elm; // 返回新 DOM 元素
}
patchVnode函数
执行流程:
- 如果oldVnode和vnode指向同一个对象,直接return,因为同一地址的内容一定相同
- oldVnode的DOM属性变成Vnode,关联到真实的DOM,对真实DOM进行update,class,style,props,events均被进行更新
- oldVnode和Vnode如果都是文本节点,看是否为单一节点,是的话直接更新内容即可,vnode.text
- oldVnode有子节点,Vnode没有子节点,删除oldVnode
- oldVnode没有子节点,Vnode有子节点,添加节点,并转换为真实DOM,最后挂载到DOM上
- oldVnode和Vnode都有子节点,updateChildren
patchVnode核心逻辑:
function patchVnode (oldVnode, // 旧虚拟节点vnode, // 新虚拟节点insertedVnodeQueue, // 插入队列(用于触发插入钩子)ownerArray, // 父级节点数组(用于子节点复用)index, // 当前节点在父数组中的索引removeOnly // 特殊模式标记(仅用于 transition-group)
) {// 1. 新旧节点地址相同 → 跳过更新if (oldVnode === vnode) return// 2. 克隆复用节点(维护不可变性)if (isDef(vnode.elm) && isDef(ownerArray)) {vnode = ownerArray[index] = cloneVNode(vnode)}// 3. 复用旧节点的 DOM 元素const elm = vnode.elm = oldVnode.elm// 4. 处理异步占位符(SSR 激活)if (isTrue(oldVnode.isAsyncPlaceholder)) {if (isDef(vnode.asyncFactory.resolved)) {hydrate(oldVnode.elm, vnode, insertedVnodeQueue)} else {vnode.isAsyncPlaceholder = true}return}// 5. 静态节点复用优化(跳过 Diff)if (isTrue(vnode.isStatic) &&isTrue(oldVnode.isStatic) &&vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.componentInstance = oldVnode.componentInstancereturn}// 6. 执行 prepatch 钩子(组件级预处理)let iconst data = vnode.dataif (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {i(oldVnode, vnode)}// 7. 获取新旧子节点const oldCh = oldVnode.childrenconst ch = vnode.children// 8. 更新节点属性(核心)if (isDef(data) && isPatchable(vnode)) {// 更新属性(class, style, attrs, domProps, events...)for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)// 调用自定义 update 钩子if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)}// 9. 处理文本节点(优化路径)if (isUndef(vnode.text)) {// 非文本节点 → 处理子节点if (isDef(oldCh) && isDef(ch)) {// 新旧都有子节点 → 双端对比算法if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)} else if (isDef(ch)) {// 仅新节点有子节点 → 批量添加if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)} else if (isDef(oldCh)) {// 仅旧节点有子节点 → 批量移除removeVnodes(elm, oldCh, 0, oldCh.length - 1)} else if (isDef(oldVnode.text)) {// 旧节点是文本 → 清空nodeOps.setTextContent(elm, '')}} else if (oldVnode.text !== vnode.text) {// 文本节点 → 直接更新内容nodeOps.setTextContent(elm, vnode.text)}// 10. 执行 postpatch 钩子(组件级后处理)if (isDef(data) && isDef(i = data.hook) && isDef(i = i.postpatch)) {i(oldVnode, vnode)}
}
双端比较原理(updateChildren函数)
vue2diff
function vue2Diff(prevChildren, nextChildren, parent) {let oldStartIndex = 0;let oldEndIndex = prevChildren.length - 1;let newStartIndex = 0;let newEndIndex = newChildren.length - 1;let oldStartNode = prevChildren[oldStartIndex];let oldEndNode = prevChildren[oldEndIndex];let newStartNode = nextChildren[newStartIndex];let newEndNode = nextChildren[newEndIndex];while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {// 头头 尾尾 头尾 尾头if (oldStartNode.key == newStartNode.key) {patch(oldStartNode, newStartNode, parent);oldStartIndex++;newStartIndex++;oldStartNode = prevChildren[oldStartIndex];newStartNode = nextChildren[newStartIndex];} else if (oldEndNode.key == newEndNode.key) {patch(oldEndNode, newEndNode, parent);oldEndIndex--;newEndIndex--;oldEndNode = prevChildren[oldEndIndex];newEndNode = nextChildren[newEndIndex];} else if (oldStartNode.key == newEndNode.key) {patch(oldStartNode, newEndNode, parent);parent.insertBefore(oldStartNode.el,oldEndNode.el.nextSibling)oldStartIndex++;newEndIndex--;oldStartNode = prevChildren[oldStartIndex];newEndNode = nextChildren[newEndIndex];} else if (oldEndNode.key == newStartNode.key) {patch(oldEndNode, newStartNode, parent);parent.insertBefore(oldEndNode.el,oldStartNode.el)oldEndIndex--;newStartIndex++;oldEndNode = prevChildren[oldEndIndex];newStartNode = nextChildren[newStartIndex];}else{// 四次比较都没有比较到// 在prevChildren 当前的key,拿着这个key,遍历nextChildren找有没有相同的keylet newKey =newStartNode.key,oldIndex =prevChildren.findIndex(child=>child.key===newKey)if(oldIndex>-1){let oldNode =prevChildren[oldIndex]patch(oldNode,newStartNode,parent)parent.insertBefore(oldNode.el,oldStartNode.el)prevChildren[oldIndex]=undefined}else {mount(newStartNode,parent,oldStartNode.el)// 在旧的开头创建一个新的节点}newStartNode =nextChildren[++newStartIndex]//更新起始节点}}
// 此时新节点还有节点,旧节点已经处理完毕if(oldEndIndex<oldStartIndex){for(let i=newStartIndex;i<=newEndIndex;i++){mount(nextChildren[i])}// 此时新节点已经处理完毕,没有处理的旧节点需要删除}else if(newEndIndex<newStartIndex){parent.removeChild(prevChildren[i])}
}
vue2比较的时候是全量比较,每次比较都要遍历所有旧节点或者新节点,比较消耗内存
vue3diff优化点
1.静态标记+非全量Diff:Vue3在创建虚拟DOM树的时候,会根据DOM中的内容会不会发生变化,添加一个静态标记,之后在与上次虚拟节点进行对比的时候,就只会对比这些带有静态标记的节点。
2. 使用最长递增子序列优化对比流程,可以最大程度的减少DOM的移动,达到最少的DOM操作
实现思路:
vue3的diff算法其中有两个概念,第一个是相同的前置和后置元素的预处理;第二个则是最长递增子序列
寻找source中最长递增子序列 ,如果有多个最长递增子序列,则第一个最长递增子序列不动,移动其他元素使得在prevChildren中的元素除了前面和后面相同的元素按照source数组的方式进行移动,比如上述中,prevChildren中的b应该放在d后面,即source1在3后面
function vue3Diff(prevChildren, nextChildren, parent) {let j = 0;let prevEnd = prevChildren.length - 1;let nextEnd = nextChildren.length - 1;let prevNode = prevChildren[j];let nextNode = nextChildren[j];// 1. 从前往后同步相同的前缀节点while (prevNode && nextNode && prevNode.key === nextNode.key) {patch(prevNode, nextNode, parent);j++;prevNode = prevChildren[j];nextNode = nextChildren[j];}// 2. 从后往前同步相同的后缀节点prevNode = prevChildren[prevEnd];nextNode = nextChildren[nextEnd];while (prevNode && nextNode && prevNode.key === nextNode.key) {patch(prevNode, nextNode, parent);prevEnd--;nextEnd--;prevNode = prevChildren[prevEnd];nextNode = nextChildren[nextEnd];}// 3. 处理新增节点(旧列表处理完,新列表有剩余)if (j > prevEnd && j <= nextEnd) {const nextPos = nextEnd + 1;const anchor = nextPos < nextChildren.length ? nextChildren[nextPos].el : null;while (j <= nextEnd) {mount(nextChildren[j], parent, anchor);j++;}return;}// 4. 处理删除节点(新列表处理完,旧列表有剩余)if (j > nextEnd) {while (j <= prevEnd) {unmount(prevChildren[j], parent);j++;}return;}// 5. 处理中间乱序序列(核心优化逻辑)const nextStart = j;const prevStart = j;const nextLeft = nextEnd - j + 1;// 5.1 构建 key 到新索引的映射const keyToNewIndexMap = new Map();for (let i = nextStart; i <= nextEnd; i++) {keyToNewIndexMap.set(nextChildren[i].key, i);}// 5.2 遍历旧列表,标记可复用节点const newIndexToOldIndexMap = new Array(nextLeft).fill(-1);let patched = 0;let moved = false;let maxNewIndexSoFar = 0;for (let i = prevStart; i <= prevEnd; i++) {const prevChild = prevChildren[i];if (patched >= nextLeft) {unmount(prevChild, parent);continue;}const newIndex = keyToNewIndexMap.get(prevChild.key);if (newIndex === undefined) {unmount(prevChild, parent);} else {newIndexToOldIndexMap[newIndex - nextStart] = i;if (newIndex >= maxNewIndexSoFar) {maxNewIndexSoFar = newIndex;} else {moved = true; // 出现逆序需要移动}patch(prevChild, nextChildren[newIndex], parent);patched++;}}// 5.3 计算最长递增子序列(LIS)const increasingSequence = moved ? getSequence(newIndexToOldIndexMap) : [];let seqIndex = increasingSequence.length - 1;// 5.4 从右向左处理移动和新增for (let i = nextLeft - 1; i >= 0; i--) {const nextIndex = nextStart + i;const nextChild = nextChildren[nextIndex];const anchor = nextIndex + 1 < nextChildren.length ? nextChildren[nextIndex + 1].el : null;if (newIndexToOldIndexMap[i] === -1) { // 新增节点mount(nextChild, parent, anchor);} else if (moved) { // 需要移动的节点if (seqIndex < 0 || i !== increasingSequence[seqIndex]) {move(nextChild.el, parent, anchor);} else {seqIndex--;}}}
}// 最长递增子序列算法(O(n log n))
function getSequence(arr) {const p = arr.slice();const result = [0];let i, j, u, v, c;const len = arr.length;for (i = 0; i < len; i++) {if (arr[i] !== 0) {j = result[result.length - 1];if (arr[j] < arr[i]) {p[i] = j;result.push(i);continue;}u = 0;v = result.length - 1;while (u < v) {c = (u + v) >> 1;if (arr[result[c]] < arr[i]) {u = c + 1;} else {v = c;}}if (arr[i] < arr[result[u]]) {if (u > 0) p[i] = result[u - 1];result[u] = i;}}}u = result.length;v = result[u - 1];while (u-- > 0) {result[u] = v;v = p[v];}return result;
}
一、双端对比,跳过相同前后缀
-
同步前缀节点(从左向右)
while (旧头节点.key === 新头节点.key) {执行 patch 更新属性;旧/新头指针后移; }
作用:跳过所有相同的前置节点(如列表头部未变的部分)。
-
同步后缀节点(从右向左)
while (旧尾节点.key === 新尾节点.key) {执行 patch 更新属性;旧/新尾指针前移; }
作用:跳过所有相同的后置节点(如列表尾部未变的部分)。
双端对比后场景示例:
旧列表:[A, B, C, D, E]
新列表:[A, D, B, C, F]↑ ↑ ↑同步前缀 同步后缀
处理后的中间待处理旧区间:B, C, D
新列表的中间待新区间:D, B, C, F
二、处理极端情况(新增/删除)
若双端对比后旧或新列表已经处理完毕:
-
旧列表处理完 → 新增新列表剩余节点
while(新列表还有未处理节点){创建新节点并插入到尾部; }
-
新列表处理完 → 删除旧列表剩余节点
while(旧列表还有未处理节点){卸载旧节点及其 DOM; }
三、处理中间乱序部分(LIS 优化核心)
当双端对比后新旧列表中间存在乱序时,进入最关键的 最长递增子序列优化。
步骤分解:
-
构建 Key 到新索引的映射
const keyToNewIndexMap = new Map(); // { D → 1, B → 2, C → 3, F → 4 }
- 目的:通过
key
快速查找新节点在旧列表中的位置。
- 目的:通过
-
遍历旧列表,标记可复用的节点位置
生成newIndexToOldIndexMap
数组:// 新索引 → 旧索引(偏移量处理) // 示例结果:[2, 0, 1, -1](-1 表示新列表中新增)
- 意义:
[新位置0对应旧索引2, 新位置1对应旧索引0...]
- 意义:
-
计算最长递增子序列(LIS)
使用算法(如动态规划 + 二分)找出递增最多的旧索引顺序:const increasingSequence = getSequence(newIndexToOldIndexMap); // 示例结果:[0, 1] → 对应新位置的索引0和1(即 B 和 C)
- 优化逻辑:最长递增序列中的节点顺序未变,无需移动 DOM。
-
反向遍历新列表中间部分,更新或移动节点
从右向左遍历新列表中间: if (节点是新增的) {创建新节点; } else if (需要移动) {if (当前节点不在 LIS 中) {移动 DOM 到正确位置;} }
- 为什么反向遍历?
DOM 插入操作需要锚点(anchor
),反向处理确保锚点节点已处于正确位置。
- 为什么反向遍历?
四、示例图解:具体更新操作
以以下新旧列表为例:
旧列表: [A, B, C, D]
新列表: [A, D, B, C, F] // 新增 F,移动 D 到 B 前
-
双端对比后:
- 处理完前后不变的节点(A);
- 中间待处理旧区间: [B, C, D]
- 中间待处理新区间: [D, B, C, F]
-
LIS 优化流程:
- Key-To-Index 映射:
{ D:0, B:1, C:2, F:3 }
- newIndexToOldIndexMap: [2 (D旧索引), 0 (B旧索引), 1 (C旧索引), -1 (F新增)]
- 最长递增子序列:
[0,1]
(对应 B 和 C) - 操作步骤:
- 遍历新列表从右至左(F → C → B → D)
- 处理 F:newIndexToOldIndexMap[3] = -1 → 新增节点,追加到尾部。
- 处理 C:在 LIS 中 → 无需移动。
- 处理 B:在 LIS 中 → 无需移动。
- 处理 D:不在 LIS → 将旧 D 对应 DOM 移动到 B 之前。
- Key-To-Index 映射:
五、性能优化点
策略 | 效果 |
---|---|
双端对比快速跳过多余节点 | 避免不必要的遍历,快速缩小处理范围 |
Key 映射快速查找节点 | 直接定位可复用节点,无需遍历旧列表 |
LIS 减少移动次数 | 仅移动非递增序列中的节点,将 DOM 操作次数降到最低(根据数据集降幅可达50%↑) |
批量 DOM 操作 | 通过反向遍历统一位置计算,减少 DOM 重排次数 |
总结
Vue3 的 Diff 算法通过 双端对比快速锁定差异范围 + 最长递增子序列最小化移动次数,在大部分实际场景中,DOM 操作量与 Vue2 相比大幅减少。尤其是对顺序调整较多的列表(如拖拽排序),性能优势更为显著。
相关文章:
Vue的Diff算法原理
Vue中的Diff算法(差异算法)是虚拟DOM的核心优化手段,用于对比新旧虚拟DOM树,找出最小变更,高效更新真实DOM,其设计目标是减少DOM操作次数,提升渲染性能 diff算法: 特点:…...
CentOS系统-超详细的Kubernetes集群搭建教程(kubernetes:1.28.2)
小伙伴们,今天给大家带来一份超详细的Kubernetes集群搭建教程,保证让你从环境准备到安装验证,一路畅通无阻!🚀 🌈 一、环境准备 首先,咱们得确保硬件和软件环境都达标哦! &am…...
自动驾驶系列—GLane3D: Detecting Lanes with Graph of 3D Keypoints
🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…...
【Amazon 工具】在MacOS本地安装 AWS CLI、kubectl、eksctl工具
文章目录 安装 AWS CLI安装 kubectl安装 eksctl参考链接 安装 AWS CLI 创建访问密钥安装或更新 AWS CLI curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" sudo installer -pkg AWSCLIV2.pkg -target /要验证 Shell 是否可以在 $PAT…...
基于GTID的主从复制
MySQL主从复制实战指南(基于二进制日志)-CSDN博客 二、基于GTID的主从复制 基于 GTID 方式:全局事务标示符,自mysql5.6版本开启的新型复制方式。 GTID的组成:server_uuid:序列号 UUID:每个m…...
linux多线(进)程编程——(8)多进程的冲突问题
前言 随着时间的推移,共享内存已经在修真界已经沦为禁术。因为使用这种方式沟通的两人往往会陷入到走火入魔的状态,思维扭曲。进程君父子见到这种情况,连忙开始专研起来,终于它们发现了共享内存存在的问题: 进程间冲…...
数据结构——八大排序算法
排序在生活中应用很多,对数据排序有按成绩,商品价格,评论数量等标准来排序。 数据结构中有八大排序,插入、选择、快速、归并四类排序。 目录 插入排序 直接插入排序 希尔排序 选择排序 堆排序 冒泡排序 快速排序 hoare…...
线性代数 | 知识点整理 Ref 1
注:本文为 “线性代数 | 知识点整理” 相关文章合辑。 因 csdn 篇幅合并超限分篇连载,本篇为 Ref 1。 略作重排,未整理去重。 图片清晰度限于引文原状。 如有内容异常,请看原文。 线性代数知识汇总 Arrow 于 2016-11-27 16:27:5…...
Docker 设置镜像源后仍无法拉取镜像问题排查
#记录工作 Windows系统 在使用 Docker 的过程中,许多用户会碰到设置了国内镜像源后,依旧无法拉取镜像的情况。接下来,记录了操作要点以及问题排查方法,帮助我们顺利解决这类问题。 Microsoft Windows [Version 10.0.27823.1000…...
线性回归 (Linear Regression) 多项式回归 (Polynomial Regression)
目录 线性回归 (Linear Regression)单变量线性回归 (Univariate linear regression)代价函数 (Cost function)梯度下降 (gradient descent) 及公式由来梯度下降的变体Quiz多类特征 (Multiple features)多元线性回归 (Multiple linear regression)向量化 (Vectorization)正规方程…...
AI在能源消耗管理及能源效率提升中的核心应用场景及技术实现
以下是 AI在能源消耗管理及能源效率提升中的核心应用场景及技术实现,分领域详细说明: 1. 实时能源监测与异常检测 AI技术应用: 物联网(IoT) 传感器数据采集:实时收集设备、建筑或工厂的能耗数据ÿ…...
dumpsys--音频服务状态信息
Audio相关的信息获取指令: dumpsys media.audio_flinger dumpsys media.audio_policy dumpsys audio media.audio_flinger dumpsys media.audio_flinger 用于获取 AudioFlinger 服务的详细状态信息。 1. 命令作用 该命令输出当前系统的 音频设备状态、活跃音频流…...
JavaScript模块化开发:CommonJS、AMD到ES模块
引言 在Web开发的早期阶段,JavaScript代码通常被编写在一个庞大的文件中或分散在多个脚本标签里,这种方式导致了全局变量污染、依赖关系难以管理、代码复用困难等问题。随着Web应用日益复杂,模块化编程成为了解决这些问题的关键。本文将带您…...
面试情景题:企业内部系统如何做微前端拆分,如何通信?
在前端开发领域,技术的演进总是伴随着业务需求的复杂化与规模化而不断向前推进。近年来,微前端(Micro Frontends)作为一种全新的架构理念,逐渐成为解决大型前端应用复杂性的重要手段。与传统的单体前端应用不同&#x…...
OpenHarmony Camera开发指导(五):相机预览功能(ArkTS)
预览是在相机启动后实时显示场景画面,通常在拍照和录像前执行。 开发步骤 创建预览Surface 如果想在屏幕上显示预览画面,一般由XComponent组件为预览流提供Surface(通过XComponent的getXcomponentSurfaceId方法获取surfaceid)&…...
鸿蒙API15 “一多开发”适配:解锁黄金三角法则,开启高效开发新旅程
一、引言 在万物互联的时代浪潮中,鸿蒙操作系统以其独特的 “一多开发” 理念,为开发者打开了一扇通往全场景应用开发的新大门。“一多开发”,即一次开发,多端部署 ,旨在让开发者通过一套代码工程,就能高效…...
RAG(检索增强生成)、ReAct(推理与行动) 和 多模态AI 的详细解析,包括三者的定义、工作原理、应用场景及协同关系
以下是 RAG(检索增强生成)、ReAct(推理与行动) 和 多模态AI 的详细解析,包括三者的定义、工作原理、应用场景及协同关系: 一、RAG(Retrieval-Augmented Generation) 1. 核心原理 …...
网络安全知识点2
1.虚拟专用网VPN:VPN用户在此虚拟网络中传输私网流量,在不改变网络现状的情况下实现安全,可靠的连接 2.VPN技术的基本原理是利用隧道技术,对传输报文进行封装,利用VPN骨干网建立专用数据传输通道,实现报文…...
DS-SLAM 运动一致性检测的源码解读
运动一致性检测是Frame.cc的Frame::ProcessMovingObject(const cv::Mat &imgray)函数。 对应DS-SLAM流程图Moving consistency check的部分 把这个函数单独摘出来,写了一下对两帧检测,查看效果的程序: #include <opencv2/opencv.hpp…...
VSTO幻灯片退出播放(C#模拟键盘鼠标的事件)
今天遇到了个问题,幻灯片放映到某一页时需要退出播放,没有找到对应的方法,所以想到了直接通过ESC键可以退出,所以模拟执行了一下ESC键,发现真的可以。在此记录一下。 C# 模拟键盘鼠标的事件整理 1、模拟键盘2、模拟鼠标…...
Echarts柱状图斜线环纹(图形的贴花图案)
单独设置 <!--此示例下载自 https://echarts.apache.org/examples/zh/editor.html?cbar-stack&codePYBwLglsB2AEC8sDeAoWszGAG0iAXMmuhgE4QDmFApqYQOQCGAHhAM70A0x6L7ACsAjQwtQqhIkwATxDUGbABaMAJsADu9HrAC-xHd3TZqNaCvEHiFcuaKTjAMzAMAzAFIu28hUXPY9ABYPQxIAI2AwTABbV…...
前端页面效果收集
文章目录 数字雨元素融化动画电子签名共享屏幕 数字雨 <canvas id"matrix"></canvas> <script>const canvas document.getElementById(matrix);const ctx canvas.getContext(2d);canvas.width window.innerWidth;canvas.height window.innerH…...
ASP.NET Core Web API 配置系统集成
文章目录 前言一、配置源与默认设置二、使用步骤1)创建项目并添加配置2)配置文件3)强类型配置类4)配置Program.cs5)控制器中使用配置6)配置优先级测试7)动态重载配置测试8)运行结果示…...
【hadoop】基于hive的B站用户行为大数据分析
1.需求分析 b站现在积累有用户数据和视频列表数据,为了配合市场部门做好用户运营工作,需要对b站的用户行为进行分析,其具体需求如下所示: 统计b站视频不同评分等级(行转列)的视频数。 统计上传b站视频最多的…...
如何搭建符号执行环境并跑通第一个测试样例
0.如题 我使用的是verilator和klee进行符号执行的学习,目前还处于起步阶段,起步阶段除了要了解符号执行的定义和作用之外就是环境的搭建了,没想到搭建环境这一步就浪费了很多时间,主要问题出在按照官方的步骤进行搭建的时候&…...
基于 Django 进行 Python 开发
基于 Django 进行 Python 开发涉及多个方面的知识点,以下为你详细介绍: 1. Django 基础 项目与应用创建 借助django-admin startproject project_name来创建新的 Django 项目。利用python manage.py startapp app_name创建新的应用。项目结构 理解项目各文件和目录的作用,像…...
【含文档+PPT+源码】基于微信小程序的非遗文化黄梅戏宣传平台的设计与实现
课程目标: 教你从零开始部署运行项目,学习环境搭建、项目导入及部署,含项目源码、文档、数据库、软件等资料 课程简介: 本课程演示的是一款基于微信小程序的非遗文化黄梅戏宣传平台的设计与实现,主要针对计算机相关…...
使用DDR4控制器实现多通道数据读写(八)
一、 本章概括 在之前的章节已经详细介绍了DDR4的AXI协议,并实现了对DDR4简单的读写操作。这一章节来建立单通道的256位数据的读写,并放出工程框架,说明整体设计思路。 二、 工程框架 三、 设计思路 DDR内存通常用于大容量数据存储…...
Oracle 处理“不允许长度为0的列”(ORA-01723)问题解析
错误原因 当使用 CREATE TABLE ... AS SELECT 或创建物化视图时,若查询结果中的某列值为空字符串()或隐式 NULL 且未显式指定数据类型,Oracle 无法推断该列的长度和类型,从而抛出 ORA-01723: zero-length columns…...
燕山大学计算机网络之Java实现TCP数据包结构设计与收发
觉得博主写的好,给博主点点免费的关注吧! 目录 摘要.................................................................................................................... 4 前言.............................................................…...
Linux操作系统学习之---进程状态
目录 明确进程的概念: Linux下的进程状态: 虚拟终端的概念: 见一见现象: 用途之一 : 结合指令来监控进程的状态: 和进程强相关的系统调用函数接口: getpid()和getppid(): fork(): fork函数创建子进程的分流逻辑: 进程之间具有独立性: 进程中存在的写时拷贝: 见一见进程状态…...
Oracle 12.1.0.2补丁安装全流程
第一步,先进行备份 tar -cvf u01.tar /u01 第二步,更新OPatch工具包 根据补丁包中readme信息汇总提示的信息,下载对应版本的OPatch工具包,本次下载的版本为: p6880880_122010_Linux-x86-64.zip opatch版本为最新的…...
第19章:基于efficientNet实现的视频内容识别系统
目录 1.efficientNet 网络 2. 猫和老鼠 3. QT推理 4. 项目 1.efficientNet 网络 本章做了一个视频内容识别的系统 本文选用的模型是efficientNet b0版本 EfficientNet 是 Google 团队在 2019 年提出的一系列高效卷积神经网络模型,其核心思想是通过复合缩放&…...
【Java面试系列】Spring Cloud微服务架构中的分布式事务解决方案与Seata框架实现原理详解 - 3-5年Java开发必备知识
【Java面试系列】Spring Cloud微服务架构中的分布式事务解决方案与Seata框架实现原理详解 - 3-5年Java开发必备知识 引言 在微服务架构中,分布式事务是一个不可避免的挑战。随着业务复杂度的提升,如何保证跨服务的数据一致性成为了面试中的高频问题。本…...
div(HTML标准元素)和view(微信小程序专用组件)的主要区别体
div(HTML标准元素)和view(微信小程序专用组件)的主要区别体现在以下方面: 一、应用场景与开发框架 适用平台不同 div是HTML/CSS开发中通用的块级元素,用于Web页面布局;view是微信小程序专…...
AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析
以下是 AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析: 1. 多Agent协同的定义与核心目标 多Agent系统(MAS, Multi-Agent System): 由多个独立或协作的智能体(Agent)组成ÿ…...
03_Americanas精益管理项目_StarRocks
文章目录 03_StarRocks(一)StarRocks简介1、什么是StarRocks【理解】1)概述2)适用场景2、系统架构【理解】1)系统架构图2)数据管理3、使用【熟悉】(二)表设计4、StarRocks表设计【理解】1)列式存储2)索引3)加速处理5、数据模型【掌握】5-1 明细模型1)适用场景2)创…...
CSS进度条带斑马纹动画(有效果图)
效果图 .wxml <view class"tb"><view class"tb-line" style"transform:translateX({{w%}})" /> </view> <button bind:tap"updateLine">增加进度</button>.js Page({data: {w:0,},updateLine(){this.…...
C++ static的使用方法及不同作用
在 C 里,static 是一个用途广泛的关键字,在不同场景下有不同含义,下面为你详细介绍: 1. 全局变量前的 static 当 static 用在全局变量前时,它会改变变量的链接属性。 默认全局变量:默认的全局变量具有外…...
CSS 美化页面(四)
一、浮动float属性 属性值描述适用场景left元素向左浮动,腾出右侧空间供其他元素使用,其他内容会围绕在其右侧。横向排列元素(如导航菜单)、图文混排布局。right元素向右浮动,腾出左侧空间供其他元素使…...
驱动-原子操作
前面 对并发与竞争进行了实验, 两个 app 应用程序之间对共享资源的竞争访问引起了数据传输错误, 而在 Linux 内核中, 提供了四种处理并发与竞争的常见方法: 分别是原子操作、 自旋锁、 信号量、 互斥体, 这里了解下原子…...
Flutter ListView 详解
ListView 是 Flutter 中用于构建滚动列表的核心组件,支持垂直、水平滚动以及复杂的动态布局。本文将深入解析其核心用法、性能优化策略和高级功能实现,助你打造流畅高效的列表界面。 一、基础篇:快速构建各类列表 1. 垂直列表(默…...
关于视频的一些算法内容,不包含代码等
视频算法: 视频降噪, 去除视频中的噪音,提高图像质量 工作原理: 时域降噪:利用相邻帧之间的相似性,通过平均或滤波来减少随机噪声。 空域降噪:在单帧内使用滤波器(高斯滤波器&am…...
OpenCV 图形API(43)颜色空间转换-----将 BGR 图像转换为 LUV 色彩空间函数BGR2LUV()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 将图像从BGR色彩空间转换为LUV色彩空间。 该函数将输入图像从BGR色彩空间转换为LUV。B、G和R通道值的传统范围是0到255。 输出图像必须是8位无符…...
keil报错 ..\..\Libraries\CMSIS\stm32f10x.h(298): error: #67: expected a “}“
报错原因: 通常是由于启动文件、头文件定义或驱动选择不一致导致的。以下是一些具体的解决方案,可以帮助你解决这个问题: 检查步骤: 1. 检查启动文件 确保你的启动文件与你的芯片型号相匹配。例如,如果你的芯片是S…...
图像预处理-添加水印
一.ROI切割 类似裁剪图片,但是原理是基于Numpy数组的切片操作(ROI数组切片是会修改原图数据的),也就是说这个“裁剪”不是为了保存“裁剪”部分,而是为了方便修改等处理。 import cv2 as cv import numpy as npimg cv.imread(../images/dem…...
扩展欧几里得算法【Exgcd】的内容与题目应用
1.简介 exgcd的目的是表示出二元一次不定方程的通解。 形式化地,exgcd算法就是输入a,b,c的值,返回一组x,y,满足 a x b y c axbyc axbyc。 2.1方程无整数解的情况 当 c 不能被 a ,b最小公倍…...
OpenCV day5
函数内容接上文:OpenCV day4-CSDN博客 目录 9.cv2.adaptiveThreshold(): 10.cv2.split(): 11.cv2.merge(): 12.cv2.add(): 13.cv2.subtract(): 14.cv2.multiply(): 15.cv2.divide(): 1…...
Spring DI 详解
学习过 IoC 后,就知道我们可以将对象交给 Spring 进行管理,但是我们在一个类会有若干属性,也就是这个类依赖于这若干个属性,那么我们就可以将交给 Spring 管理的对象注入到这个类中,这也就是依赖注入。 依赖注入有三种…...
解锁动态规划的奥秘:从零到精通的创新思维解析(9)
前言: 小编在前几日写了关于动态规划中的多状态dp的问题,此时小编将会讲述一个动态规划我们常常会遇到的一类问题——股票问题,股票问题就类似小编上一篇所讲述的粉刷房子的问题,可以通过一个二维的dp表来代替多个一维的dp表。买卖…...