深度拷贝 java_Java深度拷贝方式和性能对比
前言
Java的深度拷贝大致分为克隆(实现Java的Clone接口)和序列化(实现Java的Serializable接口)两种,但是基于不同的序列化方式,有可以延伸出几种方式。下面分析一下每种的注意事项和性能对比【当前电脑为4核16G,只是当前使用main方法单线程测试】。
一、拷贝和深浅拷贝
可以使用Java native方法提供的Clone方式进行对象的拷贝,其性能是最高的,甚至高过new 关键字。使用new关键字创建对象,如果是第一次创建则会经历类加载机制的双亲委派(加载、验证、准备、解析、初始化)。即使非第一次创建也会经历(常量池判断,内存分配,值初始化,init方法调用,栈中对象的引用等)等过程。
我们需要继承自Clone接口,重写Object的clone方法。如下:
public class DeepCopyEntity implements Cloneable {
@Override
protected DeepCopyEntity clone() {
try {
return (DeepCopyEntity)super.clone();
} catch (CloneNotSupportedException e) {
log.info("没有实现克隆接口");
return null;
}
}
}
但是我们在使用的时候,需要每个对象都编写这样的代码。可以优化为继承自类似下面的 CloneSupport 类(前体是没有继承其他的类):
public class CloneSupport implements Cloneable {
@SuppressWarnings("unchecked")
@Override
public T clone() {
try {
return (T) super.clone();
} catch (CloneNotSupportedException e) {
throw new CloneRuntimeException(e);
}
}
}
但是即使是克隆之后的对象也是浅拷贝。即对象的属性如果是非基本数据类型和String的情况下,新老对象的对象属性的内存地址任然相同,则任何一个对象改变其值之后,另一个对象的值也就是改变了,这很多时候可能是我们不想要的。那么需要进行深度的拷贝。则需要其属性对象的类也继承自Clone接口,并且重新clone方法。如下(是我项目中使用的):
public class PurOrderSkuBO implements Serializable, Cloneable {
@Override
public PurOrderSkuBO clone() {
try {
final PurOrderSkuBO clone = (PurOrderSkuBO) super.clone();
clone.purOrderSkuDTO = purOrderSkuDTO.clone();
clone.productList = productList.stream().map(PurOrderItemBO::clone).collect(Collectors.toList());
return clone;
} catch (CloneNotSupportedException e) {
return new PurOrderSkuBO();
}
}
private PurOrderSkuDTO purOrderSkuDTO;
private List productList;
}
public class PurOrderSkuDTO extends CloneSupport {
}
二、序列化
另一种实现深度拷贝的方式就是序列化,无论是Jdk的序列化还是其他方式的序列化都需要实现自 java.io.Serializable接口,并且设置自己的serialVersionUID,并且保证项目中不能有相同的值(很多开发的时候,基于原来的类copy过来后需要进行修改),如下:
public class DeepCopyEntity implements Cloneable, Serializable {
private static final long serialVersionUID = 6172279441386879379L;
}
三、深度拷贝的方式
1、new关键字
实现对象的深度拷贝,就是对象的每一层属性的内存地址都不相同,那么基于new 对象,再每一层设置new的属性对象。也是可以实现的,或者基于反射的方式,并且性能也是比较高的。需要注意jdk 6及之前的反射性能比较差。
优点:性能高,缺点:就是每个对象都需要new,并且每一层都需要用setter等进行赋值【硬编码】。
2、Clone
优点:性能高,缺点:所有层级只要有属性对象就需要实现Clone,并且重写clone方法。如果对象有七八层,其中每一层的每一个地方没有注意到就可能非深拷贝。
3、jdk序列化
jdk序列化只需要基于ObjectOutputStream将原对象流写出去(写入本地磁盘),再基于ObjectInputStream将对象流读回来即可。如下:
/**
* 深层拷贝 - 需要类继承序列化接口
* @param 对象类型
* @param obj 原对象
* @return 深度拷贝的对象
* @throws Exception
* @see java.io.Closeable
* @see AutoCloseable 不用进行关闭
*/
@SuppressWarnings("unchecked")
public static T copyImplSerializable(T obj) throws Exception {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
Object o = null;
//如果子类没有继承该接口,这一步会报错
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
o = ois.readObject();
return (T) o;
} catch (Exception e) {
throw new Exception("对象中包含没有继承序列化的对象");
}
}
优点:不需要像克隆和new一样单独开发,缺点:性能比较差
4、kyro序列化
kyro需要单独引入maven依赖,如:
com.esotericsoftware
kryo
5.0.0-RC9
使用时需要创建 Kryo对象【 Kryo kryo = new Kryo(); 】,只是该对象是非线程安全的,所有如果在项目中使用时,最好放到ThreadLocal中进行创建。使用就比较简单了:
public static T copyByKryo(T source){
return kryo.copy(source);
}
优点:性能较高, 缺点:需要单独引入maven,性能比new 和clone的低一点
5、Json序列化
项目上使用Json 进行 redis、rpc调用(如 Spring Cloud Feign) 进行序列化和反序列化是比较常用的,但是如果仅仅是本地深度拷贝,则使用该方式性能是最差的。可以在下面进行比较,各种json框架的序列化方式都差不多。
四、性能对比
创建一个50个字段的对象,并使用不同的深度拷贝方式,创建对象N多遍。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeepCopyEntity implements Cloneable, Serializable {
/**
* 序列化标识
*/
private static final long serialVersionUID = 6172279441386879379L;
@Override
protected DeepCopyEntity clone() {
try {
return (DeepCopyEntity)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
private String id;
private String field1;
private String field2;
private String field3;
private String field4;
private String field5;
private String field6;
private String field7;
private String field8;
private String field9;
private String field10;
private String field11;
private String field12;
private String field13;
private String field14;
private String field15;
private String field16;
private String field17;
private String field18;
private String field19;
private String field20;
private String field21;
private String field22;
private String field23;
private String field24;
private String field25;
private String field26;
private String field27;
private String field28;
private String field29;
private String field30;
private String field31;
private String field32;
private String field33;
private String field34;
private String field35;
private String field36;
private String field37;
private String field38;
private String field39;
private String field40;
private String field41;
private String field42;
private String field43;
private String field44;
private String field45;
private String field46;
private String field47;
private String field48;
private String field49;
private String field50;
}
package com.kevin.deepcopy;
import com.esotericsoftware.kryo.Kryo;
import net.sf.json.JSONObject;
import org.springframework.util.StopWatch;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 深度拷贝类型 循环次数[1000] 循环次数[10000] 循环次数[1000000]
* new 5 ms 14 ms 133 ms
*
* Cloneable: < 1 ms 7 ms 88 ms
*
* Jdk序列化: 272 ms 1589 ms 66190 ms
*
* Kryo序列化: 95 ms 123 ms 2438 ms
*
* Json序列化: 1203 ms 3746 ms 163512 ms
*
* 总结: 1)、序列化性能 Clone > new > Kryo序列化 > Jdk序列化 > Json(各种Json类似)序列化
* 2)、Clone深拷贝性能最高,但是如果属性中有特定的对象字段,则需要自己编写代码
* 3)、new 性能仅次于Clone,因为需要执行Jvm过程(常量池判断,内存分配,值初始化,init方法调用,栈中对象的引用等),并且主要是每个对象需要单独编写代码,当然也不建议使用反射
* 4)、kryo 性能较高,并且不需要单独的开发, 若对性能不是特别高,可以考虑使用.(kryo是非线程安全的,项目中使用时可以放入ThreadLocal中)
* 5)、Jdk序列化和Json序列化,性能太低,高性能项目不建议使用
*
* 总结的总结: 如果性能要求特别高(或者对象结构层次不深),可以使用Clone方式;否则可以考虑使用 Kryo序列化和反序列化实现对象深拷贝
*
* @author kevin
* @date 2020/9/27 13:45
* @since 1.0.0
*/
public class DeepCopyTest {
/**
* 循环的次数
*/
private static final int LOOP = 1000;
private static Kryo kryo = new Kryo();
public static void main(String[] args) throws Exception {
DeepCopyEntity demo = getInit();
StopWatch stopWatch = new StopWatch("测试深拷贝");
stopWatch.start();
for (int i = 0; i < LOOP; i++) {
// DeepCopyEntity deep = newObject(demo);
final DeepCopyEntity deep = demo.clone();
// final DeepCopyEntity deepCopyEntity = copyImplSerializable(demo);
// final DeepCopyEntity deepCopyEntity = copyByKryo(demo);
// final DeepCopyEntity deepCopyEntity1 = copyByJson(demo);
}
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
/**
* 深层拷贝 - 需要net.sf.json.JSONObject
* @param
* @param obj
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static T copyByJson(T obj) throws Exception {
return (T) JSONObject.toBean(JSONObject.fromObject(obj),obj.getClass());
}
/**
*
* @param source
* @return
*/
public static DeepCopyEntity copyByKryo(DeepCopyEntity source){
return kryo.copy(source);
}
/**
* 深层拷贝 - 需要类继承序列化接口
* @param
* @param obj
* @return
* @throws Exception
* @see java.io.Closeable
* @see AutoCloseable 不用进行关闭
*/
@SuppressWarnings("unchecked")
public static T copyImplSerializable(T obj) throws Exception {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
Object o = null;
//如果子类没有继承该接口,这一步会报错
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
o = ois.readObject();
return (T) o;
} catch (Exception e) {
throw new Exception("对象中包含没有继承序列化的对象");
}
}
private static DeepCopyEntity newObject(DeepCopyEntity demo) {
final DeepCopyEntity deepCopyEntity = new DeepCopyEntity();
deepCopyEntity.setId(demo.getId());
deepCopyEntity.setField1(demo.getField1());
deepCopyEntity.setField2(demo.getField2());
deepCopyEntity.setField3(demo.getField1());
deepCopyEntity.setField4(demo.getField1());
deepCopyEntity.setField5(demo.getField1());
deepCopyEntity.setField6(demo.getField1());
deepCopyEntity.setField7(demo.getField1());
deepCopyEntity.setField8(demo.getField1());
deepCopyEntity.setField9(demo.getField1());
deepCopyEntity.setField10(demo.getField1());
deepCopyEntity.setField11(demo.getField1());
deepCopyEntity.setField12(demo.getField1());
deepCopyEntity.setField13(demo.getField1());
deepCopyEntity.setField14(demo.getField1());
deepCopyEntity.setField15(demo.getField1());
deepCopyEntity.setField16(demo.getField1());
deepCopyEntity.setField17(demo.getField1());
deepCopyEntity.setField18(demo.getField1());
deepCopyEntity.setField19(demo.getField1());
deepCopyEntity.setField20(demo.getField1());
deepCopyEntity.setField21(demo.getField1());
deepCopyEntity.setField22(demo.getField1());
deepCopyEntity.setField23(demo.getField1());
deepCopyEntity.setField24(demo.getField1());
deepCopyEntity.setField25(demo.getField1());
deepCopyEntity.setField26(demo.getField1());
deepCopyEntity.setField27(demo.getField1());
deepCopyEntity.setField28(demo.getField1());
deepCopyEntity.setField29(demo.getField1());
deepCopyEntity.setField30(demo.getField1());
deepCopyEntity.setField31(demo.getField1());
deepCopyEntity.setField32(demo.getField1());
deepCopyEntity.setField33(demo.getField1());
deepCopyEntity.setField34(demo.getField1());
deepCopyEntity.setField35(demo.getField1());
deepCopyEntity.setField36(demo.getField1());
deepCopyEntity.setField37(demo.getField1());
deepCopyEntity.setField38(demo.getField1());
deepCopyEntity.setField39(demo.getField1());
deepCopyEntity.setField40(demo.getField1());
deepCopyEntity.setField41(demo.getField1());
deepCopyEntity.setField42(demo.getField1());
deepCopyEntity.setField43(demo.getField1());
deepCopyEntity.setField44(demo.getField1());
deepCopyEntity.setField45(demo.getField1());
deepCopyEntity.setField46(demo.getField1());
deepCopyEntity.setField47(demo.getField1());
deepCopyEntity.setField48(demo.getField1());
deepCopyEntity.setField49(demo.getField1());
deepCopyEntity.setField50(demo.getField1());
return deepCopyEntity;
}
/**
* 获取初始化值
* @return demo对象
*/
private static DeepCopyEntity getInit() {
final DeepCopyEntity deepCopyEntity = new DeepCopyEntity();
deepCopyEntity.setId("测试字段进来撒个是个是个复活节快乐时刻六公里按时交付格拉斯可根据ask了接受了嘎嘎健康金克拉是个零售价格克拉斯关键时刻两个jklsghbld时间噶设立国家级法国设计规划拉萨尽快赶回监考老师的风格就是看来撒骨灰两个据类");
// 省略后面所有字段的设置,都设置一样的字段 ......
return deepCopyEntity;
}
}
总结:
1)、序列化性能 Clone > new > Kryo序列化 > Jdk序列化 > Json(各种Json类似)序列化
2)、Clone深拷贝性能最高,但是如果属性中有特定的对象字段,则需要自己编写代码
3)、new 性能仅次于Clone,因为需要执行Jvm过程(常量池判断,内存分配,值初始化,init方法调用,栈中对象的引用等),
并且主要是每个对象需要单独编写代码,当然也不建议使用反射
4)、kryo 性能较高,并且不需要单独的开发, 若对性能不是特别高,可以考虑使用.
kryo是非线程安全的,项目中使用时可以放入ThreadLocal中
5)、Jdk序列化和Json序列化,性能太低,高性能项目不建议使用
如果性能要求特别高(或者对象结构层次不深),可以使用Clone方式;
否则可以考虑使用 Kryo序列化和反序列化实现对象深拷贝
结尾
本文到这里就结束了,感谢看到最后的朋友,都看到最后了,点个赞再走啊,如有不对之处还请多多指正。
深度拷贝 java_Java深度拷贝方式和性能对比相关推荐
- 详解C#实例化对象的三种方式及性能对比
前言 做项目过程中有个需求要实例化两万个对象并添加到List 中,这个过程大概需要1min才能加载完(传参较多),于是开启了代码优化之旅,再此记录. 首先想到的是可能实例化比较耗时,于是开始对每种实例 ...
- 求斐波那契数列第n位的几种实现方式及性能对比
在每一种编程语言里,斐波那契数列的计算方式都是一个经典的话题.它可能有很多种计算方式,例如:递归.迭代.数学公式.哪种算法最容易理解,哪种算法是性能最好的呢? 这里给大家分享一下我对它的研究和总结:下 ...
- 单例模式的3种实现方式, 及其性能对比
2019独角兽企业重金招聘Python工程师标准>>> 1. 懒汉模式(double check), 线程安全, 效率不高, 可以延迟加载 public class Singleto ...
- Java集合篇:Map常用遍历方式 以及 性能对比
一.Map集合常用的遍历方式: 遍历Map集合的常用方式有三种:使用keySet的方式,使用entrySet的方式,使用values()取值的方式,这三种方式中,都有对应的for循环遍历和Iterat ...
- 求斐波那契数列第n位的几种实现方式及性能对比(c#语言)
在每一种编程语言里,斐波那契数列的计算方式都是一个经典的话题.它可能有很多种计算方式,例如:递归.迭代.数学公式.哪种算法最容易理解,哪种算法是性能最好的呢? 这里给大家分享一下我对它的研究和总结:下 ...
- canvas 擦除动画_帧动画的多种实现方式与性能对比
作者: 前端向朔 from 迅雷前端 本文目录 Web 动画形式 应用场景 素材准备 实现方案 一.GIF 图 二.CSS3 帧动画 三.JS 帧动画 方案总结 注意事项 总结 Web 动画形式 首先 ...
- 帧动画的多种实现方式与性能对比
文章目录 Web动画形式 应用场景 素材准备 实现方案 一.GIF图 二.CSS3帧动画 三.JS帧动画 方案总结 注意事项 总结 Web动画形式 首先我们来了解一下Web有哪些动画形式 1. CSS ...
- Go字符串拼接的方式与性能对比
一.Go中字符串的特殊之处 Go中的字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节).由于该编码对占用字节长度的不定性,Go ...
- 深度拷贝 java_java 深度拷贝 复制 深度复制
1.深度拷贝.复制代码实现 最近需要用到比较两个对象属性的变化,其中一个是oldObj,另外一个是newObj,oldObj是newObj的前一个状态,所以需要在newObj的某个状态时,复制一个一样 ...
最新文章
- 错误:“Cannot load JDBC driver class ‘com.mysql.jdbc.Driver”的解决方法
- html和css之间有什么区别,html语法和css语法之间有什么区别
- 万字干货:教新手从0到1搭建完整的增长数据体系(30图)
- Java如何查看死锁?
- JavaScript或jQuery中使用键盘控制对象运动
- html 滚动条停止事件,CSS scroll-snap滚动事件停止及元素位置检测实现
- 如何用 Flutter 实现混合开发?闲鱼公开源代码实例
- 数据监测驱动下的信息流广告优化
- 信息学奥赛一本通(1051:分段函数)
- js调用微信扫一扫demo_JS 调用微信扫一扫功能
- 使用git初始化本地仓库并提交到远程分支
- [设计模式] - 工厂模式
- Pytorch——torch.Tensor.data操作的含义
- Hadoop概念学习系列之Hadoop HA进一步深入(二十八)
- 一个架构师谈什么是架构,以及怎么成为架构师
- python之模块随笔记-os
- 计算机中新建的快捷键是什么,什么是新建文件夹的快捷键?新建文件夹的快捷键有哪些?...
- QQ通过xml卡片自动探测对方ip
- multisim 10.0安装、破解、汉化
- html 超链接打开pdf,HTML利用超链接打开链接文件
热门文章
- visual studio安装python插件_Visual Studio 2012 Ultimate 上安装 Python 开发插件 PTVS
- Ubuntu系统下桌面卡死,但是鼠标键盘可以动
- SSM个人遇到的问题汇总——不定期更新
- ajax 泛微oa表单js_OA项目总结
- python毒酒检测_检测OpenSSL漏洞的脚本
- ext时间控件Ext.ux.form.DateTimeField和Ext.form.DateField的用法比较
- html 怎么让tr的css覆盖td的_前端项目实战——华图教育网页(适合学习了HTML和CSS的小伙伴们)...
- sybase 事务插入时不可查询_InnoDB事务与锁
- ajax xmlhttp.responsetext,Ajax:xmlhttp.responseText响应显示完整的内部HTML而不是关闭所需文本...
- C语言函数sscanf()的用法