Java深度拷貝方式和效能對比

it_lihongmin發表於2020-09-27

目錄

一、拷貝和深淺拷貝

二、序列化

三、深度拷貝的方式

1、new關鍵字

2、Clone

3、jdk序列化

4、kyro序列化

5、Json序列化

四、效能對比

總結: 


    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<T> 類(前體是沒有繼承其他的類):

public class CloneSupport<T> 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<PurOrderItemBO> productList;
}
public class PurOrderSkuDTO extends CloneSupport<PurOrderSkuDTO> {

}

 

二、序列化

    另一種實現深度拷貝的方式就是序列化,無論是Jdk的序列化還是其他方式的序列化都需要實現自 java.io.Serializable介面,並且設定自己的serialVersionUID,並且保證專案中不能有相同的值(很多開發的時候,基於原來的類copy過來後需要進行修改),如下:

public class DeepCopyEntity implements Cloneable, Serializable {
    private static final long serialVersionUID = 6172279441386879379L;
}

   序列化其實還有很對的注意項,可以參見之前寫的:java序列化

三、深度拷貝的方式

1、new關鍵字

    實現物件的深度拷貝,就是物件的每一層屬性的記憶體地址都不相同,那麼基於new 物件,再每一層設定new的屬性物件。也是可以實現的,或者基於反射的方式,並且效能也是比較高的。需要注意jdk 6及之前的反射效能比較差。

    優點:效能高,缺點:就是每個物件都需要new,並且每一層都需要用setter等進行賦值【硬編碼】。

2、Clone

    優點:效能高,缺點:所有層級只要有屬性物件就需要實現Clone,並且重寫clone方法。如果物件有七八層,其中每一層的每一個地方沒有注意到就可能非深拷貝。

3、jdk序列化

    jdk序列化只需要基於ObjectOutputStream將原物件流寫出去(寫入本地磁碟),再基於ObjectInputStream將物件流讀回來即可。如下:

/**
 * 深層拷貝 - 需要類繼承序列化介面
 * @param <T> 物件型別
 * @param obj 原物件
 * @return 深度拷貝的物件
 * @throws Exception
 * @see java.io.Closeable
 * @see AutoCloseable 不用進行關閉
 */
@SuppressWarnings("unchecked")
public static <T> 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依賴,如:

<dependency>
   <groupId>com.esotericsoftware</groupId>
   <artifactId>kryo</artifactId>
   <version>5.0.0-RC9</version>
</dependency>

 使用時需要建立 Kryo物件【 Kryo kryo = new Kryo(); 】,只是該物件是非執行緒安全的,所有如果在專案中使用時,最好放到ThreadLocal中進行建立。使用就比較簡單了:

public static <T> 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 <T>
     * @param obj
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> 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 <T>
     * @param obj
     * @return
     * @throws Exception
     * @see java.io.Closeable
     * @see AutoCloseable 不用進行關閉
     */
    @SuppressWarnings("unchecked")
    public static <T> 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序列化和反序列化實現物件深拷貝

 

 

 

 

 

 

 


 

 

相關文章