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

news/2024/4/26 1:15:33/文章来源:https://blog.csdn.net/Trouvailless/article/details/128199982

一、问题剖析

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

问你道简单的送分题:设计一个虚拟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 = tagNamethis.props = propsthis.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.propsfor (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 = nullvar currentNodeIndex = indexoldChildren.forEach(function (child, i) {var newChild = newChildren[i]currentNodeIndex = (leftNode && leftNode.count) // 计算节点的标识? currentNodeIndex + leftNode.count + 1: currentNodeIndex + 1dfsWalk(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.luyixian.cn/news_show_231335.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据存储,详细讲解

✨数据存储&#xff0c;详细讲解&#x1f49c;数据类型的介绍&#xff1a;&#x1f499;整形的内存存储大小端介绍&#xff1a;&#x1f49b;浮点数的存储&#x1f49c;数据类型的介绍&#xff1a; 1.内置类型&#xff1a; char //字符数据类型&#xff08;1&#xff…

POI在指定excel插入行java

我想在第三行&#xff0c;插入数据库的数据&#xff0c;这里假如数据库有10条&#xff0c;并且继承第二行的格式 数据库数据 {"clark",25}&#xff0c;我写个json对象&#xff0c;10条这个 造数据代码 JSONArray jsonArray new JSONArray();for (int i 0; i <…

Azkaban登录分析

分析意义:目前azkaban采用的是azkaban-users.xml配置文件的方式,配置登录用户。如果公司需要二次开发,增加安全性和便捷性,想从数据库取值呢,该如何着手开发呢?本文分析登录过程,便于进行azkaban的二次登录开发。 1、登录请求地址,请求方式和参数 请求地址:http://x…

计讯物联数字乡村解决方案全力助推三农信息化建设

​2020年&#xff0c;中央网信办等七部门联合印发《关于开展国家数字乡村试点工作的通知》。《通知》提出&#xff0c;做好数字乡村发展整体规划设计&#xff0c;统筹推进乡村的新型基础设施、数字经济、数字农业农村、农村科技创新、乡村数字治理、信息惠民服务等建设和发展。…

TF卡格式化了怎么办?tf卡数据恢复,看这3个方法

现在手机存储卡都很普及&#xff0c;TF卡是最常见的存储卡之一。但是你知道吗&#xff1f;TF卡也会有问题&#xff0c;比如出现误删数据&#xff0c;或者把数据格式化。因为手机内存有限&#xff0c;我们经常会把 TF卡设置为默认的最大空间&#xff0c;这样就可能会出现存储空间…

中国书画院院士、著名画家——戴友

戴友 戴友 中国书画院院士、著名画家 广州美术学院国画系毕业的专业画家 师从著名国画大家关山月、黎雄才、方楚雄、周波 艺术简介 戴友&#xff0c;著名画家、中国书画院院士。1960年生于广东&#xff0c;江苏省溧阳市人&#xff0c;汉族。自幼自学绘画&#xff0c;1991年…

DolphinDB 诚挚招募实施伙伴

随着 DolphinDB 业务发展&#xff0c;为满足迅速增长的客户需求&#xff0c;我们现正式启动“实施伙伴招募计划”。DolphinDB 客户已经涵盖7家Top 10券商、头部公募及私募基金、知名银行、交易所、世界500强制造业客户、标杆能源企业等&#xff0c;我们非常期待和欢迎实施伙伴们…

【C语言程序设计】实验 3

目录 1. 水仙花数 2. 五位回文数 3. 输入x&#xff0c;计算y 4. 百分制改为等级制 5. 同构数 6. 月份天数 7. 加一天后日期&#xff08;条件&#xff09; 8. 计算服装款&#xff08;条件&#xff09; 1. 水仙花数 【问题描述】输入一个3位正整数&#xff0c;判断该…

精彩数据:2021年我国民旅客周转量6530亿公里,审定受理飞机2803架

2021年是特殊的一年&#xff0c;全体民航成员在努力克服疫情防控、经营亏损、安全压力等困难交织叠加的影响下&#xff0c;切实的推动了民航的高质量发展&#xff0c;再各项工作上都取得了较好的成绩。下面是小编使用可视化互动平台对民航发展统计报告进行报表数据处理分析后得…

Word控件Spire.Doc 【图像形状】教程(12) 如何在C#中旋转word文档上的形状

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

C#学习记录——.NET Framework的组成及C#程序的执行过程

『好好学习&#xff0c;天天向上。』—— 毛主席语录 .NET Framework的组成 .NET Framework 是由微软公司推出的一种完全面向对象的软件开发平台&#xff0c;它主要由两个组件构成&#xff0c;分别为公共语言运行库&#xff08;CLR&#xff09;和.NET Framework类库&#xff…

深度学习-第P1周——实现mnist手写数字识别

深度学习-第P1周——实现mnist手写数字识深度学习-第P1周——实现mnist手写数字识一、前言二、我的环境三、前期工作1、导入依赖项并设置GPU2、导入数据集3、数据可视化四、构建简单的CNN网络五、训练模型1、设置超参数2、编写训练函数3、编写测试函数4、正式训练六、结果可视化…

机器学习与深度学习的基本概念

目录 机器学习是什么&#xff1f; 机器学习的任务 回归Regression 分类Classification 创造学习Structed Learing 机器学习怎么找这个函数 定义含未知参数的函数 定义loss损失函数 定义优化器optimization 写出一个更复杂的有未知参数的函数 sigmoid 基本推理过程 si…

一套Altair Feko复杂结构模型散射和天线辐射仿真建模攻略

导读&#xff1a;Feko软件广泛应用于电磁散射、电磁辐射仿真&#xff0c;例如&#xff1a;天线、天线布局、天线罩、屏蔽效能、电磁散射、频选结构、线束EMC等方面。问题种类繁多&#xff0c;但是无论仿真哪一类问题&#xff0c;其仿真流程是相同的&#xff0c;我们只需掌握了这…

使用 Echarts 插件完成中国旅游地图

目录前言&#xff1a;什么是 Echarts 插件具体实现思路中国旅游地图成品展示步骤&#xff1a;完成中国旅游地图代码总结&#xff1a;前言&#xff1a; 大家都知道&#xff0c;一般情况下&#xff0c;想要使用前端设置一个 中国旅游地图 需要使用 canvas 画布进行编写&#xff…

基于ARM架构openEuler系统通过qemu模拟器自动安装启动ARM架构的openEuler虚拟机

【原文链接】基于ARM架构openEuler系统通过qemu模拟器自动安装启动ARM架构的openEuler虚拟机 文章目录一、基础准备工作二、自动创建基于dhcp自动获取ip地址的openEuler虚拟机三、自动创建配置静态IP地址的openEuler虚拟一、基础准备工作 &#xff08;1&#xff09;下载ARM架构…

[附源码]Python计算机毕业设计Django校园订餐系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【Linux|树莓派】分文件编程以及静态库动态库

一、分文件编程 简单来说树莓派的分文件编程就是将一个项目的代码放在不同的文件里面&#xff0c;然后在主函数添加一个头文件&#xff0c;这样会使#控制字体颜色主程序变得简单。 在编译的时候要将主函数和功能函数一起编译&#xff1a; 注意&#xff1a;include <stdio.h…

Matplotlib入门[04]——处理图像

Matplotlib入门[04]——处理图像 参考&#xff1a; https://ailearning.apachecn.org/Matplotlib官网 图片来源&#xff1a;百度&#xff08;如有侵权&#xff0c;立删&#xff09; 使用Jupyter进行练习 import matplotlib.pyplot as plt import matplotlib.image as mpimg imp…

【Java学习Note】第8章 多线程

8. 多线程 文章目录8. 多线程8.1 程序、进程、线程8.2 线程的创建8.2.1 继承Thread类-创建线程方法之一8.2.2 Thread常用方法8.2.3 实现Runnable接口-创建线程方法之二8.2.4 两种多线程的区别8.3 线程得调度8.4 线程的生命周期8.5 线程的同步8.5.1 线程同步--Synchronized8.5.2…