浅拷贝&深拷贝

2023-11-05

什么是深/浅拷贝,他们跟赋值有什么关系?

浅拷贝

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

深拷贝

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

我们用一张图理解一下

总而言之,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

赋值和深/浅拷贝的区别

在比较的都是在引用类型的前提下

赋值:当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

//对象赋值
let obj1 = { 
    person: {
        name: "冰冰",
        age: 20
    },
    sports:'run' 
};
let obj2=obj1;//赋值
obj2.person.name='bb';
obj2.sports='baseball';
console.log(obj2);//{person:{name:'bb',age:20},sport:'baseball'}
console.log(obj1);//{person:{name:'bb',age:20},sport:'baseball'}

浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。

//浅拷贝
let obj1 = { 
    person: {
        name: "冰冰",
        age: 20
    },
    sports:'run' 
};
let obj3=lightClone(obj1);
obj3.person.name='bb';
obj3.sports='baseball';
console.log(obj3);//{person:{name:'bb',age:20},sport:'baseball'}
console.log(obj1);//{person:{name:'bb',age:20},sport:'run'}

深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。

//深拷贝
let obj1 = { 
    person: {
        name: "冰冰",
        age: 20
    },
    sports:'run' 
};
let obj4=deepClone(obj1);
obj4.person.name='bb';
obj4.sports='baseball';
console.log(obj4);//{person:{name:'bb',age:20},sport:'baseball'}
console.log(obj1);//{person:{name:'冰冰',age:20},sport:'run'}

obj1是原始对象,obj2是赋值操作得到的对象,obj3浅拷贝得到的对象,obj4深拷贝得到的对象

浅拷贝的实现方式

Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

注意

如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

如果该参数不是对象,则会先转成对象,然后返回。但是由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。而且Object.assign()拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。属性名为 Symbol 值的属性,也会被Object.assign()拷贝。

更详细的可以看对象的新增方法 - ECMAScript 6入门

let obj1 = { 
    person: {
        name: "冰冰",
        age: 20
    },
    sports:'run' 
};
let obj2 = Object.assign({}, obj1);
console.log(obj1);
obj2.person.name = "chen";
obj2.sports = 'football'
console.log(obj1); 
// { person: { name: "chen" , age: 20 } , sports:'run'};

用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

function clone(origin) {
    let originProto = Object.getPrototypeOf(origin);
    return Object.assign(Object.create(originProto), origin);
  }

函数库lodash的_.clone方法

函数库lodash也有提供_.clone用来做 Shallow Copy。

第一步:

npm i lodash

第二步:

var _=require('lodash');
let obj1 = { 
    person: {
        name: "冰冰",
        age: 20
    },
    sports:'run' 
};
let obj2=_.clone(obj1);
console.log(obj2.person==obj1.person);
//true

展开运算符...

展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。

let obj1 = { 
    person: {
        name: "冰冰",
        age: 20
    },
    sports:'run' 
};
let obj2={...obj1};
console.log(obj2.person==obj1.person);
//true

Array.prototype.concat()

let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2 = arr.concat();    
arr2[0]=6;
arr2[2].username = 'wade';
console.log(arr); 
//[ 1, 3, { username: 'wade' } ]

Array.prototype.slice()

let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2 = arr.slice();    
arr2[0]=6;
arr2[2].username = 'wade';
console.log(arr); 
//[ 1, 3, { username: 'wade' } ]

如何实现深拷贝?

JSON.parse(JSON.stringify())

利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

let obj = {
    person:{
        name:'bb',
        age:20
    },
    sports:'run'
};
let obj2 = JSON.parse(JSON.stringify(obj));
obj2.person.name = '冰冰'; 
console.log(obj,obj2)

但是这个方法有坑!

存在的坑

  1. 当对象中有时间类型的元素时候,时间类型会被变成字符串类型数据

const obj = {
    date:new Date()
}
console.log(typeof obj.date)//object
const objCopy = JSON.parse(JSON.stringify(obj));
console.log(typeof objCopy.date); //string

到最后,getTime()调不了了,getYearFull()也调不了了,所有时间类型的内置方法都调不动了。但,string类型的内置方法全能调了。

  1. 当对象中有undefined类型或function类型的数据时, undefined和function会直接丢失

const obj = {
    undef: undefined,
    fun: () => { console.log('复制我就消失!') }
}
console.log(obj,"obj"); 
const objCopy = JSON.parse(JSON.stringify(obj));
console.log(objCopy,"objCopy")
  1. 当对象中有NaN、Infinity和-Infinity这三种值的时候 ,会变成null

const obj = {
    nan:NaN,
    infinityMax:1.7976931348623157E+10308,//浮点数最大值
    infinityMin:-1.7976931348623157E+10308,//浮点数最小值
}
console.log(obj, "obj");
const objCopy = JSON.parse(JSON.stringify(obj));
console.log(objCopy,"objCopy")
  1. 当对象循环引用的时候会报错

const obj = {
    objChild:null
}
obj.objChild = obj;
const objCopy = JSON.parse(JSON.stringify(obj));
console.log(objCopy,"objCopy")
  1. symbol 值在序列化过程中会被忽略

  1. 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

对JSON.parse(JSON.stringify())改写

// 对象序列化,undefined和函数丢失问题
const JSONStringify = (option) => {
  return JSON.stringify(option,
    (key, val) => {
      // 处理函数丢失问题
      if (typeof val === 'function') {
        return `${val}`;// 转化为字符串
      }
      // 处理undefined丢失问题
      if (typeof val === 'undefined') {
        return 'undefined';
      }
      return val;
    },
  )
}
// 对象序列化解析
const JSONParse = (objStr) => {
  return JSON.parse(objStr, (k, v) => {
    if (typeof v === 'string' && v.indexOf && v.indexOf('function') > -1) {
      // eval 可能在eslint中报错,需要加入下行注释
      // eslint-disable-next-line
      return eval(`(function(){return ${v}})()`);
      // 立即执行函数 返回原有的 eval('字符串') 字符串是要执行的语句
    }
    return v;
  });
}

深入JSON.stringify()

函数库lodash的_.cloneDeep方法

不过如果只是为了一个深复制功能引入整个第三方库就显得很不值得了,可以考虑一下按需加载,用哪个模块加载哪个模块。

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

jQuery.extend()方法

jquery 有提供一個$.extend可以用来做 Deep Copy

$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false

不过jQuery的感觉现在不常用了,这个方法仅适用于JQuery构建的项目。

手写递归方法

递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

具体思路:

  1. 创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性依次添加到新对象上,返回。

  1. 如果是深拷贝的话,考虑到我们要拷贝的对象是不知道有多少层深度的,我们可以用递归来解决问题,如果有更深层次的对象可以继续递归直到属性为原始类型。

  1. 如果是原始类型,无需继续拷贝,直接返回

  1. 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。

//深拷贝函数
function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
//测试
const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
let objClone=clone(target);
console.log(objClone)

有种特殊情况需注意就是对象存在循环引用的情况,即对象的属性直接的引用了自身的情况,就会发生栈溢出。

如果你在上面代码中加入target.target=target;,也就是

为了解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。

这个存储空间,需要可以存储key-value形式的数据,且key可以是一个引用类型,我们可以选择Map这种数据结构:

  • 检查map中有无克隆过的对象

  • 有 - 直接返回

  • 没有 - 将当前对象作为key,克隆对象作为value进行存储

  • 继续克隆

function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

你会发现,target变成一个循环应用的类型。整个对象也可以被完美的深拷贝下来。

优化

强弱引用的优化

其实还有一个优化点,上面提到需要可以存储key-value形式的数据,且key可以是一个引用类型,所以我们用来Map。

但是,设想一下,如果我们要拷贝的对象非常庞大时,使用Map会对内存造成非常大的额外消耗,而且我们需要手动清除Map的属性才能释放这块内存,而WeakMap会帮我们巧妙化解这个问题。

Set 和 Map 数据结构 - ECMAScript 6入门

我们知道,WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

弱引用:在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。

举个例子

  • 使用Map,对象间是存在强引用关系,虽然我们手动将obj,进行释放,然是target依然对obj存在强引用关系,所以这部分内存依然无法被释放。

let obj = { name : 'bb'}
const target = new Map();
target.set(obj,'冰冰');
obj = null;
  • 如果是WeakMap的话,target和obj存在的就是弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉。

let obj = { name : 'bb'}
const target = new Map();
target.set(obj,'冰冰');
obj = null;

运行效率的优化

我们都知道,循环的方式有很多种,for in 、forEach、for、while等等,但是他们的执行效率并不同。采用相同代码块进行测试发现,while的效率是最好的,所以,我们可以想办法把for in遍历改变为while遍历。

我们先使用while来实现一个通用的forEach遍历,iteratee是遍历的回调函数,他可以接收每次遍历的value和index两个参数:

function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}

现在我们把forEach用到clone函数中,当遍历数组时,直接使用forEach进行遍历,当遍历对象时,使用Object.keys取出所有的key进行遍历,然后在遍历时把forEach会调函数的value当作key使用:

function clone(target, map = new WeakMap()) {
    if (typeof target === 'object') {
        const isArray = Array.isArray(target);
        let cloneTarget = isArray ? [] : {};

        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);

        const keys = isArray ? undefined : Object.keys(target);
        forEach(keys || target, (value, key) => {
            if (keys) {
                key = value;
            }
            cloneTarget[key] = clone2(target[key], map);
        });

        return cloneTarget;
    } else {
        return target;
    }
}

我们可以用console.time();和console.timeEnd();配合算出clone函数的执行时间,最后会发现每次都是优化后的比较快。

类型优化

在上面的代码中,我们其实只考虑了普通的object和array两种数据类型,实际上所有的引用类型远远不止这两个,还有很多,下面我们先尝试获取对象准确的类型。

  • 首先,判断是否为引用类型,我们还需要考虑function和null两种特殊的数据类型

function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}
if (!isObject(target)) {
  return target;
}
//...
  • 获取数据类型,我们可以使用toString来获取准确的引用类型,前提是此方法在自定义对象中未被覆盖,toString才会达到预想的效果。事实上,大部分引用类型比如Array、Date、RegExp等都重写了toString方法。我们可以直接调用Object原型上未被覆盖的toString()方法,使用call来改变this指向来达到我们想要的效果。

function getType(target) {
    return Object.prototype.toString.call(target);
}

在上面的结果中,我们可以分为

  • 可以继续遍历的类型

  • 不可以继续遍历的类型

我们先考虑object、array、Map,Set

有序这几种类型还需要继续进行递归,我们首先需要获取它们的初始化数据,例如上面的[]和{},我们可以通过拿到constructor的方式来通用的获取。

例如:const target = {}就是const target = new Object()的语法糖。另外这种方法还有一个好处:因为我们还使用了原对象的构造方法,所以它可以保留对象原型上的数据,如果直接使用普通的{},那么原型必然是丢失了的。

function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}

我们改写clone函数,对可继续遍历的数据类型进行处理:

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';

function clone(target, map = new WeakMap()) {

    // 克隆原始类型
    if (!isObject(target)) {
        return target;
    }

    // 初始化
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
    }

    // 防止循环引用
    if (map.get(target)) {
        return map.get(target);
    }
    map.set(target, cloneTarget);

    // 克隆set
    if (type === setTag) {
        target.forEach(value => {
            cloneTarget.add(clone(value,map));
        });
        return cloneTarget;
    }

    // 克隆map
    if (type === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, clone(value,map));
        });
        return cloneTarget;
    }

    // 克隆对象和数组
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) {
            key = value;
        }
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;
}

其他剩余的类型我们把它们统一归类成不可处理的数据类型,我们依次进行处理:

Bool、Number、String、String、Date、Error这几种类型我们都可以直接用构造函数和原始数据创建一个新对象:

function cloneOtherType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(targe);
        case regexpTag:
            return cloneReg(targe);
        case symbolTag:
            return cloneSymbol(targe);
        default:
            return null;
    }
}
//克隆Symbol
function cloneSymbol(target) {
    return Object(Symbol.prototype.valueOf.call(target));
}

//克隆正则:

function cloneReg(target) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(target));
    result.lastIndex = target.lastIndex;
    return result;
}

最后

//可继续遍历的数据类型
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag=['object Arguments'];
//不可继续遍历的数据类型
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const funcTag='[object Function]';

const deepTag=[mapTag,setTag,arrayTag,objectTag,argsTag];

// 通用while循环
function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}

// 判断是否为引用类型
function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}

// 获取实际类型
function getType(target) {
    return Object.prototype.toString.call(target);
}

// 初始化被克隆的对象
function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}

// 克隆Symbol
function cloneSymbol(target) {
    return Object(Symbol.prototype.valueOf.call(target));
}

// 克隆正则
function cloneReg(target) {
    const reFlags = /\w*$/;
    const result = new target.constructor(target.source, reFlags.exec(target));
    result.lastIndex = target.lastIndex;
    return result;
}

// 克隆函数
function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}

