Java幾種深度拷貝方法效率比較


Java在復制一個對象時有淺拷貝與深拷貝之分,具體區別就不在此贅述,本文主要分析Java深拷貝的幾種方法以及他們的效率高低。

1. 使用Java序列化方法

想要深拷貝一個對象,常用的方法是序列化為數據流,此方法的前提是對象以及對象中包含的子對象都要繼承Serializable接口。

2. 利用Kryo序列化框架

Kryo是一個快速高效的Java序列化框架,旨在提供快速、高效和易用的API。無論文件、數據庫或網絡數據Kryo都可以隨時完成序列化。Kryo還可以執行自動深拷貝(克隆)、淺拷貝(克隆)。這是對象到對象的直接拷貝,非對象->字節->對象的拷貝。該方法不需要繼承Serializable接口。

<dependency>
        <groupId>com.esotericsoftware</groupId>
        <artifactId>kryo</artifactId>
        <version>4.0.1</version>
</dependency>

3. 利用Json轉化的方法

如果對象沒有繼承Serializable接口,可以先將對象轉化為JSON,再序列化為對象,和第一種方法類似。Json轉換工具可以用Jackson或者Json-lib,本文選擇Json-lib只需要在maven里面添加以下依賴:

<dependency>
        <groupId>net.sf.json-lib</groupId>
        <artifactId>json-lib</artifactId>
        <version>2.4</version>
        <classifier>jdk15</classifier>
</dependency>

4. 手動New對象的方法

人工構建對象,如果需要復制的對象中包含非基本類型,如List,對象等結構時,可以在需要的時候手動new對象,將屬性值挨個調用set方法,比較繁瑣。

5. 具體實例

(1) 首先構造兩個實體對象User.java和Name.java,具體代碼如下

public class User implements Serializable{
	private static final long serialVersionUID = -6952319891279734655L;
	private Name name;
	private String phone;
	private String sex;
	private int age;
  ...//此處省略get、set方法
}
public class Name implements Serializable{
	private static final long serialVersionUID = -2023200990550843496L;
	private String firstName;
	private String lastName;
  ...//此處省略get、set方法
}

(2) 測試的主程序代碼如下:

package com.test.sort.hello;

/**
 * Created by GeekBoy on 2017/12/10.
 */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import com.esotericsoftware.kryo.Kryo;
import net.sf.json.JSONObject;

/**
 * @author GeekBoy
 *
 */
public class TestCopy {

    public static void main(String[] args) {

        User source=new User();
        source.setAge(25);
        source.setPhone("13590117892");
        source.setSex("1");
        Name name=new Name();
        name.setFirstName("li");
        name.setLastName("ming");
        source.setName(name);
        Long before=System.currentTimeMillis();
        for(int i=0;i<1000000;i++){
            User tmp=copyByNewObject(source);
            try {
                //User tmp=copyImplSerializable(source);
                //User tmp=copyByJson(source);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Long after=System.currentTimeMillis();

        System.out.println(after-before);

    }

    /**
     * 深層拷貝 - 需要類繼承序列化接口
     * @param <T>
     * @param obj
     * @return
     * @throws Exception
     */
    @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("對象中包含沒有繼承序列化的對象");
        } finally{
            try {
                baos.close();
                oos.close();
                bais.close();
                ois.close();
            } catch (Exception e2) {
                //這里報錯不需要處理
            }
        }
    }

    /**
     * 深層拷貝 - 需要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());
    }


    /**
     * 通過new 對象的方法深拷貝
     * @param source
     * @return
     */
    public static User copyByNewObject(User source){
        User result=new User();
        result.setAge(source.getAge());
        result.setPhone(source.getPhone());
        result.setSex(source.getSex());
        Name name=new Name();
        name.setFirstName(source.getName().getFirstName());
        name.setLastName(source.getName().getLastName());
        result.setName(name);
        return result;
    }
    /**
     * 通過Kryo框架深拷貝
     * @param source
     * @return
     */
    public static User copyByKryo(User source){
        Kryo kryo = new Kryo();
        return kryo.copy(source);
    }
}

6. 運行結果及效率分析

new 對象 JDK序列化 Kyro序列化 Json轉化
1000次 1 237 307 477
10000次 3 914 790 1328
100000次 10 2951 1780 2604

通過上面的測試可以看出new 對象的方法是最快的,比較適合性能要求較高的場合。其次是Kyro序列化方法,Json轉化的方法還有優化的余地,使用不同的Json庫會有不同結果。最慢的是JDK的序列化操作,不建議用此種方案進行深度拷貝。

參考文獻

http://blog.csdn.net/isea533/article/details/9375907

https://github.com/EsotericSoftware/kryo

鏡像地址

http://www.zhangwei.wiki/#/posts/1

pay


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM