Vue3源码解读之patch

news2025/8/13 12:16:50

例子代码

本篇将要讲解dom diff,那么咱们结合下面的例子来进行讲解,这个例子是在上一篇文章的基础上,加了一个数据变更,也就是list的值发生了改变。html中增加了一个按钮change,通过点击change按钮来调用change函数,来改变list的值。例子位于源代码/packages/vue/examples/classic/目录下,下面是例子的代码:

const app = Vue.createApp({
    data() {
        return {
            list: ['a', 'b', 'c', 'd']
        }
    },
    methods: {
        change() {
            this.list = ['a', 'd', 'e', 'b']
        }
    }
});
app.mount('#demo')
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport"
          content="initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no,target-densitydpi=medium-dpi,viewport-fit=cover"
    />
    <title>Vue3.js hello example</title>

    <script src="../../dist/vue.global.js"></script>
</head>
<body>
<div id="demo">
    <ul>
        <li v-for="item in list" :key="item">
            {{item}}
        </li>
    </ul>
    <button @click="change">change</button>
</div>
<script src="./hello.js"></script>
</body>
</html>

源码解读

关于Vue3中数据发生变更,最终影响到页面发生变化的过程,我们本篇文章只对componentEffect以及以后的代码进行讲解,对于数据变更后,是如何执行到componentEffect函数,以及为何会执行componentEffect,后面的文章再进行讲解。

componentEffect

来看下componentEffect更新部分的代码:

  // @file packages/runtime-core/src/renderer.ts
  function componentEffect() {
    if (!instance.isMounted) {
        // first render
    } else {
        let {next, bu, u, parent, vnode} = instance
        let originNext = next
        let vnodeHook: VNodeHook | null | undefined

        if (next) {
            updateComponentPreRender(instance, next, optimized)
        } else {
            next = vnode
        }
        next.el = vnode.el

        // beforeUpdate hook
        if (bu) {
            invokeArrayFns(bu)
        }
        // onVnodeBeforeUpdate
        if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
            invokeVNodeHook(vnodeHook, parent, next, vnode)
        }
        const nextTree = renderComponentRoot(instance)
        const prevTree = instance.subTree
        instance.subTree = nextTree

        if (instance.refs !== EMPTY_OBJ) {
            instance.refs = {}
        }
        patch(
            prevTree,
            nextTree,
            hostParentNode(prevTree.el!)!,
            getNextHostNode(prevTree),
            instance,
            parentSuspense,
            isSVG
        )
        next.el = nextTree.el
        if (originNext === null) {
            updateHOCHostEl(instance, nextTree.el)
        }
        // updated hook
        if (u) {
            queuePostRenderEffect(u, parentSuspense)
        }
        // onVnodeUpdated
        if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
            queuePostRenderEffect(() => {
                invokeVNodeHook(vnodeHook!, parent, next!, vnode)
            }, parentSuspense)
        }
    }
  }

参考Vue3源码视频讲解:进入学习

当数据发生变化的时候,最终会走到上面的else的逻辑部分。

  • 默认情况下next是null,父组件调用processComponent触发当前调用的时候会是VNode,此时next为null;
  • 调用当前实例beforeUpdate钩子函数;调用要更新的Vnode(next)的父组件的beforeUpdate钩子函数;
  • 获取当前实例的vNode => prevTree;获取要更新的vNode=> nextTree;然后调用patch;

调用patch函数的过程,也就是根据VNode的type,走不同的支流的过程;点击change按钮:n1的值: n2的值: 根据这个值,可以知晓,会走到processFragment函数;

processFragment

调用processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized)函数,参数的值:

  • 此时n1和n2如上图;
  • container为#demo;
  • anchor为null;
  • parentComponent为instance实例;
  • parentSuspense为null;
  • isSVG为false;
  • optimized为false;

来看下processFragment函数的源码:

// @file packages/runtime-core/src/renderer.ts
const processFragment = (
    n1: VNode | null,    n2: VNode,    container: RendererElement,    anchor: RendererNode | null,    parentComponent: ComponentInternalInstance | null,    parentSuspense: SuspenseBoundary | null,    isSVG: boolean,    optimized: boolean
) => {
    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!
    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!

    let {patchFlag, dynamicChildren} = n2
    if (patchFlag > 0) {
        optimized = true
    }

    if (n1 == null) {
        // first render的逻辑
    } else {
        if (
            patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT && dynamicChildren
        ) {
            patchBlockChildren(
                n1.dynamicChildren!,
                dynamicChildren,
                container,
                parentComponent,
                parentSuspense,
                isSVG
            )
            if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
                traverseStaticChildren(n1, n2)
            } else if (
                n2.key != null ||
                (parentComponent && n2 === parentComponent.subTree)
            ) {
                traverseStaticChildren(n1, n2, true /* shallow */)
            }
        } else {
            patchChildren(
                n1,
                n2,
                container,
                fragmentEndAnchor,
                parentComponent,
                parentSuspense,
                isSVG,
                optimized
            )
        }
    }
}

刨除掉first render的代码后,可以看到下面还是分为了两个分支;根据n1和n2可知,我们将会走if分支,执行patchBlockChildren。

patchBlockChildren

调用patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, container, parentComponent, parentSuspense, isSVG)函数,此时参数如下:

  • oldChildren:n1.dynamicChildren,也就是Symbol(Fragment) =>ul 和button两个元素组成的数组;
  • newChildren: n2.dynamicChildren,也就是Symbol(Fragment) =>ul 和button两个元素组成的数组;
  • fallbackContainer:container,也就是#demo;
  • parentComponent:instance实例;
  • parentSuspense:null;
  • isSVG:false。

来看下patchBlockChildren的源码:

// @file packages/runtime-core/src/renderer.ts
const patchBlockChildren: PatchBlockChildrenFn = (
    oldChildren,    newChildren,    fallbackContainer,    parentComponent,    parentSuspense,    isSVG
) => {
    for (let i = 0; i < newChildren.length; i++) {
        const oldVNode = oldChildren[i]
        const newVNode = newChildren[i]
        const container =
            oldVNode.type === Fragment ||
            !isSameVNodeType(oldVNode, newVNode) ||
            oldVNode.shapeFlag & ShapeFlags.COMPONENT ||
            oldVNode.shapeFlag & ShapeFlags.TELEPORT
                ? hostParentNode(oldVNode.el!)!
                : fallbackContainer
        patch(
            oldVNode,
            newVNode,
            container,
            null,
            parentComponent,
            parentSuspense,
            isSVG,
            true
        )
    }
}

可以看到patchBlockChildren是for循环调用patch函数,上面看到newChildren是一个长度为2的数组。循环遍历调用patch(oldVNode, newVNode, container, null, parentComponent, parentSuspense, isSVG, true);

  • 第一次循环:
    • oldVNode:老的ul数组生成的VNode对象;
    • newVNode:新的ul数组生成的VNode对象;
    • container:ul元素;
    • anchor:上面传递的是null;
    • parentComponent: instance实例;
    • parentSuspense: null;
    • isSVG: false;
    • optimized: true;
  • 第二次循环:
    • oldVNode: 老的change按钮构成的VNode对象;
    • newVNode:新的change按钮构成的VNode对象;
    • container:此时的container为#demo;
    • anchor:上面传递的是null;
    • parentComponent: instance实例;
    • parentSuspense: null;
    • isSVG: false;
    • optimized: true;

processElement

咱们先说第二次循环,第二次比较简单;上面说到调用patch函数,通过上面了解到第二次循环newVNode的type是button;则会走到processElement,参数全部是透传过来的:

const processElement = (
        n1: VNode | null,        n2: VNode,        container: RendererElement,        anchor: RendererNode | null,        parentComponent: ComponentInternalInstance | null,        parentSuspense: SuspenseBoundary | null,        isSVG: boolean,        optimized: boolean
    ) => {
        isSVG = isSVG || (n2.type as string) === 'svg'
        if (n1 == null) {
            // first render
        } else {
            patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
        }
    }

如上代码,会直接调用patchElement,此时参数为:

  • n1: 老的change按钮构成的VNode对象;
  • n2:新的change按钮构成的VNode对象;
  • parentComponent: instance实例;
  • parentSuspense: null;
  • isSVG: false;
  • optimized: true;

patchChildren

现在再来说第一次循环,执行patch的时候,newVNode的type为Symbol(Fragment) => ul,此时还是会走到processFragment函数,不过此时的dynamicChildren为空,会继续运行到patchChildren函数。

patchChildren

此时运行到patchChildren函数,我们来看下运行到此时的参数:

  • n1:老的ul数组生成的VNode对象;
  • n2:新的ul数组生成的VNode对象;
  • container:ul元素;
  • anchor:ul结尾生成的对象;
  • parentComponent:instance实例;
  • parentSuspense:null
  • isSVG:false;
  • optimized:true;

下面看下patchChildren的源码:

const patchChildren: PatchChildrenFn = (
    n1,    n2,    container,    anchor,    parentComponent,    parentSuspense,    isSVG,    optimized = false
) => {
    const c1 = n1 && n1.children
    const prevShapeFlag = n1 ? n1.shapeFlag : 0
    const c2 = n2.children

    const {patchFlag, shapeFlag} = n2
    if (patchFlag > 0) {
        if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
            patchKeyedChildren(
                c1 as VNode[],
                c2 as VNodeArrayChildren,
                container,
                anchor,
                parentComponent,
                parentSuspense,
                isSVG,
                optimized
            )
            return
        } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
            // patchUnkeyedChildren
            return
        }
    }

    // other ......
}

此时patchFlag的值为128,同时我们的list渲染是有key的,so 会运行patchKeyedChildren函数,c1为四个li组成的数组(a,b,c,d);c2为新的li组成的数组(a,d,e,b);其他值透传到patchKeyedChildren。

patchKeyedChildren

上面对patchKeyedChildren函数的参数已经进行了说明,在这里我们再回顾下:

  • c1:四个li组成的数组(a,b,c,d);
  • c2:新的li组成的数组(a,d,e,b);
  • container:ul元素;
  • parentAnchor:ul结尾生成的对象;
  • parentComponent:instance实例;
  • parentSuspense:null
  • isSVG:false;
  • optimized:true;

接下来看下patchKeyedChildren函数的源码:

