深度拷贝 java_Java深度拷贝方式和性能对比
前言
Java的深度拷貝大致分為克隆(實(shí)現(xiàn)Java的Clone接口)和序列化(實(shí)現(xiàn)Java的Serializable接口)兩種,但是基于不同的序列化方式,有可以延伸出幾種方式。下面分析一下每種的注意事項(xiàng)和性能對(duì)比【當(dāng)前電腦為4核16G,只是當(dāng)前使用main方法單線(xiàn)程測(cè)試】。
一、拷貝和深淺拷貝
可以使用Java native方法提供的Clone方式進(jìn)行對(duì)象的拷貝,其性能是最高的,甚至高過(guò)new 關(guān)鍵字。使用new關(guān)鍵字創(chuàng)建對(duì)象,如果是第一次創(chuàng)建則會(huì)經(jīng)歷類(lèi)加載機(jī)制的雙親委派(加載、驗(yàn)證、準(zhǔn)備、解析、初始化)。即使非第一次創(chuàng)建也會(huì)經(jīng)歷(常量池判斷,內(nèi)存分配,值初始化,init方法調(diào)用,棧中對(duì)象的引用等)等過(guò)程。
我們需要繼承自Clone接口,重寫(xiě)Object的clone方法。如下:
public class DeepCopyEntity implements Cloneable {
@Override
protected DeepCopyEntity clone() {
try {
return (DeepCopyEntity)super.clone();
} catch (CloneNotSupportedException e) {
log.info("沒(méi)有實(shí)現(xiàn)克隆接口");
return null;
}
}
}
但是我們?cè)谑褂玫臅r(shí)候,需要每個(gè)對(duì)象都編寫(xiě)這樣的代碼。可以?xún)?yōu)化為繼承自類(lèi)似下面的 CloneSupport 類(lèi)(前體是沒(méi)有繼承其他的類(lèi)):
public class CloneSupport implements Cloneable {
@SuppressWarnings("unchecked")
@Override
public T clone() {
try {
return (T) super.clone();
} catch (CloneNotSupportedException e) {
throw new CloneRuntimeException(e);
}
}
}
但是即使是克隆之后的對(duì)象也是淺拷貝。即對(duì)象的屬性如果是非基本數(shù)據(jù)類(lèi)型和String的情況下,新老對(duì)象的對(duì)象屬性的內(nèi)存地址任然相同,則任何一個(gè)對(duì)象改變其值之后,另一個(gè)對(duì)象的值也就是改變了,這很多時(shí)候可能是我們不想要的。那么需要進(jìn)行深度的拷貝。則需要其屬性對(duì)象的類(lèi)也繼承自Clone接口,并且重新clone方法。如下(是我項(xiàng)目中使用的):
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 {
}
二、序列化
另一種實(shí)現(xiàn)深度拷貝的方式就是序列化,無(wú)論是Jdk的序列化還是其他方式的序列化都需要實(shí)現(xiàn)自 java.io.Serializable接口,并且設(shè)置自己的serialVersionUID,并且保證項(xiàng)目中不能有相同的值(很多開(kāi)發(fā)的時(shí)候,基于原來(lái)的類(lèi)copy過(guò)來(lái)后需要進(jìn)行修改),如下:
public class DeepCopyEntity implements Cloneable, Serializable {
private static final long serialVersionUID = 6172279441386879379L;
}
三、深度拷貝的方式
1、new關(guān)鍵字
實(shí)現(xiàn)對(duì)象的深度拷貝,就是對(duì)象的每一層屬性的內(nèi)存地址都不相同,那么基于new 對(duì)象,再每一層設(shè)置new的屬性對(duì)象。也是可以實(shí)現(xiàn)的,或者基于反射的方式,并且性能也是比較高的。需要注意jdk 6及之前的反射性能比較差。
優(yōu)點(diǎn):性能高,缺點(diǎn):就是每個(gè)對(duì)象都需要new,并且每一層都需要用setter等進(jìn)行賦值【硬編碼】。
2、Clone
優(yōu)點(diǎn):性能高,缺點(diǎn):所有層級(jí)只要有屬性對(duì)象就需要實(shí)現(xiàn)Clone,并且重寫(xiě)clone方法。如果對(duì)象有七八層,其中每一層的每一個(gè)地方?jīng)]有注意到就可能非深拷貝。
3、jdk序列化
jdk序列化只需要基于ObjectOutputStream將原對(duì)象流寫(xiě)出去(寫(xiě)入本地磁盤(pán)),再基于ObjectInputStream將對(duì)象流讀回來(lái)即可。如下:
/**
* 深層拷貝 - 需要類(lèi)繼承序列化接口
* @param 對(duì)象類(lèi)型
* @param obj 原對(duì)象
* @return 深度拷貝的對(duì)象
* @throws Exception
* @see java.io.Closeable
* @see AutoCloseable 不用進(jìn)行關(guān)閉
*/
@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;
//如果子類(lèi)沒(méi)有繼承該接口,這一步會(huì)報(bào)錯(cuò)
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("對(duì)象中包含沒(méi)有繼承序列化的對(duì)象");
}
}
優(yōu)點(diǎn):不需要像克隆和new一樣單獨(dú)開(kāi)發(fā),缺點(diǎn):性能比較差
4、kyro序列化
kyro需要單獨(dú)引入maven依賴(lài),如:
com.esotericsoftware
kryo
5.0.0-RC9
使用時(shí)需要?jiǎng)?chuàng)建 Kryo對(duì)象【 Kryo kryo = new Kryo(); 】,只是該對(duì)象是非線(xiàn)程安全的,所有如果在項(xiàng)目中使用時(shí),最好放到ThreadLocal中進(jìn)行創(chuàng)建。使用就比較簡(jiǎn)單了:
public static T copyByKryo(T source){
return kryo.copy(source);
}
優(yōu)點(diǎn):性能較高, 缺點(diǎn):需要單獨(dú)引入maven,性能比new 和clone的低一點(diǎn)
5、Json序列化
項(xiàng)目上使用Json 進(jìn)行 redis、rpc調(diào)用(如 Spring Cloud Feign) 進(jìn)行序列化和反序列化是比較常用的,但是如果僅僅是本地深度拷貝,則使用該方式性能是最差的。可以在下面進(jìn)行比較,各種json框架的序列化方式都差不多。
四、性能對(duì)比
創(chuàng)建一個(gè)50個(gè)字段的對(duì)象,并使用不同的深度拷貝方式,創(chuàng)建對(duì)象N多遍。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeepCopyEntity implements Cloneable, Serializable {
/**
* 序列化標(biāo)識(shí)
*/
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;
/**
* 深度拷貝類(lèi)型 循環(huán)次數(shù)[1000] 循環(huán)次數(shù)[10000] 循環(huán)次數(shù)[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
*
* 總結(jié): 1)、序列化性能 Clone > new > Kryo序列化 > Jdk序列化 > Json(各種Json類(lèi)似)序列化
* 2)、Clone深拷貝性能最高,但是如果屬性中有特定的對(duì)象字段,則需要自己編寫(xiě)代碼
* 3)、new 性能僅次于Clone,因?yàn)樾枰獔?zhí)行Jvm過(guò)程(常量池判斷,內(nèi)存分配,值初始化,init方法調(diào)用,棧中對(duì)象的引用等),并且主要是每個(gè)對(duì)象需要單獨(dú)編寫(xiě)代碼,當(dāng)然也不建議使用反射
* 4)、kryo 性能較高,并且不需要單獨(dú)的開(kāi)發(fā), 若對(duì)性能不是特別高,可以考慮使用.(kryo是非線(xiàn)程安全的,項(xiàng)目中使用時(shí)可以放入ThreadLocal中)
* 5)、Jdk序列化和Json序列化,性能太低,高性能項(xiàng)目不建議使用
*
* 總結(jié)的總結(jié): 如果性能要求特別高(或者對(duì)象結(jié)構(gòu)層次不深),可以使用Clone方式;否則可以考慮使用 Kryo序列化和反序列化實(shí)現(xiàn)對(duì)象深拷貝
*
* @author kevin
* @date 2020/9/27 13:45
* @since 1.0.0
*/
public class DeepCopyTest {
/**
* 循環(huán)的次數(shù)
*/
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("測(cè)試深拷貝");
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);
}
/**
* 深層拷貝 - 需要類(lèi)繼承序列化接口
* @param
* @param obj
* @return
* @throws Exception
* @see java.io.Closeable
* @see AutoCloseable 不用進(jìn)行關(guān)閉
*/
@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;
//如果子類(lèi)沒(méi)有繼承該接口,這一步會(huì)報(bào)錯(cuò)
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("對(duì)象中包含沒(méi)有繼承序列化的對(duì)象");
}
}
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對(duì)象
*/
private static DeepCopyEntity getInit() {
final DeepCopyEntity deepCopyEntity = new DeepCopyEntity();
deepCopyEntity.setId("測(cè)試字段進(jìn)來(lái)撒個(gè)是個(gè)是個(gè)復(fù)活節(jié)快樂(lè)時(shí)刻六公里按時(shí)交付格拉斯可根據(jù)ask了接受了嘎嘎健康金克拉是個(gè)零售價(jià)格克拉斯關(guān)鍵時(shí)刻兩個(gè)jklsghbld時(shí)間噶設(shè)立國(guó)家級(jí)法國(guó)設(shè)計(jì)規(guī)劃拉薩盡快趕回監(jiān)考老師的風(fēng)格就是看來(lái)撒骨灰兩個(gè)據(jù)類(lèi)");
// 省略后面所有字段的設(shè)置,都設(shè)置一樣的字段 ......
return deepCopyEntity;
}
}
總結(jié):
1)、序列化性能 Clone > new > Kryo序列化 > Jdk序列化 > Json(各種Json類(lèi)似)序列化
2)、Clone深拷貝性能最高,但是如果屬性中有特定的對(duì)象字段,則需要自己編寫(xiě)代碼
3)、new 性能僅次于Clone,因?yàn)樾枰獔?zhí)行Jvm過(guò)程(常量池判斷,內(nèi)存分配,值初始化,init方法調(diào)用,棧中對(duì)象的引用等),
并且主要是每個(gè)對(duì)象需要單獨(dú)編寫(xiě)代碼,當(dāng)然也不建議使用反射
4)、kryo 性能較高,并且不需要單獨(dú)的開(kāi)發(fā), 若對(duì)性能不是特別高,可以考慮使用.
kryo是非線(xiàn)程安全的,項(xiàng)目中使用時(shí)可以放入ThreadLocal中
5)、Jdk序列化和Json序列化,性能太低,高性能項(xiàng)目不建議使用
如果性能要求特別高(或者對(duì)象結(jié)構(gòu)層次不深),可以使用Clone方式;
否則可以考慮使用 Kryo序列化和反序列化實(shí)現(xiàn)對(duì)象深拷貝
結(jié)尾
本文到這里就結(jié)束了,感謝看到最后的朋友,都看到最后了,點(diǎn)個(gè)贊再走啊,如有不對(duì)之處還請(qǐng)多多指正。
總結(jié)
以上是生活随笔為你收集整理的深度拷贝 java_Java深度拷贝方式和性能对比的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: thinkphp长连接MySQL_Thi
- 下一篇: java reference详解_Jav