// 克隆不可遍历类型
function cloneOtherType(target, type) {
    const Ctor = target.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(target);
        case regexpTag:
            return cloneReg(target);
        case symbolTag:
            return cloneSymbol(target);
        case funcTag:
            return cloneFunction(target);
        default:
            return null;
    }
}

function clone(target,map=new WeakMap()){
    // 原始类型直接返回
    if(!isObject(target)){
        return target;
    }
    
    // 根据不同类型进行操作
    const type=getType(target);
    let cloneTarget;
    if(deepTag.includes(type)){
        cloneTarget=getInit(target,type);
    }else{
        return cloneOtherType(target,type);
    }

    // 处理循环引用
    if(map.get(target)){
        return target;
    }
    map.get(target,cloneTarget);

    // 处理set
    if(type===setTag){
        target.forEach(value=>{
            cloneTarget.add(clone(value));
        });
        return cloneTarget;
    }
    // 处理map
    if(type==mapTag){
        target.forEach((value,key)=>{
            cloneTarget.set(key,clone(value))
        });
        return cloneTarget;
    }
    // 处理对象和数组
    const keys=type===arrayTag?undefined:Object.keys(target);
    forEach(keys||target,(value,key)=>{
        if(keys){
            key=value;
        }
        cloneTarget[key]=clone(target[key],map);
    })
    return cloneTarget;
}

const map = new Map();
map.set('key', 'value');
map.set('bb', '冰冰真棒');

const set = new Set();
set.add('冰冰很爱吃');
set.add('冰冰啥都吃');

const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8],
    empty: null,
    map,
    set,
    bool: new Boolean(true),
    num: new Number(2),
    str: new String(2),
    symbol: Object(Symbol(1)),
    date: new Date(),
    reg: /\d+/,
    error: new Error(),
    func1: () => {
        console.log('冰冰很自恋');
    },
    func2: function (a, b) {
        return a + b;
    }
};
let obj2=clone(target);
console.log(obj2)

参考

如何写出一个惊艳面试官的深拷贝?

深拷贝的终极探索(99%的人都不知道)

对象的新增方法 - ECMAScript 6入门

JSON.stringify()还能这么玩

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅拷贝&深拷贝 的相关文章

随机推荐

  • 简单制作后台系统页面(含菜单)

    第一步 制作数据库表 我个人喜欢在PowerDesigner先建好数据库模型 然后导入到mysql里 导入方式 在PowerDesigner导航栏点开Datebase选择Datebase Generation再选择最后的preview 然后
  • Day.js 常用用法

    我认为克服恐惧最好的办法理应是 面对内心所恐惧的事情 勇往直前地去做 直到成功为止 罗斯福 Day js 时间戳转换 const nowTime dayjs format console log 获取当前时间 nowTime const n
  • GPT4来了!微软云能否反超亚马逊夺冠,就靠它了

    文 光锥智能 作者 刘雨琦 Azure 微软云 能否反超AWS 亚马逊云 夺冠 就靠ChatGPT了 今天凌晨 GPT4横空出世 支持图像输入和混合输入 多模态大模型的出现 将对算力产生更高的需求 一场由ChatGPT引发的算力革命 即将给
  • TCP的三次握手(一个好男人追女孩的故事)一看必懂系列

    网络世界如情场 女生指代服务端 在网络协议内 和TCP是纯情男的作风 UDP作风则称为 渣男 理由非常的简单 由于UDP的行为就是从来不会和任何女人产生感情 不建立连接 因此追女生的效率 具有高效率的特性 就比TCP作风高的多 从来不付出
  • 通过U盘向服务器拷贝文件

    目录 完整操作流程 检查U盘是否被识别 gt 挂载U盘 gt 拷贝文件 gt 卸载U盘 检查U盘是否被识别 挂载U盘 拷贝文件 卸载U盘 完整操作流程 检查U盘是否被识别 gt 挂载U盘 gt 拷贝文件 gt 卸载U盘 检查U盘是否被识别
  • 数据结构算法设计——深搜DFS(走迷宫)

    一 什么是深搜 深搜就是 深度搜索 也就是 深度优先的搜索 那什么是 深度优先 呢 我们拿最常见的迷宫问题举例 深度优先就是你照着一条路死命的走 有个形象的说法叫 不撞南墙不回头 一直到这条路走不通了 再返回上一步选择其他的方向 在算法中我
  • Java8 Streams用法总结大全 之 Collector用法详解

    1 前言 在 Java8 Streams用法总结大全 之 Stream中的常见操作 中 我们已经学习了Stream中的常用操作 其中也提到了collect 的用法 当时只是通过入参Collectors toList 实现了把Stream转为
  • [SQL]postgreSQL中如何查找无主键的sql语句

    查找postgreSQL数据库中 查找无主键的表 可以通下面语句查找 select from pg tables where hasindexes is false and schemaname public
  • 新编法学概论--吴祖谋

    新编法学概论 吴祖谋 2007 pdf 介绍法学概论的书籍 但是写的太官僚了 什么阶级论 之类的开头 让我读着那样的不理解 能不能有本写的比较通俗易懂的法学概论 这样的书籍 真心的不喜欢看 但是没办法 还是看一看吧 1 宪法 三次完全的更新
  • 什么是代码区、常量区、静态区(全局区)、堆区、栈区?

    前言 和作者有同样的感觉 对代码区 常量区 静态区 全局区 堆区 栈区没有较深刻的认识 通过查找网络找到本篇比较优秀的文章 特此转发 摘自CSDN https blog csdn net u014470361 article details
  • oracle中translate与replace的使用

    1 translate 语法 TRANSLATE char from to 用法 返回将出现在from中的每个字符替换为to中的相应字符以后的字符串 若from比to字符串长 那么在from中比to中多出的字符将会被删除 三个参数中有一个是
  • OpenCV中的图像腐蚀和膨胀操作有哪些?

    在OpenCV中 图像腐蚀 Erosion 和膨胀 Dilation 是常用的图像形态学操作 它们可以用于去除噪声 填充空洞 提取图像中的结构等 下面是几种常见的腐蚀和膨胀操作 腐蚀操作 图像腐蚀可以通过函数cv2 erode 来实现 腐蚀
  • [Linux Audio Driver] 音频POP音问题归纳总结

    1 板级电容 电感发声 情况就是你设备开机之后 啥也没干 然后听到呲啦刺啦的声音 这种情况我遇到过一次 这个是 不合理的结构设计或者走线导致的 硬件实力挖坑 需要改版解决 2 播放声音长时间有杂音 这个锅我们送给硬件 这个是芯片之间有干扰
  • SVN 启动模式

    首先 在服务端进行SVN版本库的相关配置 手动新建版本库目录 mkdir opt svn 利用svn命令创建版本库 svnadmin create opt svn runoob 使用命令svnserve启动服务 svnserve d r 目
  • java tomcat远程调试端口_tomcat开发远程调试端口以及利用eclipse进行远程调试

    一 tomcat开发远程调试端口 方法1 WIN系统 在catalina bat里 SET CATALINA OPTS server Xdebug Xnoagent Djava compiler NONE Xrunjdwp transpor
  • LeetCode2.两数相加

    给你两个 非空 的链表 表示两个非负的整数 它们每位数字都是按照 逆序 的方式存储的 并且每个节点只能存储 一位 数字 请你将两个数相加 并以相同形式返回一个表示和的链表 你可以假设除了数字 0 之外 这两个数都不会以 0 开头 示例 1
  • 了解VGG网络结构特点,利用VGG完成图像分类

    学习目标 知道VGG网络结构的特点 能够利用VGG完成图像分类 2014年 牛津大学计算机视觉组 Visual Geometry Group 和Google DeepMind公司的研究员一起研发出了新的深度卷积神经网络 VGGNet 并取得
  • 为什么同样是软件测试员,别人拿3万月薪,你却只能拿3千?你反思了没有?

    有人说软件测试入门门槛低 但是为什么同样是软件测试员 别人拿3万元的月薪 你却只能拿3千月薪 有的人只会抱怨 别人运气好 经验足等等 但是有没有去发现一些根本性的东西 最近我就看到一个有意思的视频 我觉得最能体现出软件测试员薪酬为什么区别这
  • 【机器学习】之Anaconda中使用的命令

    操作之前 点击上图入口 进入Prompt 示例是在base环境下 cls清屏 base C Users bubusa gt cls 1 base环境下的操作 1 列出所有虚拟环境 base C Users bubusa gt conda e
  • 浅拷贝&深拷贝

    什么是深 浅拷贝 他们跟赋值有什么关系 浅拷贝 浅拷贝是创建一个新对象 这个对象有着原始对象属性值的一份精确拷贝 如果属性是基本类型 拷贝的就是基本类型的值 如果属性是引用类型 拷贝的就是内存地址 所以如果其中一个对象改变了这个地址 就会影