精华内容
下载资源
问答
  • Golang深拷贝浅拷贝
    万次阅读
    2019-05-30 23:19:37

    Golang深拷贝浅拷贝

    在了解原型设计模式之前我们需要新知道Golang的深拷贝与浅拷贝之间的区别。

    推荐大家新看看Slice 和 Map那么常见的坑:https://blog.csdn.net/weixin_40165163/article/details/90707593

    github:https://github.com/zhumengyifang/GolangDesignPatterns

    数据结构:

    //速度速值
    type Speed int
    
    //风扇转速
    type FanSpeed struct {
    	Speed Speed
    }
    
    //售价
    type Money struct {
    	Length float64
    }
    
    //内存数量以及大小
    type Memory struct {
    	Count      int
    	MemorySize []int
    }
    
    //电脑信息
    type Computer struct {
    	SystemName string              //系统名字
    	UseNumber  int                 //使用次数
    	Memory     Memory              //存储
    	Fan        map[string]FanSpeed //风扇
    	Money      Money               //售价
    }

    浅拷贝:

     接触过 Java或者C#的同学应该知道浅拷贝对于值类型的话是完全拷贝一份,而对于引用类型是拷贝其地址。也就是拷贝的对象修改引用类型的变量同样会影响到源对象。

    这里Golang同理,在上述测试类型中涉及到 Slice 和 Map的修改则会互相影响。

    测试1:

    func ComputerStart1() {
    	Pc1 := Computer{
    		SystemName: "Windows",
    		UseNumber:  1000,
    		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
    		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
    		Money:      Money{123.45},
    	}
    
        //浅拷贝
    	Pc2:=Pc1
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
        //修改切片内容以及map信息影响Pc1
        Pc2.SystemName ="MacOs"
    	Pc2.UseNumber =100
        Pc2.Memory.Count =2
        Pc2.Memory.MemorySize[0]=8
    	Pc2.Memory.MemorySize[1]=8
    	Pc2.Memory.MemorySize[2]=0
    	Pc2.Memory.MemorySize[3]=0
    	Pc2.Fan["left"]=FanSpeed{2000}
    	Pc2.Fan["right"]=FanSpeed{1500}
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    }

     输入信息:

    PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
    PcInfo Pc1:{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}, Pc2:{MacOs 100 {2 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}

    对于PC2的修改影响到了PC1的Slice 和 Map

    测试2:

    func ComputerStart2() {
    	Pc1 := Computer{
    		SystemName: "Windows",
    		UseNumber:  1000,
    		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
    		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
    		Money:      Money{123.45},
    	}
    
        //浅拷贝
    	Pc2:=Pc1
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    
    	ModifyCat(Pc2)
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    }
    
    func ModifyCat(pc Computer) {
    	fmt.Printf("PcInfo Pc1:%v\n", pc)
    	pc.SystemName ="MacOs"
    	pc.UseNumber =100
    	pc.Memory.Count =2
    	pc.Memory.MemorySize[0]=8
    	pc.Memory.MemorySize[1]=8
    	pc.Memory.MemorySize[2]=0
    	pc.Memory.MemorySize[3]=0
    	pc.Fan["left"]=FanSpeed{2000}
    	pc.Fan["right"]=FanSpeed{1500}
    	fmt.Printf("PcInfo Pc1:%v\n", pc)
    }

    输入:

    PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
    PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
    PcInfo Pc1:{MacOs 100 {2 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}
    PcInfo Pc1:{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}, Pc2:{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}

    这里在方法中修改PC2同样影响到了 PC1以及PC2,是因为在Golang中方法中传递的参数同样被拷贝了一份,他们修改的Slice 和 Map都是同一份地址。

    那么对于浅拷贝来说如何避免这种情况的发生呢?

    测试3:

    func ComputerStart2() {
    	Pc1 := Computer{
    		SystemName: "Windows",
    		UseNumber:  1000,
    		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
    		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
    		Money:      Money{123.45},
    	}
    
        //浅拷贝
    	Pc2:=Pc1
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
        //切片以及map新空间互不影响
        Pc2.SystemName ="MacOs"
    	Pc2.UseNumber =100
    	Pc2.Memory =Memory{Count: 2, MemorySize: []int{8, 8}}
    	Pc2.Fan =map[string]FanSpeed{"left": {2000}, "right": {1500}}
    	Pc2.Money =Money{1000.45}
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    }

    输出:

    PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
    PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:{MacOs 100 {2 [8 8]} map[left:{2000} right:{1500}] {1000.45}}

    既然只有Slice 和 Map会受到影响我们这里重新给定地址重新生成一个Slice和Map就可以不受影响。

    深拷贝

    对于深拷贝就比较好了解了,任何对象都会被完完整整的拷贝一份,拷贝对象与被拷贝对象不存在如何联系,也就不会互相影响。如果你需要拷贝的对象中没有引用类型,那么对于Golang而言使用浅拷贝就可以了。

    基于序列化和反序列化来实现对象的深度拷贝:

    func deepCopy(dst, src interface{}) error {
        var buf bytes.Buffer
        if err := gob.NewEncoder(&buf).Encode(src); err != nil {
            return err
        }
        return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
    }

    需要深拷贝的变量必须首字母大写才可以被拷贝

    测试1:

    func ComputerStart4() {
    	Pc1 := &Computer{
    		SystemName: "Windows",
    		UseNumber:  1000,
    		Memory:     Memory{Count: 4, MemorySize: []int{32, 32, 32, 32}},
    		Fan:        map[string]FanSpeed{"left": {2500}, "right": {2000}},
    		Money:      Money{123.45},
    	}
    
    	//深拷贝
    	Pc2:= new(Computer)
    	if err:= deepCopy(Pc2,Pc1);err!=nil{
              panic(err.Error())
    	}
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    
    	ModifyCat1(*Pc2)
    	fmt.Printf("PcInfo Pc1:%v, Pc2:%v\n", Pc1, Pc2)
    }

    输出:

    PcInfo Pc1:&{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:&{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
    PcInfo Pc1:{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}
    PcInfo Pc1:{MacOs 100 {2 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}
    PcInfo Pc1:&{Windows 1000 {4 [32 32 32 32]} map[left:{2500} right:{2000}] {123.45}}, Pc2:&{Windows 1000 {4 [8 8 0 0]} map[left:{2000} right:{1500}] {123.45}}

    可以看到PC2经过浅拷贝(参数传递)在修改Slice和Map受到影响的也只有PC2和PC2的浅拷贝对象。对于PC1没有任何影响。

    下一章原型设计模式:https://blog.csdn.net/weixin_40165163/article/details/90671135

    更多相关内容
  • 详解C++中String类模拟实现以及深拷贝浅拷贝 在C语言中/C++中,字符串是一个应用很广泛的类型,也是很基础的类型,C语言并没有直接处理字符串的操作而是采用字符指针和字符串数组进行操作,而在C++中标准库为我们...
  • 通过询问百度是深拷贝浅拷贝的问题 解决方法有两种 1 var b = JSON.parse(JSON.stringify(a)); b就是拷贝的结果,修改b不影响a。但是这种方法也有缺陷: 无法复制函数 原型链没了,对象就是object,所属的类没了。...
  • 本文将会深入的探讨一下在拷贝对象中会出现的浅拷贝深拷贝的情况。 拷贝接口 java中所有的对象都是继承自java.lang.Object。Object对象中提供了一个clone方法,来供我们对java对象进行拷贝。 protected native ...
  • 今天小编就为大家分享一篇关于Java Clone深拷贝浅拷贝的两种实现方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • 本文实例讲述了JS赋值、浅拷贝深拷贝(数组和对象的深浅拷贝)。分享给大家供大家参考,具体如下: 深拷贝浅拷贝是只针对Object和Array这样的引用数据类型的。  浅拷贝 只是拷贝了基本类型的数据,而引用类型...
  • 深拷贝浅拷贝.zip

    2020-08-11 09:20:58
    该例子代码主要实现C#的浅拷贝深拷贝深拷贝是通过复制对象和序列化对象两种方法分别实现的。各位同学各取所需。
  • 关于赋值 赋值,即直接将变量相等,=,此时对于这两个变量来讲...浅拷贝的理解就是两个变量的内存地址是不一样的,但对于不同类型的变量是有区别的: 基本数据类型: 一个变量的更改不会引起另一个变量的更改 引用类

    关于赋值

    赋值,即直接将变量相等,=,此时对于这两个变量来讲,指向了同个内存地址,所以,内存地址指向的内容一旦改变就会同步改变

    关于 js 变量类型

    基本数据类型: Number, Boolean, String, Symbol, null, undefined

    引用类型: Object,诸如 array, function, object

    关于浅拷贝

    浅拷贝的理解就是两个变量的内存地址是不一样的,但对于不同类型的变量是有区别的:

    基本数据类型: 一个变量的更改不会引起另一个变量的更改

    引用类型:一个变量的内容更改会引起另一个变量的更改

    const a = [{aa: 1}]
    
    var b = a.map(item => { // 因为此时item是一个对象,所以是直接更改item.aa也会影响a
      item.aa = 2
      return item
    })
    console.log(b) // b: {aa: 2}
    console.log(a) // a: {aa: 2}
    

    实现一个浅拷贝

    Object.assign

    利用 Object.assign 能进行浅拷贝,仅拷贝一级 key 的 value 值,如果你对原值进行二级以上的 value 值的拷贝,则两个变量都会同时受到影响

    展开运算符

    var a = {aa: 1}
    var b = {...a}
    

    深拷贝概念

    浅拷贝的理解就是两个变量的内存地址是不一样的,一般更加应用于引用类型

    引用类型:一个变量的内容更改不会引起另一个变量的更改,因为内容对应的内存地址不一样,所以取到的值也就不一样

    实现一个深拷贝

    简单版

    var obj = {a: 1}
    
    function clone(origin) {
      return JSON.parse(JSON.stringify(origin))
    }
    
    var copy = clone(obj)
    
    copy.a = 2
    
    console.log(obj)
    console.log(copy)
    

    打印出来的结果是:

    20200802104031

    可以看到两者不受影响了

    上述方法存在问题,比如

    原对象有函数,是不能被序列化的

    原对象有 value 是 undefined 的,也是不能被序列化的

    原对象有正则的,不能被序列化

    比如:

    var obj = {
                a: 1,
                b: undefined,
                say: function() {
                 console.log(this.a)
                }
              }
    
    function clone(origin) {
      return JSON.parse(JSON.stringify(origin))
    }
    
    var copy = clone(obj)
    
    copy.a = 2
    
    console.log(obj)
    console.log(copy)
    

    20200802104453

    可见:第二个对象丢失了 b,say 等 key

    进阶版+1

    利用递归所有的 key,手动赋值给新对象

    var obj = {
    	a: 1,
    	b: undefined,
    	c: { cc: 1 },
    	say: function () {
    		console.log(this.a);
    	},
    };
    
    function clone(origin) {
    	let newValue = new origin.constructor();
    	for (let key in origin) {
    		if (typeof origin[key] === 'object') {
    			debugger;
    			newValue[key] = clone(origin[key]);
    		} else {
    			newValue[key] = origin[key];
    		}
    	}
    	return newValue;
    }
    
    var copy = clone(obj);
    
    copy.c.cc = 2;
    
    console.log(obj);
    console.log(copy);
    

    20200802110803

    进阶版+2

    上述存在以下问题:

    边界条件判断

    循环引用

    循环引用见以下例子:

    var obj = {
    	a: 1,
    };
    
    var obj2 = {
    	obj: obj,
    };
    
    obj.obj2 = obj2;
    
    function clone(origin) {
    	let newValue = new origin.constructor();
    	for (let key in origin) {
    		if (typeof origin[key] === 'object') {
    			newValue[key] = clone(origin[key]);
    		} else {
    			newValue[key] = origin[key];
    		}
    	}
    	return newValue;
    }
    
    var copy = clone(obj);
    
    console.log(obj);
    console.log(copy);
    

    20200802111136

    js 达到最大调用栈了,死循环了

    解决思路:

    利用缓存,可以将已经存在的引用直接返回。同时,可以利用 weakmap 解决内存泄露的问题

    var obj = {
    	a: 1,
    };
    
    var obj2 = {
    	obj: obj,
    };
    
    obj.obj2 = obj2;
    
    // stack为缓存,是一个weapMap实例
    function clone(origin, stack = new WeakMap()) {
    	let newValue = new origin.constructor();
    	if (stack.has(origin)) { // 如果缓存已存在,直接返回
    		return stack.get(origin);
    	}
    	stack.set(origin, newValue); // 缓存中不存在,则新增一条记录
    	for (let key in origin) {
    		if (typeof origin[key] === 'object') {
    			newValue[key] = clone(origin[key], stack);
    		} else {
    			newValue[key] = origin[key];
    		}
    	}
    	return newValue;
    }
    
    var copy = clone(obj);
    
    console.log(obj);
    console.log(copy);
    

    20200802165157

    最后,完整代码:

    var obj = {
    	a: 1,
    };
    
    var obj2 = {
    	obj: obj,
    };
    
    obj.obj2 = obj2;
    
    function clone(origin, stack = new WeakMap()) {
    	if (!origin) {
    		return origin;
    	}
    	if (obj instanceof Date) {
    		// 增加Date类型的copy
    		return new Date(origin);
    	}
    	if (obj instanceof RegExp) {
    		// 增加正则类型的copy
    		return new RegExp(origin);
    	}
    	let newValue = new origin.constructor();
    	if (stack.has(origin)) {
    		return stack.get(origin);
    	}
    	stack.set(origin, newValue);
    	for (let key in origin) {
    		if (typeof origin[key] === 'object') {
    			newValue[key] = clone(origin[key], stack);
    		} else {
    			newValue[key] = origin[key];
    		}
    	}
    	return newValue;
    }
    
    var copy = clone(obj);
    
    console.log(obj);
    console.log(copy);
    

    lodash cloneDeep 关键源码分析

    clone 用到的关键的 baseClone 函数

    /**
     * The base implementation of `clone` and `cloneDeep` which tracks
     * traversed objects.
     *
     * @private
     * @param {*} value The value to clone.
     * @param {number} bitmask The bitmask flags.
     *  1 - Deep clone
     *  2 - Flatten inherited properties
     *  4 - Clone symbols
     * @param {Function} [customizer] The function to customize cloning.
     * @param {string} [key] The key of `value`.
     * @param {Object} [object] The parent object of `value`.
     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
     * @returns {*} Returns the cloned value.
     */
    function baseClone(value, bitmask, customizer, key, object, stack) {
    	// Check for circular references and return its corresponding clone.
    	// 这里同样用到缓存
    	stack || (stack = new Stack());
    	const stacked = stack.get(value);
    	if (stacked) {
    		return stacked;
    	}
    	stack.set(value, result);
    
    	const props = isArr ? undefined : keysFunc(value);
    	// 开始遍历递归
    	arrayEach(props || value, (subValue, key) => {
    		if (props) {
    			key = subValue;
    			subValue = value[key];
    		}
    		// Recursively populate clone (susceptible to call stack limits).
    		assignValue(
    			result,
    			key,
    			baseClone(subValue, bitmask, customizer, key, value, stack)
    		);
    	});
    
    	return result;
    }
    
    

    更多精彩文章可以看我的博客,如有错误,欢迎指正,共同进步

    展开全文
  • C++之深拷贝浅拷贝

    2015-09-09 19:06:07
    通过简短的代码和图片来说明C++中深拷贝和浅拷贝的区别和概念。
  • 变量-引用-对象(可变对象,不可变对象)-切片-拷贝(浅拷贝深拷贝) 【变量-对象-引用】 在Python中一切都是对象,比如说:3, 3.14, ‘Hello’, [1,2,3,4],{‘a’:1}…… 甚至连type其本身都是对象,type对象 Python...
  • C/C++ 浅拷贝深拷贝的实例详解 深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。 浅拷贝就是对内存地址...
  • 深拷贝浅拷贝都是对对象进行的拷贝复制。而他们的不同就在于:一个对象中可能会有值类型的字段,也可能会有引用类型的字段。浅拷贝直接拷贝值、引用类型的指针。而深拷贝则是重新构造字段指针所指向的对象,并没有...
  • java.lang.Cloneable和java.io.Serializable一样属于标记型接口,没有定义任何方法和属性。 一个类想要使用克隆方法 重写clone()方法,因为Object的clone()的修饰符是protected; @HotSpotIntrinsicCandidate ...
  • python的深拷贝浅拷贝 引言 前两天在用python写A*算法的时候,被python的深拷贝浅拷贝恶搞了一番,实际上还是因为没搞清楚哪些是深拷贝,哪些是浅拷贝,现特意写一篇小结,加深理解。 什么是浅拷贝 所谓浅拷贝,...
  • Map拷贝 关于对象深拷贝 浅拷贝的问题 概念 浅复制:则是只复制对象的引用,两个引用仍然指向同一个对象,在内存中占用同一块内存。被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然...

    1、概念

    浅复制:则是只复制对象的引用,两个引用仍然指向同一个对象,在内存中占用同一块内存。被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

    深复制:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

    2、浅拷贝-复制对象引用

    Map<String,String> map = new HashMap<>();
    Map<String,String> map1 = map;
    map.put("a","A");
    System.out.println(map);
    System.out.println(map1);
    

    输出结果

    {a=A}
    {a=A}
    

    分析

    map1复制的是对象map的引用,map1和map指向的是同一个对象。这里我们可以通过System.identityHashCode(Object obj)来返回对象内存地址转化后的hashcode,之所以用不直接用map.hashCode(),是因为HashMap的实现类重写了hashCode方法,返回的值不子再是对象内存地址转化的hashcode了。如下,通过输出结果可以看出map和map1指向同一内存地址。

    Map<String,String> map = new HashMap<>();
    Map<String,String> map1 = map;
    map.put("a","A");
    System.out.println(map);
    System.out.println(map1);
    System.out.println(System.identityHashCode(map));
    System.out.println(System.identityHashCode(map1));
    

    输出结果

    {a=A}
    {a=A}
    918221580
    918221580
    

    3、浅拷贝-map.putAll();

    Map<String,String> map = new HashMap<>();
    Map<String,String> map1 = new HashMap<>();
    map.put("a","A");
    map1.putAll(map);
    System.out.println(map);
    System.out.println(map1);
    System.out.println(System.identityHashCode(map));
    System.out.println(System.identityHashCode(map1));
    

    输出结果

    {a=A}
    {a=A}
    918221580
    2055281021
    

    分析

    这里map1通过new初始化,并将map通过putAll()方法将map的key和value复制到map1,所以这里输出时虽然map和map1的内容都是{a=A},但map和map1的内存地址对应的hashCode却不相同,二者在内存中不是同一对象。putAll通过循环map的entrySet调用map1的put()方法将key和value赋值给map1。经过putAll,map1只是对map的key和value分别进行了对象浅拷贝。如下,通过输出结果发现map和map1中的key和value的内存地址都是一致的。

    Map<String,String> map = new HashMap<>();
    Map<String, String> map1 = new HashMap<>();
    map.put("a","A");
    map1.putAll(map);
    map.keySet().stream().forEach(x-> System.out.println("map的key的内存地址:"+System.identityHashCode(x)));
    map1.keySet().stream().forEach(x-> System.out.println("map1的key的内存地址:"+System.identityHashCode(x)));
    
    map.values().stream().forEach(v-> System.out.println("map的值的内存地址:"+System.identityHashCode(v)));
    map1.values().stream().forEach(v-> System.out.println("map1的值的内存地址:"+System.identityHashCode(v)));
    

    输出结果

    map的key的内存地址:159413332
    map1的key的内存地址:159413332
    map的值的内存地址:20132171
    map1的值的内存地址:20132171
    

    如果我们这里对map1执行put(“a”,“C”),我们会发现这里只是将键“a”指向的对象的内存地址指向了“C”的内存地址,原来“A”的内存地址并没有发生变化。

    Map<String,String> map = new HashMap<>();
    Map<String, String> map1 = new HashMap<>();
    map.put("a","A");
    map1.putAll(map);
    map.keySet().stream().forEach(x-> System.out.println("map的key的内存地址:"+System.identityHashCode(x)));
    map1.keySet().stream().forEach(x-> System.out.println("map1的key的内存地址:"+System.identityHashCode(x)));
    map1.put("a","C");
    System.out.println("修改map1键a的value为C后,key的内存地址没有变化,但value指向的内存地址发生了变化:");
    map.keySet().stream().forEach(x-> System.out.println("map的key的内存地址:"+System.identityHashCode(x)));
    map1.keySet().stream().forEach(x-> System.out.println("map1的key的内存地址:"+System.identityHashCode(x)));
    map.values().stream().forEach(x-> System.out.println("map的value的内存地址:"+System.identityHashCode(x)));
    map1.values().stream().forEach(v-> System.out.println("map1的value的内存地址:"+System.identityHashCode(v)));
    

    输出结果

    map的key的内存地址:159413332
    map1的key的内存地址:159413332
    修改map1键a的value为C后,key的内存地址没有变化,但value指向的内存地址发生了变化:
    map的key的内存地址:159413332
    map1的key的内存地址:159413332
    map的value的内存地址:2094548358
    map1的value的内存地址:455896770
    

    4、如何实现深拷贝

    如果要拷贝的对象实现了Serializable接口,我们可以通过对象流的输出(out)输入(input)实现对象的深拷贝。

    public class CloneUtils {
        @SuppressWarnings("unchecked")
        public static <T extends Serializable> T clone(T obj) {
            T clonedObj = null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(obj);
                oos.close();
    
                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bais);
                clonedObj = (T) ois.readObject();
                ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return clonedObj;
        }
    }
    

    通过CloneUtils工具类实现深拷贝

    HashMap<String, String> map = new HashMap<>();
    map.put("a", "A");
    HashMap<String, String> map1 = CloneUtils.clone(map);
    map.keySet().stream().forEach(x -> System.out.println("map的key的内存地址:" + System.identityHashCode(x)));
    map1.keySet().stream().forEach(x -> System.out.println("map1的key的内存地址:" + System.identityHashCode(x)));
    map.values().stream().forEach(x -> System.out.println("map的value的内存地址:" + System.identityHashCode(x)));
    map1.values().stream().forEach(v -> System.out.println("map1的value的内存地址:" + System.identityHashCode(v)));
    

    输出结果

    map的key的内存地址:1392838282
    map1的key的内存地址:1706234378
    map的value的内存地址:804564176
    map1的value的内存地址:1342443276
    

    分析

    通过对象流的输入输出生成的map1是新的对象,而且map1的key和value对应对象的内存地址也跟map的不一样,这样才算实现了深拷贝。分别对他们key和value进行修改不会受到影响。

    注意:文章中举例的key和value都是String,如果大家感觉比较特殊,可以自定义一个对象Person,并实现Serializable进行测试。

    展开全文
  • 本文实例讲述了JavaScript深拷贝浅拷贝概念与用法。分享给大家供大家参考,具体如下: js中的浅拷贝深拷贝,只是针对复杂数据类型(Objcet,Array)的复制问题。简单来讲浅拷贝深拷贝都可以实现在原有对象的基础...
  • Java学习之深拷贝浅拷贝及对象拷贝的两种思路

    万次阅读 多人点赞 2017-12-18 22:22:01
    Java语言中的深拷贝浅拷贝以及对象拷贝

    I. Java之Clone

    0. 背景

    对象拷贝,是一个非常基础的内容了,为什么会单独的把这个领出来讲解,主要是先前遇到了一个非常有意思的场景

    有一个任务,需要解析类xml标记语言,然后生成document对象,之后将会有一系列针对document对象的操作

    通过实际的测试,发现生成Document对象是比较耗时的一个操作,再加上这个任务场景中,需要解析的xml文档是固定的几个,那么一个可以优化的思路就是能不能缓存住创建后的Document对象,在实际使用的时候clone一份出来

    1. 内容说明

    看到了上面的应用背景,自然而言的就会想到深拷贝了,本篇博文则主要内容如下

    • 介绍下两种拷贝方式的区别
    • 深拷贝的辅助工具类
    • 如何自定义实现对象拷贝

    II. 深拷贝和浅拷贝

    0. 定义说明

    深拷贝

    相当于创建了一个新的对象,只是这个对象的所有内容,都和被拷贝的对象一模一样而已,即两者的修改是隔离的,相互之间没有影响

    浅拷贝

    也是创建了一个对象,但是这个对象的某些内容(比如A)依然是被拷贝对象的,即通过这两个对象中任意一个修改A,两个对象的A都会受到影响

    看到上面两个简单的说明,那么问题来了

    • 浅拷贝中,是所有的内容公用呢?还是某些内容公用?
    • 从隔离来将,都不希望出现浅拷贝这种方式了,太容易出错了,那么两种拷贝方式的应用场景是怎样的?

    1. 浅拷贝

    一般来说,浅拷贝方式需要实现Cloneable接口,下面结合一个实例,来看下浅拷贝中哪些是独立的,哪些是公用的

    @Data
    public class ShallowClone implements Cloneable {
    
        private String name;
    
        private int age;
    
        private List<String> books;
    
    
        public ShallowClone clone() {
            ShallowClone clone = null;
            try {
                clone = (ShallowClone) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return clone;
        }
    
    
        public static void main(String[] args) {
            ShallowClone shallowClone = new ShallowClone();
            shallowClone.setName("SourceName");
            shallowClone.setAge(28);
            List<String> list = new ArrayList<>();
            list.add("java");
            list.add("c++");
            shallowClone.setBooks(list);
    
    
            ShallowClone cloneObj = shallowClone.clone();
    
    
            // 判断两个对象是否为同一个对象(即是否是新创建了一个实例)
            System.out.println(shallowClone == cloneObj);
    
            // 修改一个对象的内容是否会影响另一个对象
            shallowClone.setName("newName");
            shallowClone.setAge(20);
            shallowClone.getBooks().add("javascript");
            System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString());
    
            shallowClone.setBooks(Arrays.asList("hello"));
            System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString());
        }
    }

    输出结果:

    false
    source: ShallowClone(name=newName, age=20, books=[java, c++, javascript])
    clone:ShallowClone(name=SourceName, age=28, books=[java, c++, javascript])
    source: ShallowClone(name=newName, age=20, books=[hello])
    clone:ShallowClone(name=SourceName, age=28, books=[java, c++, javascript])

    结果分析:

    • 拷贝后获取的是一个独立的对象,和原对象拥有不同的内存地址
    • 基本元素类型,两者是隔离的(虽然上面只给出了int,String)
      • 基本元素类型包括:
      • int, Integer, long, Long, char, Charset, byte,Byte, boolean, Boolean, float,Float, double, Double, String
    • 非基本数据类型(如基本容器,其他对象等),只是拷贝了一份引用出去了,实际指向的依然是同一份

    其实,浅拷贝有个非常简单的理解方式:

    浅拷贝的整个过程就是,创建一个新的对象,然后新对象的每个值都是由原对象的值,通过 = 进行赋值

    这个怎么理解呢?

    上面的流程拆解就是:

    - Object clone = new Object();
    - clone.a = source.a
    - clone.b = source.b
    - ...

    那么=赋值有什么特点呢?

    基本数据类型是值赋值;非基本的就是引用赋值

    2. 深拷贝

    深拷贝,就是要创建一个全新的对象,新的对象内部所有的成员也都是全新的,只是初始化的值已经由被拷贝的对象确定了而已

    那么上面的实例改成深拷贝应该是怎样的呢?

    可以加上这么一个方法

    public ShallowClone deepClone() {
        ShallowClone clone = new ShallowClone();
        clone.name = this.name;
        clone.age = this.age;
        if (this.books != null) {
            clone.books = new ArrayList<>(this.books);
        }
        return clone;
    }
    
    
    // 简单改一下测试case
    public static void main(String[] args) {
        ShallowClone shallowClone = new ShallowClone();
        shallowClone.setName("SourceName");
        shallowClone.setAge(new Integer(1280));
        List<String> list = new ArrayList<>();
        list.add("java");
        list.add("c++");
        shallowClone.setBooks(list);
    
    
        ShallowClone cloneObj = shallowClone.deepClone();
    
    
        // 判断两个对象是否为同一个对象(即是否是新创建了一个实例)
        System.out.println(shallowClone == cloneObj);
    
        // 修改一个对象的内容是否会影响另一个对象
        shallowClone.setName("newName");
        shallowClone.setAge(2000);
        shallowClone.getBooks().add("javascript");
        System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString());
    
    
        shallowClone.setBooks(Arrays.asList("hello"));
        System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString());
    }

    输出结果为:

    false
    source: ShallowClone(name=newName, age=2000, books=[java, c++, javascript])
    clone:ShallowClone(name=SourceName, age=1280, books=[java, c++])
    source: ShallowClone(name=newName, age=2000, books=[hello])
    clone:ShallowClone(name=SourceName, age=1280, books=[java, c++])

    结果分析:

    • 深拷贝独立的对象
    • 拷贝后对象的内容,与原对象的内容完全没关系,都是独立的

    简单来说,深拷贝是需要自己来实现的,对于基本类型可以直接赋值,而对于对象、容器、数组来讲,需要创建一个新的出来,然后重新赋值

    3. 应用场景区分

    深拷贝的用途我们很容易可以想见,某个复杂对象创建比较消耗资源的时候,就可以缓存一个蓝本,后续的操作都是针对深clone后的对象,这样就不会出现混乱的情况了

    那么浅拷贝呢?感觉留着是一个坑,一个人修改了这个对象的值,结果发现对另一个人造成了影响,真不是坑爹么?

    假设又这么一个通知对象长下面这样

    private String notifyUser;
    
    // xxx
    
    private List<String> notifyRules;

    我们现在随机挑选了一千个人,同时发送通知消息,所以需要创建一千个上面的对象,这些对象中呢,除了notifyUser不同,其他的都一样

    在发送之前,突然发现要临时新增一条通知信息,如果是浅拷贝的话,只用在任意一个通知对象的notifyRules中添加一调消息,那么这一千个对象的通知消息都会变成最新的了;而如果你是用深拷贝,那么苦逼的得遍历这一千个对象,每个都加一条消息了


    III. 对象拷贝工具

    上面说到,浅拷贝,需要实现Clonebale接口,深拷贝一般需要自己来实现,那么我现在拿到一个对象A,它自己没有提供深拷贝接口,我们除了主动一条一条的帮它实现之外,有什么辅助工具可用么?

    对象拷贝区别与clone,它可以支持两个不同对象之间实现内容拷贝

    Apache的两个版本:(反射机制)

    org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)
    
    
    org.apache.commons.beanutils.BeanUtils#cloneBean

    Spring版本:(反射机制)

    org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties)

    cglib版本:(使用动态代理,效率高)

    net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)

    从上面的几个有名的工具类来看,提供了两种使用者姿势,一个是反射,一个是动态代理,下面分别来看两种思路

    1. 借助反射实现对象拷贝

    通过反射的方式实现对象拷贝的思路还是比较清晰的,先通过反射获取对象的所有属性,然后修改可访问级别,然后赋值;再获取继承的父类的属性,同样利用反射进行赋值

    上面的几个开源工具,内部实现封装得比较好,所以直接贴源码可能不太容易一眼就能看出反射方式的原理,所以简单的实现了一个, 仅提供思路

    public static void copy(Object source, Object dest) throws Exception {
        Class destClz = dest.getClass();
    
        // 获取目标的所有成员
        Field[] destFields = destClz.getDeclaredFields();
        Object value;
        for (Field field : destFields) { // 遍历所有的成员,并赋值
            // 获取value值
            value = getVal(field.getName(), source);
    
            field.setAccessible(true);
            field.set(dest, value);
        }
    }
    
    
    private static Object getVal(String name, Object obj) throws Exception {
        try {
            // 优先获取obj中同名的成员变量
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            return field.get(obj);
        } catch (NoSuchFieldException e) {
            // 表示没有同名的变量
        }
    
        // 获取对应的 getXxx() 或者 isXxx() 方法
        name = name.substring(0, 1).toUpperCase() + name.substring(1);
        String methodName = "get" + name;
        String methodName2 = "is" + name;
        Method[] methods = obj.getClass().getMethods();
        for (Method method : methods) {
            // 只获取无参的方法
            if (method.getParameterCount() > 0) {
                continue;
            }
    
            if (method.getName().equals(methodName)
                    || method.getName().equals(methodName2)) {
                return method.invoke(obj);
            }
        }
    
        return null;
    }

    上面的实现步骤还是非常清晰的,首先是找同名的属性,然后利用反射获取对应的值

    Field field = obj.getClass().getDeclaredField(name);
    field.setAccessible(true);
    return field.get(obj);

    如果找不到,则找getXXX, isXXX来获取

    2. 代理的方式实现对象拷贝

    Cglib的BeanCopier就是通过代理的方式实现拷贝,性能优于反射的方式,特别是在大量的数据拷贝时,比较明显

    代理,我们知道可以区分为静态代理和动态代理,简单来讲就是你要操作对象A,但是你不直接去操作A,而是找一个中转porxyA, 让它来帮你操作对象A

    那么这种技术是如何使用在对象拷贝的呢?

    我们知道,效率最高的对象拷贝方式就是Getter/Setter方法了,前面说的代理的含义指我们不直接操作,而是找个中间商来赚差价,那么方案就出来了

    将原SourceA拷贝到目标DestB

    • 创建一个代理 copyProxy
    • 在代理中,依次调用 SourceA的get方法获取属性值,然后调用DestB的set方法进行赋值

    实际上BeanCopier的思路大致如上,具体的方案当然就不太一样了, 简单看了一下实现逻辑,挺有意思的一块,先留个坑,后面单独开个博文补上

    说明

    从实现原理和通过简单的测试,发现BeanCopier是扫描原对象的getXXX方法,然后赋值给同名的 setXXX 方法,也就是说,如果这个对象中某个属性没有get/set方法,那么就无法赋值成功了


    IV. 小结

    1. 深拷贝和浅拷贝

    深拷贝

    相当于创建了一个新的对象,只是这个对象的所有内容,都和被拷贝的对象一模一样而已,即两者的修改是隔离的,相互之间没有影响
    - 完全独立

    浅拷贝

    也是创建了一个对象,但是这个对象的某些内容(比如A)依然是被拷贝对象的,即通过这两个对象中任意一个修改A,两个对象的A都会受到影响

    • 等同与新创建一个对象,然后使用=,将原对象的属性赋值给新对象的属性
    • 需要实现Cloneable接口

    2. 对象拷贝的两种方法

    通过反射方式实现对象拷贝

    主要原理就是通过反射获取所有的属性,然后反射更改属性的内容

    通过代理实现对象拷贝

    将原SourceA拷贝到目标DestB

    创建一个代理 copyProxy
    在代理中,依次调用 SourceA的get方法获取属性值,然后调用DestB的set方法进行赋值

    V. 其他

    声明

    尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正

    扫描关注,java分享

    QrCode

    展开全文
  • 深拷贝浅拷贝

    2022-04-09 22:15:43
    深拷贝浅拷贝什么是浅拷贝,深拷贝以及和他们之间的区别深拷贝实现浅拷贝实现 什么是浅拷贝,深拷贝以及和他们之间的区别 1. 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本...
  • 本篇文章主要介绍了Python浅拷贝深拷贝及引用机制,详细的介绍了浅拷贝深拷贝的介绍和区别,有兴趣的可以了解一下。
  • OpenCV中cv::Mat的深拷贝 浅拷贝问题

    千次阅读 多人点赞 2018-12-18 17:21:34
    什么是深拷贝?什么又是浅拷贝深拷贝:  分配新内存的同时拷贝数据!当被赋值的容器被修改时,原始容器数据不会改变。 浅拷贝:  仅拷贝数据!当被赋值容器修改时,原始容器数据也会做同样改变。(感觉和C++中...
  • 解决vue项目中遇到的深拷贝浅拷贝问题 遇到的问题:使用element-ui修改表单文本框的数据时,无论确定还是取消,页面数据都在同步修改 原因:复杂类型的赋值是浅拷贝,直接赋值 会导致双向修改 解决的方法:将浅拷贝...
  • 详细讲解js中的深拷贝浅拷贝

    千次阅读 多人点赞 2021-09-05 20:53:02
    深拷贝 JSON.parse(JSON.stringify(obj))深拷贝已有对象 JSON.stingify(obj)将js中的对象转换成JSON字符串 let jack = { name: 'jack' } console.log(jack) console.log(JSON.stringify(jack)) 它们在格式上有...
  • 本文首先介绍ES6中常用的三种变量声明方式,然后讨论了JavaScript按值传递的特性以及多种的赋值方式,最后介绍了复合类型拷贝的技巧。在JavaScript中,基本的变量声明可以用var方式;JavaScript允许省略var,直接对...
  • 深拷贝 浅拷贝 内存零拷贝

    千次阅读 2017-02-27 21:48:32
    深拷贝浅拷贝探析 内存零拷贝  1.深拷贝   深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一...
  • STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素在另行拷贝一份放入到容器中,而不是将原数据直接放入到容器...
  • 深拷贝浅拷贝一些例子
  • 面试题:深拷贝浅拷贝(超级详细,有内存图)

    万次阅读 多人点赞 2019-08-07 13:07:34
    深拷贝浅拷贝是经常在面试中会出现的,主要考察你对基本类型和引用类型的理解深度。我在无数次的面试中,应聘者还没有一个人能把这个问题回答情况,包括很多机构的培训老师。这篇文章会让你把基本类型和引用类型...
  • 智能指针&&深拷贝浅拷贝

    千次阅读 2020-03-23 00:53:10
    1 CVTE 21届实习生 软件类C/C++面经 作者:没有牛气的牛客 ...order=3&pos=2&page=1 来源:牛客网 1,自我介绍 ...3,智能指针(大概介绍了原理,没...4,c++的拷贝方式(一开始没听清楚,答不出来,后来才问...
  • 2.深拷贝浅拷贝 浅拷贝: 例如: let a=[0,1,2,3,4], b=a; console.log(a===b);(true) a[0]=1; console.log(a,b);(打印的值一样) 详解: 因为基本数据类型number,string,boolean,null...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 105,411
精华内容 42,164
关键字:

深拷贝浅拷贝

友情链接: BPSK.rar