java深拷贝和浅拷贝
1. 深拷贝与浅拷贝的实现(一)
最近的学习中,仔细研究了下深拷贝和浅拷贝,下面就来简单的总结下。
首先我们了解下两种 数据类型 :
1、基本类型:像Number、String、Boolean等这种为基本类型
2、复杂类型:Object和Array
接着我们分别来了解下浅拷贝和深拷贝,深拷贝和浅拷贝是只针对Object和Array这样的复杂类型的。
浅拷贝 :
可以看出,对于对象或数组类型,当我们将a赋值给b,然后更改b中的属性,a也会随着变化。也就是说a和b指向了同一块内存,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝。
深拷贝 :
刚刚我们了解了什么是浅拷贝,那么相应的,如果给b放到新的内存中,将a的各个属性都复制到新内存里,就是深拷贝。
也就是说,当b中的属性有变化的时候,a内的属性不会发生变化。
那么除了上面简单的赋值引用,还有哪些方法使用了 浅拷贝 呢?
Object.assign()
在MDN上介绍Object.assign():”Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。”
复制一个对象
可以看到,Object.assign()拷贝的只是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。所以Object.assign()只能用于浅拷贝或是合并对象。这是Object.assign()值得注意的地方。
那么下面我们就来说说复杂的 深拷贝 。
jQuery.extend()
说到深拷贝,第一想到的就是jQuery.extend()方法,下面我们简单看下jQuery.extend()的使用。
jQuery.extend( [deep ], target, object1 [, objectN ] ),其中deep为Boolean类型,如果是true,则进行深拷贝。
我们还是用上面的数据来看下extend()方法。
通过上面的对比可以看出,当使用extend()进行深拷贝的时候,对象的所有属性都添加到target中了。
我们知道了extend()可以进行深拷贝,那么extend()是如何实现深拷贝的呢?
先来看下jQuery.extend()源码
主要看下关于深拷贝的部分,取第一个参数,如果是boolean类型的,就赋值给deep,下面如果deep为true(也就是进行深拷贝),就递归调用extend(),这样就将对象的所有属性都添加到了target中实现了深拷贝。
JSON.parse()和JSON.stringify()
上面的jQuery源码是否让你眼花缭乱?有没有什么办法无脑实现深拷贝呢?JSON.parse()和JSON.stringify()给了我们一个基本的解决办法。
可以看到改变targetCopy并没有改变原始的target,继承的属性也没有丢失,因此实现了基本的深拷贝。
但是用JSON.parse()和JSON.stringify()会有一个问题。
JSON.parse()和JSON.stringify()能正确处理的对象只有Number、String、Array等能够被json表示的数据结构,因此函数这种不能被json表示的类型将不能被正确处理。
上面的例子可以看出,hello这个属性由于是函数类型,使用JSON.parse()和JSON.stringify()后丢失了。
因此JSON.parse()和JSON.stringify()还是需要谨慎使用。
2. 深拷贝、浅拷贝的理解与使用场景
通俗解释:深拷贝是内容拷贝,浅拷贝是地址拷贝
区别点:
深拷贝会创建一个新的内存空间,拷贝的值是一样的,但是内存地址不一样。
浅拷贝只是拷贝指向原来对象的地址,使原对象的引用计数+1
像NSString、NSNumber这些不能包含其他对象的叫做非容器类对象
像NSArray、NSDictionary这些可以包含其他对象的叫容器类对象
打印结果如下:
通过对比不难发现:
上面我们使用的是不可变的NSString,下面我们再使用可变的NSMutableString对比一下:
打印结果如下:
不难发现,对于NSMutableString, 无论是还是mutableCopy都会创建一个新对象,属于深拷贝
打印结果如下:
不难发现,是浅拷贝,mutableCopy是深拷贝,不过需要注意的是容器对象的成员元素都指向相同的地址
打印结果如下:
对比可见,容器对象与非容器对象类似,可变对象的复制都是深拷贝,不可变对象是浅拷贝,mutableCopy是深拷贝
需要注意的是对容器而言,元素对象始终是指针复制
正如前面所说,容器对象中的元素对象无论是还是mutableCopy都是指针复制,如何实现容器对象的完全深拷贝呢?
系统为我们实现容器对象的完全深拷贝提供了方法
3. java 中浅拷贝与深拷贝有什么区别
浅拷贝 指的是你的类本身被拷贝,而没有拷贝类本身属性中的类
深拷贝 指的是包含类本身和属性类在内的所有类的拷贝。
简单点说:
就是浅拷贝的两个对象中的属性还会指向同一个类,而深拷贝则全部单独了。也就是说深拷贝把关联关系也拷贝了。
具体例子你可以参看我的blog,里面篇文章介绍:)
4. 编程里面的深拷贝和浅拷贝各是什么意思,怎么理解这两个
1、浅拷贝:默认的拷贝就是浅拷贝。 仅仅多了个指针指向原来的空间。
2、深拷贝:自己写的拷贝,自己申请了动态内存空间,用了new 或 malloc 。不但多了指针,而且多了空间。
3、用深拷贝的话,最好用自己写的析构,记得在里面释放内存,也可以用默认析构。
4.用浅拷贝(即默认隐藏的拷贝),最好用默认析构,若用自己写的析构里面 ,记得不要释放内存,不然会造成重复释放内存而报错。
5. 彻底讲明白浅拷贝与深拷贝
数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。
1、基本数据类型的特点:直接存储在栈(stack)中的数据
2、引用数据类型的特点: 存储的是该对象在栈中引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的 。
深拷贝和浅拷贝的示意图大致如下:
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
当我们把一个对象赋值给一个新的变量时, 赋的其实是该对象的在栈中的地址,而不是堆中的数据 。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝是按位拷贝对象, 它会创建一个新对象 ,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
我们先来看两个例子,对比赋值与浅拷贝会对原对象带来哪些改变?
上面例子中,obj1是原始数据,obj2是赋值操作得到,而obj3浅拷贝得到。我们可以很清晰看到对原始数据的影响,具体请看下表:
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
注意:当object只有一层的时候,是深拷贝
修改新对象会改到原对象:
同样修改新对象会改到原对象:
关于Array的slice和concat方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
原数组的元素会按照下述规则拷贝:
可能这段话晦涩难懂,我们举个例子,将上面的例子小作修改:
原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。
这是因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。
递归方法实现深度克隆原理: 遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
该函数库也有提供 _.cloneDeep 用来做 Deep Copy。
阅读原文
看完文章留完言,还有福利拿,往下看👇👇👇
感兴趣的小伙伴可以在公号【grain先森】后台回复【190313】获取HTML5详解、CSS3详解和Vue详解及实战项目,可以转发朋友圈和你的朋友分享哦。
6. 浅复制和深复制的区别 java
浅复制和深复制的区别 java
大体上来说,深拷贝与浅拷贝的区别主要还是在于指针(或与指针)方面,浅拷贝只是简单的把源对象(这个是指广义的对象,不仅仅单指类的实例)的指针赋值给目标对象,对目标指针的操作就是对源对象的操作,所以在很多情况下,目标对象析构(或跳出其可见域)之后,源对象相关部分也就一同析构了。而深拷贝,是为目标对象重新分配空间,这样可以与源对象的操作分开。
7. 什么是深拷贝和浅拷贝
浅拷贝就是指对象复制的时候只复制一层;深拷贝是指复制对象的所有层级。
深拷贝和浅拷贝,主要是对象发生复制的时候,根据复制的层级不同来区分的。很多人在这里经常变量赋值发生混淆。对于JavaScript数组等复杂的数据类型来说,将其赋值给其它变量,其实只是复制了对象的地址给它,两个变量指向的是同一个对象,因此普通的赋值既不是深拷贝也不是浅拷贝。
深拷贝和浅拷贝需要注意的地方就是可变元素的拷贝:
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去。
而是和原对象里的可变元素指向同一个地址,所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。
8. 对象赋值、浅拷贝和深拷贝
赋值是将原对象的内存地址直接给到新对象
生成一个新的对象,新对象拥有原对象的所有属性。如果属性值的类型是基本类型,就将原属性的值拷贝过来;如果属性值是引用类型,就将原属性值的地址拷贝过来,如果原属性值发生改变时,新属性的值也会发生改变。
将原对象中的各个属性值重新分配内存地址,不论原对象的属性值是基本类型还是引用类型,原对象属性值的变化都不会影响新对象的属性值。
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了
9. java深拷贝和浅拷贝的区别
深拷贝和浅拷贝最大的区别在于浅拷贝更多时候拷贝的是地址、引用这种东西,而深拷贝则是拷贝了一个新地址的对象,具体可以参考以下网址
网页链接
10. java深拷贝和浅拷贝的区别
浅拷贝:只复制一个对象,对象内部存在的指向其他对象数组或者引用则不复制
深拷贝:对象,对象内部的引用均复制
示例:
publicstaticObject(ObjectoldObj){
Objectobj=null;
try{
//Writetheobjectouttoabytearray
ByteArrayOutputStreambos=newByteArrayOutputStream();
ObjectOutputStreamout=newObjectOutputStream(bos);
out.writeObject(oldObj);
out.flush();
out.close();
//
//aoftheobjectbackin.
ByteArrayInputStreambis=newByteArrayInputStream(bos.toByteArray());
ObjectInputStreamin=newObjectInputStream(bis);
obj=in.readObject();
}catch(IOExceptione){
e.printStackTrace();
}catch(ClassNotFoundExceptioncnfe){
cnfe.printStackTrace();
}
returnobj;
}