三面:请设计一个虚拟DOM算法吧

news2024/11/23 19:07:58

一、问题剖析

这不是前几天面试官开局面试官就让我设计一个路由,二面过了,结果今天来个三面。

问你道简单的送分题:设计一个虚拟DOM算法?

好家伙,来吧,先进行问题剖析,谁让我们是卑微的打工人呢?

既来之,则安之。

Vue的虚拟DOM干嘛的,和diff有什么关系?

Are they friends?

我们在Vue项目实战中我们可能感受不到虚拟DOM的强大魅力,但它却是我们性能优化、响应速度背后重要的技术点,既然被问到了,那面试中我们怎么去回答比较好,我个人觉得可以从以下七个方面去回答这个面试题。

  • 先说一下虚拟DOM是什么?(是什么)
  • 为什么需要虚拟DOM?(为什么)
  • 使用了之后,有什么好处?
  • 如何实现该算法?(怎么实现)
  • 在后续的diff中的作用?
  • vue3中diff做了什么优化点?
  • 聊到虚拟DOM,可以再聊一下,key属性的作用。
老子说过:道生一,一生二,二生三,三生万物。
个人觉得回答一个问题,如果可以引申出问题不同角度或者与其相关的知识点,那面试官可能会觉得你这个人有广度,思维不局限在当前问题下,可能对你刮目相看。

二、虚拟DOM是什么?

虚拟dom就是虚拟的dom对象,通过JavaScript 对象来模拟dom对象。

简而言之:一个JS对象,里面存放着DOM对象的数据。

三、为什么需要虚拟DOM?

首先,我们都知道在前端性能优化的一个秘诀就是尽可能少地操作DOM,不仅仅是DOM相对较慢,更因为频繁变动DOM会造成浏览器的回流或者重回,这些都是性能的杀手,因此我们需要这一层抽象。

在patch过程中尽可能地一次性将差异更新到DOM中,这样保证了DOM不会出现性能很差的情况。

Vue的虚拟DOM是借鉴snabbdom.js库实现的。

关于回流或者重回的,后期出一篇文章讲讲。

四、使用了之后,有什么好处?

通过引入vdom我们可以获得如下好处:

  • 将真实元素节点抽象成 虚拟DOM,有效减少直接操作 dom 次数,从而提高程序性能方便实现跨平台。
  • 操作 dom 是很耗性能的操作,频繁的dom操作容易引起页面的重绘和回流,但是通过抽象 VNode 进行中间处理,可以有效减少直接操作dom的次数,从而减少页面重绘和回流。

五、如何实现该算法?

重头戏来了。

我们分为三个步骤来实现虚拟DOM算法。

  • 用JS对象模拟DOM树。
  • 比较两棵虚拟DOM树的差异。
  • 把差异应用到真正的DOM树上。

我们先来看看如果Vue没有用虚拟DOM之前的情况是怎样的?

此时状态发生了变化,就用模板引擎重新渲染整个视图,然后用新的视图更换掉旧的视图。

这样子做有什么不优雅的点呢?

最大的问题就是这样做会很慢,因为即使一个小小的状态变更都要重新构造整棵 DOM,性价比太低;

而且这样做的话,input和textarea的会失去原有的焦点。(因为你重新渲染了)

当然,这种做法对于局部小视图的更新,是没有问题的;但是对于大型视图,如全局应用状态变更的时候,需要更新页面较多局部视图的时候,这样的做法不可取。

Virtual DOM诞生

所以虚拟DOM(Virtual DOM)出来了。

核心是:维护状态,更新视图

好处也显而易见。

相对于 DOM 对象,原生的 JavaScript 对象处理起来更快,而且更简单。DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来:

新渲染的对象树去和旧的树进行对比,记录这两棵树差异。记录下来的不同就是我们需要对页面真正的 DOM 操作,然后把它们应用在真正的 DOM 树上,页面就变更了。这样就可以做到:视图的结构确实是整个全新渲染了,但是最后操作DOM的时候确实只变更有不同的地方。

Virtual DOM 的几个主要步骤

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异。
  3. 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。