const patchKeyedChildren = (
    c1: VNode[],    c2: VNodeArrayChildren,    container: RendererElement,    parentAnchor: RendererNode | null,    parentComponent: ComponentInternalInstance | null,    parentSuspense: SuspenseBoundary | null,    isSVG: boolean,    optimized: boolean
) => {
    let i = 0
    const l2 = c2.length
    let e1 = c1.length - 1 
    let e2 = l2 - 1 

    while (i <= e1 && i <= e2) {
        const n1 = c1[i]
        const n2 = (c2[i] = optimized ? cloneIfMounted(c2[i] as VNode) : normalizeVNode(c2[i]))
        if (isSameVNodeType(n1, n2)) {
               patch(n1,n2,container,null,parentComponent,parentSuspense,isSVG,optimized)
        } else {
            break
        }
        i++
    }

    while (i <= e1 && i <= e2) {
        const n1 = c1[e1]
        const n2 = (c2[e2] = optimized ? cloneIfMounted(c2[e2] as VNode) : normalizeVNode(c2[e2]))
        if (isSameVNodeType(n1, n2)) {
            patch(n1,n2,container,null,parentComponent,parentSuspense,isSVG,optimized)
        } else {
            break
        }
        e1--
        e2--
    }

    if (i > e1) {
        if (i <= e2) {
            const nextPos = e2 + 1
            const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
            while (i <= e2) {
                patch(
                    null,
                    (c2[i] = optimized
                        ? cloneIfMounted(c2[i] as VNode)
                        : normalizeVNode(c2[i])),
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG
                )
                i++
            }
        }
    }

    else if (i > e2) {
        while (i <= e1) {
            unmount(c1[i], parentComponent, parentSuspense, true)
            i++
        }
    }

    else {
        const s1 = i
        const s2 = i 
        for (i = s2; i <= e2; i++) {
            const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as VNode) : normalizeVNode(c2[i]))
            if (nextChild.key != null) {
                keyToNewIndexMap.set(nextChild.key, i)
            }
        }

        let j
        let patched = 0
        const toBePatched = e2 - s2 + 1
        let moved = false
        let maxNewIndexSoFar = 0
        const newIndexToOldIndexMap = new Array(toBePatched)
        for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0

        for (i = s1; i <= e1; i++) {
            const prevChild = c1[i]
            if (patched >= toBePatched) {
                unmount(prevChild, parentComponent, parentSuspense, true)
                continue
            }
            let newIndex
            if (prevChild.key != null) {
                newIndex = keyToNewIndexMap.get(prevChild.key)
            } else {
                for (j = s2; j <= e2; j++) {
                    if (
                        newIndexToOldIndexMap[j - s2] === 0 &&
                        isSameVNodeType(prevChild, c2[j] as VNode)
                    ) {
                        newIndex = j
                        break
                    }
                }
            }
            if (newIndex === undefined) {
                unmount(prevChild, parentComponent, parentSuspense, true)
            } else {
                newIndexToOldIndexMap[newIndex - s2] = i + 1
                if (newIndex >= maxNewIndexSoFar) {
                    maxNewIndexSoFar = newIndex
                } else {
                    moved = true
                }
                patch(
                    prevChild,
                    c2[newIndex] as VNode,
                    container,
                    null,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    optimized
                )
                patched++
            }
        }

        const increasingNewIndexSequence = moved
            ? getSequence(newIndexToOldIndexMap)
            : EMPTY_ARR
        j = increasingNewIndexSequence.length - 1
        for (i = toBePatched - 1; i >= 0; i--) {
            const nextIndex = s2 + i
            const nextChild = c2[nextIndex] as VNode
            const anchor =
                nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
            if (newIndexToOldIndexMap[i] === 0) {
                patch(
                    null,
                    nextChild,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG
                )
            } else if (moved) {
                if (j < 0 || i !== increasingNewIndexSequence[j]) {
                    move(nextChild, container, anchor, MoveType.REORDER)
                } else {
                    j--
                }
            }
        }
    }
}

