美文网首页干货分享
Java深度拷贝方式和性能对比

Java深度拷贝方式和性能对比

作者: 小白菜aaa | 来源:发表于2020-09-28 16:13 被阅读0次

    前言

    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;
    }
    

    三、深度拷贝的方式

    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序列化和反序列化实现对象深拷贝

    结尾

    本文到这里就结束了,感谢看到最后的朋友,都看到最后了,点个赞再走啊,如有不对之处还请多多指正。

    相关文章

      网友评论

        本文标题:Java深度拷贝方式和性能对比

        本文链接:https://www.haomeiwen.com/subject/ciuduktx.html