可以这样理解:

Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。

可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:

既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。

CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。

算法实现

5.1 用JS对象模拟DOM树

用 JavaScript 来表示一个 DOM 节点是很简单的事情,你只需要记录它的节点类型、属性,还有子节点:

创建元素对象:

element.js

function Element (tagName, props, children) {
  this.tagName = tagName
  this.props = props
  this.children = children
}

module.exports = function (tagName, props, children) {
  return new Element(tagName, props, children)
}
复制代码

使用示范:

var el = require('./element')

var ul = el(
    'ul', 
    {id: 'list'}, 
    [ el('li', {class: 'item'}, ['Item 1']),
      el('li', {class: 'item'}, ['Item 2']),
      el('li', {class: 'item'}, ['Item 3'])
    ]
)
复制代码

现在ul只是一个 JavaScript 对象表示的 DOM 结构,页面上并没有这个结构。我们可以根据这个ul构建真正的<ul>:

为Element添加渲染函数render。

Element.prototype.render = function () {
  var el = document.createElement(this.tagName) // 根据tagName构建
  var props = this.props

  for (var propName in props) { // 设置节点的DOM属性
    var propValue = props[propName]
    el.setAttribute(propName, propValue)
  }

  var children = this.children || []

  children.forEach(function (child) {
    var childEl = (child instanceof Element)
      ? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
      : document.createTextNode(child) // 如果字符串,只构建文本节点
    el.appendChild(childEl)
  })

  return el
}
复制代码

render方法会根据tagName构建一个真正的DOM节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。所以只需要:

var ulRoot = ul.render()
document.body.appendChild(ulRoot)
复制代码

到这里,我们已经可以将真实的DOM,用JS对象模拟出来。

5.2 比较两棵虚拟DOM树的差异

两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,你很少会跨越层级地移动DOM元素。所以 Virtual DOM 只会对同一个层级的元素进行对比:

这是虚拟DOM的关键:只对同一层级元素做对比。

上面的div只会和同一层级的div对比,第二层级的只会跟第二层级对比。这样算法复杂度就可以达到 O(n)。

我们比较两棵虚拟DOM树的差异,分为两小步实现:

  • 记录差异性。
  • 有哪些类型的差异?

深度优先遍历,记录差异

我们会对新旧两棵树进行一个深度优先的遍历,这样每个节点都会有一个唯一的标记:

在深度优先遍历的时候,每遍历到一个节点就把该节点和新的的树进行对比。如果有差异的话就记录到一个对象patches里面。

// diff 函数,对比两棵树
function diff (oldTree, newTree) {
  var index = 0 // 当前节点的标志
  var patches = {} // 用来记录每个节点差异的对象
  dfsWalk(oldTree, newTree, index, patches)
  return patches
}

// 对两棵树进行深度优先遍历
function dfsWalk (oldNode, newNode, index, patches) {
  // 对比oldNode和newNode的不同,记录下来
  patches[index] = [...]

  diffChildren(oldNode.children, newNode.children, index, patches)
}

// 遍历子节点
function diffChildren (oldChildren, newChildren, index, patches) {
  var leftNode = null
  var currentNodeIndex = index
  oldChildren.forEach(function (child, i) {
    var newChild = newChildren[i]
    currentNodeIndex = (leftNode && leftNode.count) // 计算节点的标识
      ? currentNodeIndex + leftNode.count + 1
      : currentNodeIndex + 1
    dfsWalk(child, newChild, currentNodeIndex, patches) // 深度遍历子节点
    leftNode = child
  })
}
复制代码

例如,上面的div和新的div有差异,当前的标记是0,那么:

patches[0] = [{difference}, {difference}, ...] // 用数组存储新旧节点的不同
复制代码

同理p是patches[1],ul是patches[3],类推。

如图所示:

差异类型

接着看第二小步:有哪些差异类型?

上面说的节点的差异指的是什么呢?对 DOM 操作可能会:

  • 替换掉原来的节点,例如把上面的div换成了section
  • 移动、删除、新增子节点,例如上面div的子节点,把p和ul顺序互换
  • 修改了节点的属性
  • 对于文本节点,文本内容可能会改变。例如修改上面的文本节点2内容为帅气的呱哥。