上面代码包含的有两个while循环和两对if-else;

  • i=0,循环开始下标;e1、e2为c1和c2的长度;l2为新的children的长度;
  • 第一个while循环,从头开始对列表进行遍历:
    • 当nodeType一样的时候,调用patch;
    • 当nodeType不一样的时候,跳出循环;
  • 第二个while循环,当第一个while循环对c1和c2都没有遍历完的时候,从尾部开始对其进行遍历:
    • 当nodeType一样的时候,调用patch;
    • 当nodeType不一样的时候,跳出循环;
  • 第一个if,i>e1证明c1已经遍历完,i<=e2证明c2还没遍历完,对剩余的c2继续遍历,调用patch;
  • 第二个else-if,i>e2证明c2已经遍历完,i<=e1证明c1还没遍历完,对剩余的c1继续遍历,因为c1为老的列表,则调用unmount把无用的列表内容卸载掉:
  • 第二个else:c1和c2至少有一个没有遍历完,走到最后一个else的逻辑:
    • for (i = s2; i <= e2; i++)for循环遍历剩余c2,收集每个c2的元素的key,构成map => keyToNewIndexMap;
    • for (i = 0; i < toBePatched; i++)for循环遍历剩余c2部分长度来生成映射,并赋值为0;
    • for (i = s1; i <= e1; i++) for循环遍历剩余c1,使用key进行直接获取(for循环剩余c2进行获取)newIndex,此处证明还是要绑定好key,唯一性很重要;newIndex有值说明c2中存在当前老的元素在c1中,老的preChild,在c2中还需要,则调用patch;如果newIndex为undefined,则说明老的preChild在c2中不需要了,调用unmount,把当前preChild卸载掉;
    • 遍历完剩余c1后,再倒着遍历剩余c2:for (i = toBePatched - 1; i >= 0; i--);如果(newIndexToOldIndexMap[i] === 0则证明当前nextChild为新的节点,调用patch;否则判断之前是否发生了移动moved,经过逻辑判断,调用move;

patchKeyedChildren 例子

根据咱们上面的例子,由old: [‘a’, ‘b’, ‘c’, ‘d’]变更为new: [‘a’, ‘d’, ‘e’, ‘b’]的过程如下:

  • 首先进入第一个while循环,此时i为0,l2为4,e1为3,e2为3;
    • 第一次循环,old-a与new-a是相同的,调用patch,不发生变化;
    • 第二次循环,old-b与new-b是不相同的,break;
    • 跳出循环,从头开始的循环结束;
  • 进入第二个while循环,此时i为1,l2为4,e1为3,e2为3;
    • 第一次循环,old-d与new-b是不相同的,break;
    • 跳出循环,从尾部开始的循环结束;
  • 进入第一个if判断为false,进入第二个else-if判断为false,进入else;
  • for循环收集每个c2的元素的key,keyToNewIndexMap = [‘d’ => 1, ‘e’ => 2, ‘b’ => 3];
  • 建立长度为剩余c2长度的数组newIndexToOldIndexMap = [0, 0 ,0];
  • 此时进入for (i = s1; i <= e1; i++) for循环遍历剩余c1阶段,此时i为1,s1为1,s2为1:
    • 第一次循环:遍历的元素为old-b,发现在new中存在,通过keyToNewIndexMap获得在new中的index为3;调用patch;
    • 第二次循环:遍历的元素为old-c,在new中不存在,调用unmount卸载当前old-c,改变后c1为[‘a’, ‘b’, ‘d’]
    • 第三次循环:遍历的元素为old-d,在new中存在,通过keyToNewIndexMap获得在new中的index为1;调用patch;
    • 跳出循环,遍历c1剩余阶段结束;
  • 此时进入for (i = toBePatched - 1; i >= 0; i--)倒着遍历剩余c2阶段,此时i为2,j为0,s1为1,s2为1,newIndexToOldIndexMap为[4, 0, 2]:
    • 第一次循环,判断当前nextChild(new-b)存不存在,通过newIndexToOldIndexMap发现nextChild存在,并且在old里面的索引值为2,j–,此时j为-1;i–,i为1;
    • 第二次循环,判断当前nextChild(new-e)存不存在,通过newIndexToOldIndexMap发现nextChild的索引值为0,表示不存在,则调用patch;i–,i为0;改变后c1为[‘a’, ‘e’, ‘b’, ‘d’];
    • 第三次循环,判断当前nextChild(new-d)存不存在,通过newIndexToOldIndexMap发现nextChild的索引值为4,表示存在,则调用move;i–,i为-1;改变后c1为[‘a’, ‘d’ ‘e’, ‘b’];
    • 此时i为-1,跳出循环,循环结束
  • 遍历结束,结果变更为了new: [‘a’, ‘d’, ‘e’, ‘b’]

isSameVNodeType

大家可以看下下面isSameVNodeType的代码,大家在写代码的时候,为了能够提高页面性能,dom diff的速度,如果没有发生变更的元素,key一定要保持一样,不要v-for="(item, index) in list" :key="index"这样来写,因为当只有数组内部元素发生了位置移动而元素未发生改变时,index的值是变更的,这样在dom diff的时候就会使程序发生误解。key的唯一性很重要

export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
    return n1.type === n2.type && n1.key === n2.key
}

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

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

相关文章

Java语法之封装

我们应该都知道Java面向对象的三大特性&#xff1a;封装&#xff0c;继承&#xff0c;多态&#xff0c;今天小编给大家分享封装这个概念以及使用&#xff0c;我们开始吧&#xff1a; 目录 &#x1f389;封装的概念 &#x1f389;封装的使用 &#x1f389;封装的好处 &#…

论文写作:word连续交叉引用

文章目录一、问题背景二、步骤一、问题背景 在写作得时候&#xff0c;使用word的 “交叉引用”功能可以形成超链接格式的标号。但是交叉引用每次只能选择一篇论文&#xff0c;在连续选择多篇论文的时候&#xff0c;就是 “[1][2][3]” 而不是 “[1-3]” 这样的格式。 如图&…

redis set zset key 常用命令

list 可以重复 set不可以 list 有序 set元素位置无序 key常用命令 #1. 存储数据 sadd key member [member ...] 获取的结果是无序的 #2. 获取数据&#xff08;获取全部数据&#xff09; smembers key #3. 随机获取一个数据&#xff08;获取的同时&#xff0c;移除数据&#…

22.11.17打卡 mysql学习笔记

为了不挂科...... 内连接 2022年11月17日 19:34 分为隐式内连接和显式内连接 区别是: 表的连接形式和链接条件的表现形式; 链接条件的表现形式 隐式内连接 显式内连接 外连接 2022年11月17日 19:44 外连接分为左外连接和右外连接 左外连接 右外连接 自连接 2022年11月17…

为什么vue3要选用proxy,好处是什么?

提问 Object.defineProperty()和proxy的区别&#xff1f;为什么vue3要选用proxy&#xff0c;好处是什么&#xff1f; proxy Proxy 对象用于创建一个对象的代理&#xff0c;从而实现基本操作的拦截和自定义&#xff08;如属性查找、赋值、枚举、函数调用等&#xff09;。 Pr…

微软推出Azure量子资源估算器,加速量子算法研发

​ &#xff08;图片来源&#xff1a;网络&#xff09; 近期&#xff0c;微软推出了Azure量子资源估算器&#xff0c;以帮助研发人员检查他们的算法是否可以在未来规模化的量子计算机上运行&#xff0c;并在不同硬件之间进行比较&#xff0c;同时估算在这些系统上执行量子应用…

JVM——垃圾回收机制和内存分配策略

文章目录垃圾回收的垃圾是什么如何判断一个对象是不是垃圾引用计数法可达性分析算法Java的引用垃圾回收基础算法标记清除标记整理标记复制分代垃圾回收GC分析实例HotSpot算法实现细节根节点枚举记忆集与卡表为什么需要记忆集卡表写屏障写屏障&#xff08;Write Barrier&#xf…

如何实现 MySQL 增删改查操作

文章目录1.新增1.1 不指定列插入1.2 指定列插入1.3 一次性插入多行2.查询2.1 全列查询&#xff08;查询表中的所有列&#xff09;2.2 指定列查询2.3 在查寻过程中进行简单计算&#xff08;列和列之间&#xff09;2.4 给查询结果的列指定一个别名2.5 查询的时候针对列来去重&…

【AI绘画 | draft意间】国产draft推荐及AI绘画背后的原理解读

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;喜欢编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

计算机网络—网络层

网络协议 IP 概述 因为网络层是整个互联网的核心&#xff0c;因此应当让网络尽可能简单。网络层提供简单灵活的、无连接的、尽最大努力交互的数据报服务。 使用 IP 协议&#xff0c;可以把异构的物理网络连接起来&#xff0c;使网络层像统一的网络 与 IP 协议配套使用有三种…

Molecular Psychiatry:神经成像预测模型在心理健康领域的未来趋势

使用神经成像数据的预测建模有潜力提高我们对精神障碍的神经生物学基础和推定的信息干预的理解。因此&#xff0c;有大量的文献回顾了已发表的研究&#xff0c;机器学习的数学基础&#xff0c;以及使用这些方法的最佳实践。随着我们在心理健康和机器学习方面的知识不断发展&…

【分布式应用】GFS分布式文件系统

文件系统&#xff1a;用于存储和管理文件的相关系统。 FS&#xff08;文件系统&#xff09;的作用:从系统角度来看&#xff0c;文件系统是对文件存储设备的空间进行组织和分配&#xff0c;负责文件存储并对存入的文件进行保护和检索的系统。 具体地说&#xff0c;它负责为用户建…

Windows重启时的电脑蓝屏怎么办?

在使用Windows电脑时&#xff0c;最害怕的是遇到系统突然崩溃的情况&#xff0c;特别是出现蓝屏。蓝屏可能会导致数据丢失、无法启动Windows等糟糕的情况。那电脑重启时蓝屏怎么解决&#xff1f; 解决方法一、使用系统还原撤消最近的更改 Windows中的系统还原功能是一个便利的…

html5+css3

目录 一、html简介&#xff1a; 1、什么是网页&#xff1f; 2、什么是html&#xff1f; 3、网页的形成 二、常用的浏览器 三、web标准&#xff08;重点&#xff09; 1、为什么要使用web标准&#xff1f; 2、遵循web标准的优点&#xff1a; 四、html语法规范 1、基本语…

Hadoop架构、组件、及其术语汇总和理解

推荐大象教程&#xff0c;介绍Hadoop、HDFS、MapReduce架构和工作原理相对来说非常的清晰。其内容是与《Hadoop the Definitive Guide》基本一致的。讲解的很细致、细节&#xff0c;又带了一些个人的理解和举例子&#xff0c;比较易懂&#xff0c;是比Hadoop官网更值得一看的入…

Assignment写作怎么避免不及格的情况出现?

俗语说得好&#xff0c;犯错误是在所难免的。中西方教育方式大不相同&#xff0c;对Assignment的写作要求也有所不同。刚到国外时&#xff0c;留学生对国外的教育制度和学习过程缺乏了解。难怪在日常学习中&#xff0c;尤其是在英文Assignment写作过程中&#xff0c;留学生都会…

限时开源,来自大佬汇总的Kafka限量笔记,绝对不会后悔!

前言 今天我们来聊聊 Kafka &#xff0c;主要是带你重新认识一下 Kafka&#xff0c;聊一下 Kafka 中比较重要的概念和问题。 我们现在经常提到 Kafka 的时候就已经默认它是一个非常优秀的消息队列了&#xff0c;我们也会经常拿它给 RocketMQ、RabbitMQ 对比。我觉得 Kafka 相…

GaussDB CN服务异常实例分析

摘要&#xff1a;先通过OPS确认节点状态是否已经恢复&#xff0c;或登录后台执行cm_ctl query -Cv确认集群是否已经Normal。本文分享自华为云社区《【实例状态】GaussDB CN服务异常》&#xff0c;作者&#xff1a;酷哥。 确认节点状态 先通过OPS确认节点状态是否已经恢复&…

sqoop部署

一、实验介绍 1.1实验内容 本次实验包括sqoop安装部署及利用Sqoop在mysql数据库与hive之间进行数据迁移。 1.2实验知识点 Sqoop安装 mysql到Hive数据迁移 1.3实验环境 Sqoop-1.4.7 网易云平台 1.4实验资源 资源名称存储目录Sqoop安装包/opt/software/package/1.5实验步…

原生 canvas 如何实现大屏?

前言 可视化大屏该如何做&#xff1f;有可能一天完成吗&#xff1f;废话不多说&#xff0c;直接看效果&#xff0c;线上 Demo 地址 lxfu1.github.io/large-scree…。 看完这篇文章&#xff08;这个项目&#xff09;&#xff0c;你将收获&#xff1a; 全局状态真的很简单&…