使用递归实现深拷贝
文章目录
- 为什么要使用递归什么深拷贝
- 具体实现
- 基础实现
- 处理 函数
- 处理 Symbol
- 处理 Set
- 处理 Map
- 处理 循环引用
- 结语-源码
为什么要使用递归什么深拷贝
我们知道在 JavaScript 中可以通过使用JSON序列化来完成深拷贝,但是这种方法存在一些缺陷,比如对于函数、Symbol、Map 等等数据是无法处理的,甚至如果是循环引用的话还会造成报错,具体关键JSON的介绍我就不在这里赘述了,有兴趣的可以看看我的另一篇文章JSON详解


具体实现
基础实现
-
我们先实现一下最简单的,只对普通数据进行处理,数据如下:
const obj = {name: '张三',age: 18,friends: [{ name: '李四', age: 20 },{ name: '王五', age: 22 }],other:{address:'长沙'} } -
我们肯定要有一个这样的方法或者函数来帮助我们完成这个深拷贝,如果要两个对象要不关联的话,那就只能是创建一个全新的对象,全新的对象怎么来,对吧,所以我们就可以写出如下代码:
function deepClone(value) {// 创建对象-用来承载数据const newObj = {} } -
现在的问题就回到了。我们如何把 obj 的数据赋值给 newObj,肯定不能是直接 newObj = obj,我们要做的就是拿到 obj 对象中的 k、v,通过这个 k、v 重新给 newObj 这个对象创建数据,但是我又不知道这个传入进来的对象具备什么属性和值,所以怎么得到这个 k、v 呢?而这种取出一个数据中的每一项,是不是感觉和数组的遍历很像呢,所以如果可以遍历这个对象的话,是不是就可以拿到呢
-
不知道大家有没有记得以前 js 基础接触过的 for…in 方法,这个方法就可以遍历对象,可以遍历对象是不是就表示可以把 obj 所有有的属性和值也给 newObj 对象赋值一下呢?所以我们下一步就应该是使用 for…in 来完成,如下:
function deepClone(value) {const newObj = {}// 遍历 value 赋值给 newObjfor (let key in value) {newObj[key] = value[key]}return newObj } -
现在是不是实现了我们的效果呢,不知道,打印结果看一下吧,添加测试代码,如下:
const newObj = deepClone(obj)console.log('newObj: ', newObj) -
结果如图:

-
值确实是一样的,但是是不是可以实现那种修改 newObj 而不影响 obj 呢?测试一下吧,测试代码如下:
console.log(newObj === obj)console.log(newObj.friends === obj.friends)console.log(newObj.other === obj.other)newObj.other.address = '上海'console.log(newObj.other.address)console.log(obj.other.address) -
输出结果如图:

-
这就非常的有意思了,newObj 确实不等于 obj 了,表示最外层的引用确实断开了,但是 friends 和 other 属性确还是相等的,且改变了 newObj.other.address 的值,obj 这个地方的值也改变了
-
其实细心一点就不难发现,firends 和 other 属性也都是一个对象啊,所以我们是不是也要对他们进行处理呢?那怎么处理呢,针对这种情况和我们处理这个外层的其实是不是一致的啊,而重复这个过程,哪想到了什么,对,本文的主题,
递归,所以如果检测到一个值是 对象 的话,就再次递归这个函数执行,修改代码如下:function deepClone(value) {const newObj = {}for (let key in value) {// 判断属性值是否为对象,如果是对象则递归调用deepClone函数newObj[key] = typeof value[key] === 'object' ? deepClone(value[key]) : value[key]}return newObj } -
查看克隆的结果,如图:

-
从结果上看 friends 这个属性值从数组变成了对象,这是因为数组也是一个对象,而我们处理数组的方式也是重新创建一个 newObj 来存储数组的数据,所以返回的值自然就变成了对象,这时候应该怎么解决呢?
-
问题尽然是最开始的 newObj 被赋值为了一个 {},那么我们只要判断当前克隆的值是一个数组的时候,就赋值为 [],否则赋值为 {},是不是就可以解决了,如下:
function deepClone(value) {// 如果是数组,则创建一个新的数组,否则创建一个新的对象const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = typeof value[key] === 'object' ? deepClone(value[key]) : value[key]}return newObj } -
结果如图:

-
现在我们不改动测试语句,查看测试结果,如图:

-
是不是感觉比较简单呢,剩下的我们需要做一下优化,判断是否是一个对象可以抽离成一个方法,且可以用于开始的边界判断,如下:
function isObject(value) {return typeof value === 'object' && value !== null } -
然后使用这个方法优化 deepClone 方法,如下:
function deepClone(value) {if (!isObject(value)) {return value}const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}return newObj }
处理 函数
-
为了更加直观的看到处理函数类型,我们先暂时对克隆的对象做一些修改,如下:
const obj = {name: '张三',age: 18,sayHi: function () {console.log('你好啊!')} } -
首先对于这个函数的处理,我们需要先认清楚一些概念,如果一个函数内部的逻辑非常复杂,我们要通过创建一个新的 Funciton 来实现一个完全的新的函数,也是可以的,但是会非常的复杂,而且没有什么必要,一个函数本身就具备复用性,内部的作用域本身就互不干扰,所以我们只要获取到这个函数,并重新赋值给新拷贝的对象里面的对应的位置即可,这也是一种目前比较常见的做法
-
因此我们只要发现当前克隆的值是一个函数的话,直接返回即可,如下:
function deepClone(value) {// 判断是否是一个函数if (typeof value === 'function') {return value}if (!isObject(value)) {return value}const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}return newObj } -
现在输出打印克隆的打印结果,如图:

处理 Symbol
-
我们还是一样先修改一下数据,如下:
const s1 = Symbol('aaa') const s2 = Symbol('bbb')const obj = {name: '张三',age: 18,[s1]: 'aaa',s2: s2 } -
我们使用目前的方法,来看一下克隆的结果,如图:

-
使用 s1 作为键的属性,没有被拷贝进来,但是使用字符串 s2 的属性和携带的 Symbol 类型的值被拷贝了进来,而且这两个对象里面的 s2 这个 Symbol值是不是同一个呢?测试代码如下:
console.log(newObj.s2 === obj.s2) // true -
就不粘贴结果了,测试中得出的还是同一个 Symbol,所以如果不希望使用同一个 Symbol 的话,我们可以在最开始的时候进行一个判定,如果当前克隆的值是一个 Symbol 的话,就重新创建 Symbol,并使用原来的 Symbol 的 description,如下:

function deepClone(value) {// 判断是否是一个 Symbolif (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}return newObj } -
查看输出结果,如下:
console.log(newObj.s2 === obj.s2) // false -
那为什么 Symbol 作为 key 的时候无法被拷贝呢?这是因为一个 Symbol 是无法被遍历的,如图:

-
那如果获取这个 Symbol 呢?如图:

-
具体是不是可以呢,我们写一些代码测试一下,如图:

-
所以我们现在需要针对 Symbol 为key时进行一些特殊的处理,如下:
function deepClone(value) {if (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}// 获取对象上所有的为 Symbol 类型的keyconst symKeys = Object.getOwnPropertySymbols(value)for (const sk of symKeys) {// 赋值调用当前函数即可// - 至于这个 key 是不是要重新 Symbol 就看自己的需求了newObj[sk] = deepClone(newObj[sk])}return newObj } -
查看结果,如图:

处理 Set
-
修改数据如下:
const obj = {name: '张三',age: 18,set: new Set([1, 2, 3]) } -
拷贝的结果如图:

-
set 变了一个空对象,这肯定不是我们需要的结果,那就还需要对是一个 set 类型的时候进行处理,这个也很简单
function deepClone(value) {if (value instanceof Set) {return new Set([...value])}if (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}const symKeys = Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] = deepClone(newObj[sk])}return newObj } -
结果如图:

-
但是这种赋值的方法是存在一些隐患的,比如我们存入的是 set 是对象呢?首先改变数据,我们看一下代码,如下:
const obj = {name: '张三',age: 18,set: new Set([1, 2, 3]),sets: new Set() }const o1 = { a: 1, b: 2 } const o2 = { c: 3, d: 4 } obj.sets.add(o1) obj.sets.add(o2) -
我们按照现在的方法看一下克隆的结果,测试代码如下:
const newObj = deepClone(obj)console.log(newObj)const arr1 = []for (const [key, value] of newObj.sets.entries()) {arr1.push(value) }console.log(arr1[0] === o1)arr1[0].a = 100 console.log(o1) -
结果如图:

-
可以看到 o1 这个对象是受到了影响的,所以我们需要单独对这个赋值的过程进行一些处理,如下:
function deepClone(value) {if (value instanceof Set) {// 创建一个数组来存储值const list = []// 通过 forEach 方法将 Set 中的值复制到数组中value.forEach(item => {// 而这个值通过递归在处理一次list.push(deepClone(item))})// 创建一个新的 Set 对象,并将数组中的值作为参数传入return new Set(list)}if (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}const symKeys = Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] = deepClone(newObj[sk])}return newObj } -
现在我们再看一下处理的结果,如图:

处理 Map
-
修改的数据如下:
const obj = {name: '张三',age: 18,map: new Map() }const o1 = { a: 1, b: 2 } const o2 = { c: 3, d: 4 } const o3 = { name: 'ls', age: 22 }obj.map.set('o1', o1) obj.map.set(o2, o3) -
如果不处理 map,看看打印的结果,如图:

-
这个结果一样也不是我们所期望的,但是 set 和 map 处理的方式其实都是差不多的,所以我就直接展示代码了,如下:
function deepClone(value) {if (value instanceof Set) {const list = []value.forEach(item => {list.push(deepClone(item))})return new Set(list)}// 处理 mapif (value instanceof Map) {const myMap = new Map()for (const [key, _val] of value) {// 这里的 key 是不是需要进行再次的递归处理就取决于自己的需求了// - 一般是不需要再次做额外的处理的const newValue = deepClone(_val)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list = []value.forEach(item => {list.push(deepClone(item))})return new Map(list)}if (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}const newObj = Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}const symKeys = Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] = deepClone(newObj[sk])}return newObj } -
结果如图:

-
测试结果,如下:
console.log(newObj.map.get('o1') === obj.map.get('o1')) // false
处理 循环引用
-
修改数据如下:
const obj = {name: '张三',age: 18 }// my 属性引用自身 obj.my = obj -
查看输出的结果,如下:

-
直接就报了栈溢出的错误,这就是每次递归拷贝的都是 obj 本身,而每个 obj 都具备 my 属性指向自身,递归没有终止条件,自然就会报错
-
这个 my 是通过 obj.my = obj 实现的,那么是不是表示我们在创建了一个 newObj 的时候,也只是需要把 newObj 这个对象自身赋值给 newObj.my = newObj 就可以了,而不需要一直的拷贝
-
为了实现这个,我们就需要来分析一下了,怎么完成 newObj.my = newObj 这一步操作,要完成这个操作首先我们就要能够获取到 newObj 这个对象,这是第一点;第二个条件就是我们还需要能够保存最开始克隆的原始对象 obj,而能够实现这一点的要求的,我们就可以联想到 map 或者 weakmap,这里保证 obj 存储的时候就是弱引用,而不会外界需要销毁的时候导致无法销毁,使用 weakpack 会更加的合适
-
所以我们应该有这么一个操作,手动创建一个 weakmap,然后再 deepClone 第一次创建了 newObj 的时候,就进行存储,wm.set(obj, newObj),而当存在一个这样的数据的时候,我们在就可以在 deepClone 方法上加上一个条件,if(当vm中存在obj这个k的时) { return 就返回存储的 newObj 这个值 }
-
梳理了过程,就可以回到具体的代码实现,如下:
// 全局创建 WeakMap const wm = new WeakMap()function deepClone(value) {if (value instanceof Set) {const list = []value.forEach(item => {list.push(deepClone(item))})return new Set(list)}if (value instanceof Map) {const myMap = new Map()for (const [key, _val] of value) {const newValue = deepClone(_val)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list = []value.forEach(item => {list.push(deepClone(item))})return new Map(list)}if (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}// 当拷贝到 obj.my 这个属性的时候,由于 obj.my 的值就是 obj 本身// - 所以此时传入的值就是 obj,而其他属性如果没有引用自身的话,就不会存在// - 就可以通过判断 weakmap 中是否存在这个数据,如果存在就直接返回 weakmap 中一开始存储的值if (wm.has(value)) {return wm.get(value)}const newObj = Array.isArray(value) ? [] : {}// 创建 newObj 的时候,以需要克隆的初始值作为 key,newObj 作为 valuewm.set(value, newObj)for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key]) : value[key]}const symKeys = Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] = deepClone(newObj[sk])}return newObj } -
结果如图:

-
基于此我们就可以实现 newObj.my.my.my… 的操作,如图:

-
而进行到这一步,还有一个需要的地方,由于我们这个 weakmap 是全局的,就会导致如果在实际的使用中多次调用 deepClone 这个方法的时候,weakmap 这个里面的数据就会越来越多,而实际当完成拷贝的时候,这个数据就不用存在了
-
因此我们可以改写一下,如下:
// 通过参数实现创建 weakmap,只要没有传递,就会自动创建,而如果没有传递了则不会创建,就会使用传递的 weakmap function deepClone(value, wm = new WeakMap()) {if (value instanceof Set) {const list = []value.forEach(item => {// 传递 weakmaplist.push(deepClone(item, wm))})return new Set(list)}if (value instanceof Map) {const myMap = new Map()for (const [key, _val] of value) {// 传递 weakmapconst newValue = deepClone(_val, wm)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list = []value.forEach(item => {// 传递 weakmaplist.push(deepClone(item, wm))})return new Map(list)}if (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}if (wm.has(value)) {return wm.get(value)}const newObj = Array.isArray(value) ? [] : {}wm.set(value, newObj)for (let key in value) {// 同时在内部调用的时候,为了防止后续调用在创建 weakmap,我们在这里调用的时候就要把第一次执行 deepClone 创建的 weakmap 传递进去newObj[key] = isObject(value) ? deepClone(value[key], wm) : value[key]}const symKeys = Object.getOwnPropertySymbols(value)for (const sk of symKeys) {// 传递 weakmapnewObj[sk] = deepClone(newObj[sk], wm)}return newObj } -
结果如图:

-
在效果上也是没有问题的
结语-源码
我在测试中都是单独每一项数据进行测试的,是为了更好的观测,实际一个对象都包含这些数据的话也都是 ok的,需要的话可以自己测试,而且写下来就会发现,其实逻辑都是差不多的,可以根据你实际的情况进行增加或者减免,在日常的开发中使用 JSON 序列化一般也可满足我们的需求,不过知道不用和不知道还是存在本质的区别的,可能现在有些你学习的技术没有实际的意义,但是只有积累的足够多的时候你才能完成一些本质上的突破
很多事情不是因为看到了希望才去坚持,而是因为坚持了才能看到希望
function deepClone(value, wm = new WeakMap()) {if (value instanceof Set) {const list = []value.forEach(item => {list.push(deepClone(item, wm))})return new Set(list)}if (value instanceof Map) {const myMap = new Map()for (const [key, _val] of value) {const newValue = deepClone(_val, wm)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list = []value.forEach(item => {list.push(deepClone(item, wm))})return new Map(list)}if (typeof value === 'symbol') {return Symbol(value.description)}if (typeof value === 'function') {return value}if (!isObject(value)) {return value}if (wm.has(value)) {return wm.get(value)}const newObj = Array.isArray(value) ? [] : {}wm.set(value, newObj)for (let key in value) {newObj[key] = isObject(value) ? deepClone(value[key], wm) : value[key]}const symKeys = Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] = deepClone(newObj[sk], wm)}return newObj
}
相关文章:
使用递归实现深拷贝
文章目录 为什么要使用递归什么深拷贝具体实现基础实现处理 函数处理 Symbol处理 Set处理 Map处理 循环引用 结语-源码 为什么要使用递归什么深拷贝 我们知道在 JavaScript 中可以通过使用JSON序列化来完成深拷贝,但是这种方法存在一些缺陷,比如对于函数…...
工程(十七)——自己数据集跑R2live
博主创建了一个科研互助群Q:772356582,欢迎大家加入讨论。 r2live是比较早的算法,编译过程有很多问题,通过以下两个博客可以解决 编译R2LIVE问题&解决方法-CSDN博客 r2live process has died 问题解决了_required process …...
【python高级用法】迭代器、生成器、装饰器、闭包
迭代器 可迭代对象:可以使用for循环来遍历的,可以使用isinstance()来测试。 迭代器:同时实现了__iter__()方法和__next__()方法,可以使用isinstance()方法来测试是否是迭代器对象 from collections.abc import Iterable, Iterat…...
Nx市工业数据洞察:Flask、MySQL、Echarts的可视化之旅
Nx市工业数据洞察:Flask、MySQL、Echarts的可视化之旅 背景数据集来源技术选型功能介绍创新点总结 背景 随着工业化的不断发展,Nx市工业数据的收集和分析变得愈发重要。本博客将介绍如何利用Flask、MySQL和Echarts等技术,从统计局获取的数据…...
关于正态分布
目录 1.正态分布是什么2.正态分布有什么用途3.如何确定数据服从正态分布 本文简单介绍正态分布的基本概念和用途。 1.正态分布是什么 正态分布,也称为高斯分布,是由德国数学家卡尔弗里德里希高斯在研究测量误差时提出的。他发现许多自然现象和统计数据…...
每日一练(编程题-C/C++)
目录 CSDN每日一练1. 2023/2/27- 一维数组的最大子数组和(类型:数组 难度:中等)2. 2023/4/7 - 小艺照镜子(类型:字符串 难度:困难)3. 2023/4/14 - 最近的回文数(难度:中等)4. 2023/2/1-蛇形矩阵(难度:困难)…...
Unity UnityWebRequest 在Mac上使用报CommectionError
今天是想把前两天写的Demo拿到Mac上打个IPA的完事我发现 在运行时释放游戏资源的时候UnityWebRequest返回的结果不是Success 查看Log发现是 req.result 是CommectionError error是 Cannot connect to destination host 代码如下: UnityWebRequest req UnityWebRequ…...
WorkPlus为企业打造私有化部署IM解决方案
在移动数字化时代,企业面临着如何全面掌控业务和生态的挑战。企业微信、钉钉、飞书、Teams等应用虽然提供了部分解决方案,但无法满足企业的私有化部署需求。此时,WorkPlus作为安全专属的移动数字化平台,被誉为移动应用的“航空母舰…...
QT上位机开发(抽奖软件)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 用抽奖软件抽奖,是一种很常见的抽奖方式。特别是写这篇文章的时候,正好处于2023年12月31日,也是一年中最后一天…...
雨课堂作业整理
第一次作业 1.下列序列是图序列的是( ) A.1,2,2,3,4,4,5 B.1,1,2,2,4,6,6 C.0,0,2&am…...
C#/WPF 只允许一个实例程序运行并将已运行程序置顶
使用用互斥量(System.Threading.Mutex): 同步基元,它只向一个线程授予对共享资源的独占访问权。在程序启动时候,请求一个互斥体,如果能获取对指定互斥的访问权,就职运行一个实例。 实例代码: /// <…...
【基础】【Python网络爬虫】【1.认识爬虫】什么是爬虫,爬虫分类,爬虫可以做什么
Python网络爬虫基础 认识爬虫1.什么是爬虫2.爬虫可以做什么3.为什么用 Ptyhon 爬虫4.爬虫的分类通用爬虫聚焦爬虫功能爬虫增量式爬虫分布式爬虫 5.爬虫的矛与盾(重点)6.盗亦有道的君子协议robots7.爬虫合法性探究 认识爬虫 1.什么是爬虫 网络爬虫&…...
【算法与数据结构】860、LeetCode柠檬水找零
文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:本题的思路比较简单,首先要保存收到的零钱,其次计算找零,最后分解找…...
「Verilog学习笔记」乘法与位运算
专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点,刷题网站用的是牛客网 观察乘数的特点: 1111_1011 1_0000_0000 - 1 - 100 timescale 1ns/1nsmodule dajiang13(input [7:0] A,output [15:0] B);//*************code*********…...
CSS与JavaScript的简单认识
CSS:是一门语言,用于控制网页表现,让页面更好看的。 CSS(Cascading Style Sheet):层叠样式表 CSS与html结合的三种方式: 1、内部样式:用style标签,在标签内部定义CSS样式…...
MAC 中多显示器的设置(Parallels Desktop)
目录 一、硬件列表: 二、线路连接: 三、软件设置: 1. 设置显示器排列位置及显示参数 2. 分别设置外接显示器为:扩展显示器,内建显示器为主显示器 3. 设置Parallels Desktop屏幕参数 四、结果 一、硬件列表&a…...
迁移到云原生:如何使用微服务迁移应用程序
企业遇到大规模部署和监督生产中的应用程序的任务。幸运的是,我们可以使用大量技术和工具。然而,从传统的,整体的结构转变为云态一个人提出了自己的障碍。在这里,您会发现将应用程序从整体设置转移到基于微服务的体系结构时要进行…...
kafka 的零拷贝原理
文章目录 kafka 的零拷贝原理 今天来跟大家聊聊kafka的零拷贝原理是什么? kafka 的零拷贝原理 零拷贝是一种减少数据拷贝的机制,能够有效提升数据的效率; 在实际应用中,如果我们需要把磁盘中的某个文件内容发送到远程服务器上…...
华为云Stack 8.X流量模型分析(五)
六、EIP流量模型分析 弹性公网IP(Elastic IP,简称EIP)提供独立的公网IP资源,包括公网IP地址与公网出口带宽服务。如果资源只配置了私网IP,则无法直接访问Internet,为资源配置弹性公网IP后,可…...
学习动态规划解决不同路径、最小路径和、打家劫舍、打家劫舍iii
学习动态规划|不同路径、最小路径和、打家劫舍、打家劫舍iii 62 不同路径 动态规划,dp[i][j]表示从左上角到(i,j)的路径数量dp[i][j] dp[i-1][j] dp[i][j-1] import java.util.Arrays;/*** 路径数量* 动态规划,dp[i][j]表示从左上角到(i,j)的路径数量…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