所以我们定义了几种差异类型:

var REPLACE = 0
var REORDER = 1
var PROPS = 2
var TEXT = 3
复制代码

对于节点替换,很简单。判断新旧节点的tagName和是不是一样的,如果不一样的说明需要替换掉。如div换成section,就记录下:

var REPLACE = 0; // 这是我们上面定义的类型

patches[0] = [{
  type: REPALCE,
  node: newNode // el('section', props, children)
}]
复制代码

如果给div新增了属性id为container,就记录下:

var REPLACE = 0;var PROPS = 2

patches[0] = [{
  type: REPALCE,
  node: newNode // el('section', props, children)
}, {
  type: PROPS,
  props: {
    id: "container"
  }
}]
复制代码

如果是文本节点,如上面的文本节点2,就记录下:

var TEXT = 3

patches[2] = [{
  type: TEXT,
  content: "帅气的呱哥"
}]
复制代码

那如果把我div的子节点重新排序呢?

例如p, ul, div的顺序换成了div, p, ul。这个该怎么对比?

如果按照同层级进行顺序对比的话,它们都会被替换掉。如p和div的tagName不同,p会被div所替代。最终,三个节点都会被替换,这样DOM开销就非常大。而实际上是不需要替换节点,而只需要经过节点移动就可以达到,我们只需知道怎么进行移动。

以上的顺序变化:可采用乾坤大挪移。

