浅拷贝与深拷贝性能对比

1 篇文章 0 订阅
1 篇文章 0 订阅

​前言

       在日常开发中,对象的转换使用的很频繁,其中对象的转换大致可以分为浅拷贝和深拷贝两种,如果细分,对于深拷贝又可以细分为好几种。本文基于主流的几种深拷贝方式做了一系列测试,并探讨其中的原理和适用场景。

        欢迎读者进行探讨,如有错漏之处,敬请指导,由于公众号新注册用户没有留言功能,想探讨的同学直接在公众号回复即可。
 

一、浅拷贝和深拷贝

        浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝

        深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

        这里主要列出了以下几种方式:

        浅拷贝:

        1)使用Java 的clone方式,要求实现cloneable并且重写clone方法,原理是调用internalClone() 这个native方法,代码侵入性较大,但是性能优秀。

        2)Apache的BeanUtil方式,公认的性能差,已经抛弃,这里不再测试。

        3)Spring的BeanUtil方式,通过反射。

        4)Hutool的BeanUtil方式,这个也是浅拷贝,性能也不错。

        5)MapStruct方式,实际是生成get和set方法,性能优异,不过每次更新都需要deploy。

        6)Apache的BeanCopier方式,与Hutool的BeanCopier的类似,使用了CGLIB动态代理,比反射性能优异,这里选取Apache的BeanCopier。

      浅拷贝主要比较Java 的clone,Spring的BeanUtil,Hutool的BeanUtil,MapStruct,BeanCopier五种方案。

        深拷贝:

        1)用各种json工具进行序列化和反序列化,性能非常差,这里不再对比。

        2)Kryo方式

        3)Orika方式

        4)Java IO进行序列化和反序列化

        5)Protobuf方式,对复杂对象序列化和反序列化还存在问题,Hessian方式和Thrift方式也存在各种问题,这里不再对比。

        6)Dozer方式

        深拷贝主要比较Kryo,Orika,Java IO,Dozer四种方案。

二、具体场景

        浅拷贝:在项目的分层结构中,经常需要BO,VO,DTO之间的转换,这种场景数量巨大,字段较多,因此对性能要求巨大。

        深拷贝:在之前做过的一个进销库存项目中,

        1)某个逻辑需要对数据库两个表(表A,表B,表A是具体的可用号码表,表B是可用号段表)进行更新操作,并且需要记录每次表操作的记录,前台需要查询历史操作记录。

        2)需要对第一次的某些复杂对象的进行修改(因为需要用来映B表,两张表有大量的字段可以映射上,大约有30多个,但是又需要对其中某些复杂对象又需要修改,比如两张表的状态各代表不同含义,比如表的字段A对于表B只是一个子集,如表A中字段C为1,表2中记录为以逗号分隔)。

        3)两个操作都完成后才会记录日志。

        此时记录的日志数据是步骤1之后,步骤2之前的,但是真正执行记录是在步骤2才进行记录操作,所以需要用到深拷贝方案进行解决。

三、测试准备和配置介绍

        1)测试配置:

        系统:mac

        CPU:i7 六核 2.6GHZ

        内存:16G 2667MHZ DDR4

        JDK:    1.8.0_241

        2)测试准备

       其中Orika代码如下,其他大同小异,分别进行1,1000,10w次的测试:

public class DeepCopyTest {    /**     * 循环的次数     */    private final int LOOP = 1;    @Test    public void test(){        DeepCopyEntity demo = getInit();        StopWatch stopWatch = new StopWatch("test");        stopWatch.start("Orika");        for (int i = 0; i < LOOP; i++) {            final DeepCopyEntity deepCopyEntity =             MapperUtils.INSTANCE.map(DeepCopyEntity.class,             demo);        }        stopWatch.stop();        System.out.println(stopWatch.prettyPrint());    }  }
public enum MapperUtils {    /**     * 默认字段实例     */    private static final MapperFacade MAPPER_FACADE =     MAPPER_FACTORY.getMapperFacade();    /**     * 映射实体(默认字段)     *     * @param toClass 映射类对象     * @param data    数据(对象)     * @return 映射类对象     */    public <E, T> E map(Class<E> toClass, T data) {        return MAPPER_FACADE.map(data, toClass);    }}
@Setter@Getter@Accessors(chain = true)@ToString(callSuper = true)public class DeepCopyEntity implements Serializable{    /**     * 序列化标识     */    private static final long serialVersionUID =     6172279441386879379L;    private String id;    private String field1;        ...        private Blind blind;}

        3)引用版本