通过动态规划求解,时间复杂度为 O(M * N)。但是我们并不需要真的达到最小的操作,我们只需要优化一些比较常见的移动情况,牺牲一定DOM操作,让算法时间复杂度达到线性的(O(max(M, N))。具体算法细节比较多

牺牲DOM,换取时间。
RED红发剧场版黄猿说过:不牺牲一些东西,哪来的和平。

5.3 把差异应用到真正的DOM树上

因为步骤一【5.1 用JS对象模拟DOM树】所构建的 JavaScript 对象树和render出来真正的DOM树的信息、结构是一样的。所以我们可以对那棵DOM树也进行深度优先的遍历,遍历的时候从步骤二生成的patches对象中找出当前遍历的节点差异,然后进行 DOM 操作。

那么vdom又如何成为dom呢?

我们经过【5.1用JS对象模拟DOM树】得知,vdom经过element函数变成vdom,用JS对象模拟DOM树。

他是在什么时候触发的?

‍♂️实际上,在vue中我们常常会为组件编写模板 - template, 这个模板会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。

但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。

小总结

Virtual DOM 算法主要是实现上面步骤的三个函数:element,diff,patch。然后就可以实际的进行使用:

// 1. 构建虚拟DOM
var tree = el('div', {'id': 'container'}, [
    el('h1', {style: 'color: blue'}, ['simple virtal dom']),
    el('p', ['Hello, virtual-dom']),
    el('ul', [el('li')])
])

// 2. 通过虚拟DOM构建真正的DOM
var root = tree.render()
document.body.appendChild(root)

// 3. 生成新的虚拟DOM
var newTree = el('div', {'id': 'container'}, [
    el('h1', {style: 'color: red'}, ['simple virtal dom']),
    el('p', ['Hello, virtual-dom']),
    el('ul', [el('li'), el('li')])
])

// 4. 比较两棵虚拟DOM树的不同
var patches = diff(tree, newTree)

// 5. 在真正的DOM元素上应用变更
patch(root, patches)
复制代码

当然实际中还需要处理事件监听等;生成虚拟 DOM 的时候也可以加入 JSX 语法。

看到这里的靓仔,你很优秀了。
快End了,再坚持坚持。

六、在后续的diff中的作用?

关于diff算法,我个人觉得可以从以下四个方面去回答。

  • 虚拟DOM和diff是什么关系?
  • 为什么要用diff?
  • 那它啥时候执行?
  • 怎么执行?

虚拟DOM和diff是什么关系?

实际上,在vue中我们常常会为组件编写模板 - template, 这个模板会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。

挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。

简而言之:Diff算法就是一种对比算法,主要是对比旧的虚拟DOM和新的虚拟DOM,找出发生更改的节点,并只更新这些更改点,而不更新未发生变化的节点,从而准确的更新DOM,减少操作真实DOM的次数,提高性能。

我们在【5.3 把差异应用到真正的DOM树上】提到的,虚拟DOM在后续的patch过程中进一步转化为dom。主要是来提升性能。

这里的patch其实也叫Diff算法。

它的必要性

那我们已经知道虚拟DOM转成真正的DOM,需要Diff算法在转换过程中,提升性能。

其实在Vue1.x视图中每个依赖均有更新函数对应,可以做到精准更新,因此并不需要虚拟DOM和patching算法支持,但是这样粒度过细导致Vue1.x无法承载较大应用;Vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,此时就需要引入patching算法才能精确找到发生变化的地方并高效更新。

简而言之:vue1之前是有变化,就全部变化;
vue2引入diff可以做到有变化,只需要找到那个变化的watcher去更新即可。

diff啥时候执行?

vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数获得最新的虚拟DOM,然后执行patch函数,并传入新旧两次虚拟DOM,通过比对两者找到变化的地方,最后将其转化为对应的DOM操作。

怎么执行?

这个是核心。圈起来,要考的。

其实我们在【5.2 比较两棵虚拟DOM树的差异】中有大概说过。

现在我们重点讲解一下。

滚动条警告⚠️⚠️⚠️

patch过程是一个递归过程,遵循深度优先、同层比较的策略;

  • 首先判断两个节点是否为相同同类节点,不同则删除重新创建
  • 如果双方都是文本则更新文本内容
  • 如果双方都是元素节点则递归更新子元素,同时更新元素属性 更新子节点时又分了几种情况:(diff算法核心:新节点孩子 VS 旧节点孩子) 新的子节点是文本,老的子节点是数组则清空,并设置文本; 新的子节点是文本,老的子节点是文本则直接更新文本; 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素; 新的子节点是数组,老的子节点也是数组,那么比较两组子节点
只能 同级比较,不能跨层比较。

diff算法在比较过程中,是通过指针进行比较的。

vue2新老节点替换的规则:

  • 旧前 和 新前;匹配:旧前的指针++、新前的指针++
  • 旧后 和 新后;匹配:旧后的指针--、新后的指针--
  • 旧前 和 新后;匹配:旧前的指针++、新后的指针--
  • 旧后 和 新前;匹配:旧后的指针--、新前的指针++
  • 以上都不满足条件 ===》 查找;匹配:新的指针++,新的添加到页面上并且新在旧的中有,要给旧的赋值为undefined
  • 创建或者删除

可能突然对这个规则会有点难以理解,没关系,结合图理解一下:

其实很好理解的,建议把图拖到另外一个窗口,再结合以上规则试着理解一遍。

不要慌,跟着我的分析一步一步往下走理解。

分析过程:

这是diff比较复杂的过程:

  • 旧前a、新前b
  • 不匹配,下一个规则,旧后d,新后m
  • 不匹配,下一个规则,旧前a,新后m
  • 不匹配,下一个规则,旧后d,新前b
  • 不匹配,下一个规则(第五种)
  • 把新前b,创建到页面中,同时去找旧里面这个b的节点
  • 找到了设置为undefined,即旧的数组是【a, undefined, c, d】
  • 这里为什么设置为undefined,请看上面的规则,OK,Go on~
  • 新的指针++,变为1(到这里,我们终于把第一个给走完
  • 接着上面的四个步骤,还是不匹配,又走第五种
  • 把新前c,创建到页面中,同时去找旧里面这个c的节点
  • 找到了设置为undefined,即【a, undefined, undefined, d】
  • 新的指针++,变为2
  • 接着旧前a,新前也是a
  • 匹配,旧指针++为1,新指针++为3
  • OK,继续走,旧前指针是1,到了b这个位置,已经设置为undefined
  • undefined在新的肯定找不到,判断如果为undefined,旧的指针++
  • 新的指针不动,即旧指针变为2,新的还是3
  • 来到c的位置,也是undefined,旧指针,变为3
  • 旧前d,新前d,匹配
  • 最后发现新的只剩下m了
  • 创建,也就是第六种情况
理解后,就能明白diff的双端算法,头头比较、尾尾比较的意思了。

为什么说一定要加key?

因为如果不加的话,整个DOM不能复用,需要删除重新建立。

所以,如果要提升性能,一定要加上key,key是唯一标识,在更改前后,确认是不是同一个节点。

七、vue3中diff做了什么优化点?

这里是对vue2的diff的一个优化。

我们知道,diff算法大概有这四个过程:

  • 同级比较,再比较子节点
  • 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
  • 比较都有子节点的情况(核心diff)
  • 递归比较子节点

正常Diff两个树的时间复杂度是O(n^3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。

Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

那vue3的做了哪些优化?

Vue3.x借鉴了 ivi算法和 inferno算法

在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)

该算法中还运用了动态规划的思想求解最长递归子序列。

vue3中引入的更新策略:编译期优化patchFlags、block等

聊到diff算法,曾经面试被问到vue和react的区别?

React的diff和Vue的diff算法的不同之处?

vue和react的diff算法都是进行同层次的比较,主要有以下两点不同:

  • vue对比节点,如果节点元素类型相同,但是className不同,认为是不同类型的元素,会进行删除重建,但是react则会认为是同类型的节点,只会修改节点属性。
  • vue的列表比对采用的是首尾指针法,而react采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到最后一个,从这点上来说vue的对比方式更加高效。
最怕面试官突如其来的灵魂拷问。
更怕空气突然安静~

八、说一下虚拟Dom以及key属性的作用?

由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。

VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。

那key的作用是啥?

‍♂️ 「key的作用是尽可能的复用 DOM 元素。」

新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。

需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。


面试官摸了摸胡子,顺便摸了摸我那八块腹肌,明天来报道。

哦,再说吧,我回家做饭了。

后记

如何设计一个虚拟DOM算法?

看似问得很浅,其实要回答的知识点很多,很深。

我们再来总结一下回答:

‍♂️我个人主要从五个方面回答

  1. 虚拟dom就是虚拟的dom对象,通过JavaScript 对象来模拟dom对象。(是什么)
  2. 因为在前端性能优化的一个秘诀就是尽可能少地操作DOM,不仅仅是DOM相对较慢,更因为频繁变动DOM会造成浏览器的回流或者重回,我们利用Diff算法尽可能地一次性将差异更新到DOM中,这样保证了DOM不会出现性能很差的情况。(为什么用)
  3. 给我设计的话,我会从三方面去考虑: 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异。 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。此阶段会利用到Diff算法。因为挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。
  4. 我们刚刚说了,期间转换为真实的DOM会利用到Diff算法。
  • Diff算法就是一种对比算法,主要是对比旧的虚拟DOM和新的虚拟DOM,找出发生更改的节点,并只更新这些更改点,而不更新未发生变化的节点,从而准确的更新DOM,减少操作真实DOM的次数,提高性能。
  • Diff过程是一个递归过程,遵循深度优先、同层比较的策略
  • 首先判断两个节点是否为相同同类节点,不同则删除重新创建
  • 如果双方都是文本则更新文本内容
  • 如果双方都是元素节点则递归更新子元素,同时更新元素属性 更新子节点时又分了几种情况:(diff算法核心:新节点孩子 VS 旧节点孩子) 新的子节点是文本,老的子节点是数组则清空,并设置文本; 新的子节点是文本,老的子节点是文本则直接更新文本; 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素; 新的子节点是数组,老的子节点也是数组,那么比较两组子节点。
  1. 在vue3中diff也有做了一些优化点。 Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。 Vue3.x借鉴了 ivi算法和 inferno算法 在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。
层层递进。由浅入深。

如果仔细看下来,其实发现虚拟DOM也不是很难理解,一些羞涩难懂的技术点,只要花点时间去理解,相信很快能攻破,也能明白在实际开发过程中,模板渲染成虚拟DOM后,通过diff算法去提升性能,背后的知识点懂了之后,开发过程中也会如翼添虎。

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

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

相关文章

学习python基础知识

1、Python 基础语法 计算机组成&#xff1a;硬件、软件、计算机运行程序方式、Python 语言的特点、应用领域、Python IDE、程序注释&#xff1a;单行注释、多行注释&#xff1b;变量的作用、定义、 命名规则、变量的数据类型、查看变量类型、输入和输入函数、算术运算符、赋值…

gazebo中添加动态障碍物

文章目录gazebo 教程gazebo 添加动态障碍物gazebo添加动态障碍物插件gazebo中动态障碍物实时posegazebo 教程 gazebo github https://github.com/gazebosim/gazebo-classic/tree/gazebo9gazebo tutorials https://classic.gazebosim.org/tutorials运行一个空白环境 <sdf v…

深入了解Java中的SQL注入

深入了解Java中的SQL注入 本文以代码实例复现了Java中JDBC及Mybatis框架采用预编译和非预编译时可能存在SQL注入的几种情况&#xff0c;并给予修复建议。 JDBC 首先看第一段代码&#xff0c;使用了远古时期的JDBC并且并没有使用预编译。这种简单的字符串拼接就存在SQL注入 …

一云七芯!ZStack 祝贺上海市金融信创联合攻关基地荣获一等奖

2022年11月初&#xff0c;由上海市总工会、中共上海市经济和信息化工作委员会、上海市经济信息化委员会主办的2022上海城市数字化转型 “智慧工匠”选树、“领军先锋”评选活动信创应用竞赛决赛暨颁奖典礼中&#xff0c;“一云七芯适配验证云平台及服务解决方案”获得信创应用案…

GitHub2022年度前100的Java面试真题高频知识点汇总

前言 这是我在工作、面试中学习并总结到的一些知识点&#xff0c;都是一些比较典型的、面试常常被问到的问题。 如果你平时没有注意去总结的话&#xff0c;那么当你面试被问到的时候可能会是一脸懵圈&#xff0c;就算这个问题你知道怎么回事&#xff0c;但是你平时没有认真总…

【win11内存占用高优化】未运行程序,系统内存占用50以上

这里写自定义目录标题前言打开控制面板找到电源键功能找到快速启动选项&#xff0c;取消勾选&#xff0c;确定win X以管理员身份打开powershell输入如下命令&#xff0c;回车关闭终端完成前言 windows11在未运行任何其他程序的情况下&#xff0c;内存占用超50%&#xff0c;可…

速度收藏,Fiddler详细使用教程出炉!

目录 01、抓取不同类型接口数据 02、数据模拟以及过滤规则 03、如何模拟接口响应数据 04、使用fiddler进行评论接口测试 绵薄之力【软件测试学习资源分享】 01、抓取不同类型接口数据 查看windows本机的IP 配置fiddler 需要保证要抓取的手机与电脑保持同一网段&#xff0…

转换 FLAC、APE 无损音乐格式为 iTunes 支持导入的 M4A 格式

大家知道常见的无损音乐格式有 FLAC、APE、WAV 等这些格式。其中 FLAC (Free Lossless Audio Codec) 格式因为是免费自由的压缩编码、无损压缩&#xff0c;且受到操作系统、软件及硬件的广泛支持。所以是非常流行常见的无损音乐格式。 自 2005 年 Mac OS X v10.4 开始&#xf…

《垃圾回收算法手册 自动内存管理的艺术》——其他分区策略(笔记)

文章目录十、其他分区策略10.1 大对象空间10.1.1 转轮回收器10.1.2 在操作系统支持下的对象移动10.1.3 不包含指针的对象10.2 基于对象拓扑结构的回收器10.2.1 成熟对象空间的回收10.2.2 基于对象相关性的回收10.2.3 线程本地回收10.2.4 栈上分配10.2.5 区域推断10.3 混合标记—…

磨金石教育摄影技能干货分享|传统民居摄影作品欣赏

我们知道在绘画领域有写实和写意之分&#xff0c;写实多用于人像的描绘&#xff0c;写意多用于山水田园画的创作。尤其是在中国传统绘画艺术中&#xff0c;写意简直就是创作的精髓。 写实和写意的区别在于&#xff0c;前者侧重真实还原&#xff0c;后者在于主管情感表达。 建筑…

哈希表、哈希桶(C++实现)

1. 哈希 1.1 概念 哈希&#xff08;hash&#xff0c;中文&#xff1a;散列&#xff1b;音译&#xff1a;哈希&#xff09;&#xff0c;是一种算法思想&#xff0c;又称散列算法、哈希函数、散列函数等。哈希函数能指导任何一种数据&#xff0c;构造出一种储存结构&#xff0c…

机器学习笔记之配分函数(二)——随机最大似然

机器学习笔记之配分函数——随机最大似然引言回顾&#xff1a;对数似然梯度关于∇θL(θ)\nabla_{\theta}\mathcal L(\theta)∇θ​L(θ)的简化基于MCMC求解负相关于书中图像的解释引言 上一节介绍了对包含配分函数的概率分布——使用极大似然估计求解模型参数的梯度(对数似然…

5款高效率,但是名气不大的小众软件

今天推荐5款十分小众的软件&#xff0c;但是每个都是非常非常好用的&#xff0c;用完后觉得不好用你找我。 1.多窗口文件整理——Q-Dir Q-Dir 是一款多窗口文件整理工具&#xff0c;特别适合用户频繁在各个文件夹中跳转进行复制粘贴的文件归档操作。如果你的电脑硬盘中文件已经…

MySQL 数据库的增删查改 (2)

文章目录一. 数据库约束1. 约束类型2.NULL 约束3.UNIQUE 约束4.DEFAULT 约束5. PRIMARY KEY 约束6.FOREIGN KEY 约束二.表的设计三.插入四.查询1.聚合查询2.联合查询3.合并查询本篇文章继承与 MySQL 表的增删改查(1) 一. 数据库约束 1. 约束类型 NOT NULL -- 表示某一行不能…

下载安全证书到jdk中的cacerts证书库

最近在公司遇到访问https请求&#xff0c;JDK返回异常信息的问题。返回如下&#xff1a; java.lang.Exception: java.lang.Exception: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: u…

废弃阶段的风险评估

概述 风险评估应贯穿于评估对象生命周期 各阶段中。评估对象生命周期各阶段中涉及的风险评估原则和方法昆一致的&#xff0c;但由干各阶段实施内容对象、安全需求不同.使得风险评估的对象、目的、要求等各方面也有所不同。在规划设计阶段&#xff0c;通过风险评估以确定评估对…

线程安全介绍

线程安全 多线程程序处于一个多变的环境当中&#xff0c;可访问的全局变量和堆数据随时都可能被其他的线程改变。因此多线程程序在并发时数据的一致性变得非常重要。 竞争和原子操作 多个线程同时访问一个共享数据&#xff0c;可能造成很恶劣的后果。下面是一个著名的例子&a…

多数之和问题

文章目录多数求和问题1两数之和(无序)题解2两数之和(有序)题解3两数之和(二叉搜索树)题解4 三数之和题解5四数之和题解多数求和问题 针对给一组用例,和一个目标数target,求用例中多数相加等于target的所有数,且不能重复问题,一般有两种解法: 集合(不要求排序)双指针(要求排序…

万德L2接口代码执行工作的过程分享

在设计万德L2接口时&#xff0c;避免不了要用到 一些代码&#xff0c;今天小编来给各位分享一下万德L2接口代码执行工作的过程分享&#xff1a; 这里只分享部分功能执行的过程&#xff1a; OrderQueueRecord&#xff08;委托队列&#xff09; 字段名 类型 备注 stock_ex…

word文档

WORD行与行中间空出一行&#xff0c;怎么办&#xff1f; 这个情况又分两种情况&#xff1a; 第①种情况&#xff1a;行与行之间的空白行都多了一个回车符&#xff1a; Word中&#xff0c;当我们从网络上复制一些文本或者是拿到一些别人的文本&#xff0c;这种文本经常会有大…