<dependency>    <groupId>com.esotericsoftware</groupId>    <artifactId>kryo</artifactId>    <version>5.2.0</version>    </dependency><dependency>    <groupId>ma.glasnost.orika</groupId>    <artifactId>orika-core</artifactId>    <version>1.5.4</version>    </dependency><dependency>    <groupId>org.mapstruct</groupId>    <artifactId>mapstruct-jdk8</artifactId>    <version>1.2.0.Final</version>    </dependency><dependency>    <groupId>org.mapstruct</groupId>    <artifactId>mapstruct-processor</artifactId>    <version>1.2.0.Final</version></dependency><dependency>    <groupId>net.sf.dozer</groupId>    <artifactId>dozer</artifactId>    <version>5.5.1</version></dependency>

四、测试结果

浅拷贝:

//次数:    1                1000              10w--------------------------------------------------------------Task name  ns         %     ns         %      ns         %--------------------------------------------------------------clone      000007177  000%  000272971  000%   011918713  001%Hutool     045269308  017%  092633666  029%   819434606  058%Spring     148264853  055%  150638012  048%   428660357  030%MapStruct  002084618  001%  003604989  001%   016203277  001%BeanCopier 073543500  027%  068328236  022%   135412730  010%


深拷贝:

//次数:    1                1000              10w--------------------------------------------------------------Task name  ns         %     ns         %      ns         %--------------------------------------------------------------jdk IO     010201657  002%  122777241  008%   4279557438  005%Kryo       058354177  009%  049618874  003%   157321253   000%Orika      344777022  056%  324837077  022%   562185127   001%Dozer      201866716  033%  996948636  067%   75920114538 094%

总结

1)浅拷贝中MapStruct性能与jdk的clone基本一致,项目中推荐使用MapStruct,而且符合JSR 269规范,它还可以支持字段的自定义转换规则,忽略字段等。

2)深拷贝主要推荐使用Kryo,性能稳定,缺点是每个对象都需要注册。如:

/**     *     * @param source     * @return     */    public DeepCopyEntity copyByKryo(DeepCopyEntity source){        kryo.register(DeepCopyEntity.class);        kryo.register(ArrayList.class);        kryo.register(Blind.class);        return kryo.copy(source);    }

3)MapStruct 使用get和set这种原始方式,是肯定比通过反射获取属性更优,缺点是每次更新mapper时需要deploy,Kryo 方式进行深拷贝,其性能接近RPC的Protobuf,跨平台能力相对也不错,需要注意的是,由于Kryo是线程不安全的,意味着每当需要序列化和反序列化时都需要实例化一次,或者借助ThreadLocal,或者使用Kryo 提供的pool来维护以保证其线程安全,如下所示:

private static final ThreadLocal<Kryo> kryoLocal = new ThreadLocal<Kryo>() {    protected Kryo initialValue() {        Kryo kryo = new Kryo();        kryo.setInstantiatorStrategy(        new Kryo.DefaultInstantiatorStrategy(                    new StdInstantiatorStrategy()));        return kryo;    };};

public KryoPool newKryoPool() {        return new KryoPool.Builder(() -> {            final Kryo kryo = new Kryo();            kryo.setInstantiatorStrategy(            new Kryo.DefaultInstantiatorStrategy(                    new StdInstantiatorStrategy()));            return kryo;        }).softReferences().build();    }

4)Kryo默认支持循环引用,如果不需要,可通过kryo.setReferences(false);关闭,关闭后性能会更优秀。

5)Kryo的源码太长,经过阅读,性能优异主要归结为两点:对long,int等数据类型,采用变长字节存储来代替java中使用固定字节(4,8)字节的模式,因为小值的字段更常见,通过这个方法可以更节省空间,这点与Protobuf 非常的相似,第二个举措是使用了类似缓存的机制,在一次序列化对象中,在整个递归序列化期间,相同的对象,只会序列化一次,后续的用一个局部int值来代替。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值