Java 对象、列表常用深拷贝方式与性能测试

Java 对象、列表常用深拷贝方式与性能测试

测试环境

项目:jdk1.8、maven3.6、idea编辑器、springboot2.2.x

测试对象

/**
 * @author: humorchen
 * date: 2024/1/22
 * description:
 **/
@Data
public class AObject {
    private String a;
    private Integer b;
    private Long c;
    private Double d;
    private Float e;
    private Boolean f;
    private Character g;
    private Byte h;
    private Short i;
    private List<ItemVo> list;
}

/**
 * @author: humorchen
 * date: 2024/1/22
 * description:
 **/
@Data
public class BObject {
    private String a;
    private Integer b;
    private Long c;
    private Double d;
    private Float e;
    private Boolean f;
    private Character g;
    private Byte h;
    private Short i;
    private List<ItemVo> list;
}

/**
 * @author: humorchen
 * date: 2024/1/22
 * description:
 **/
@Data
public class ItemVo {
    private String a;
    private Integer b;
}

测试样例对象

    /**
     * 获取AObject
     *
     * @return
     */
    public static AObject getA() {
        AObject aObject = new AObject();
        aObject.setA("a");
        aObject.setB(1);
        aObject.setC(1L);
        aObject.setD(1d);
        aObject.setE(1f);
        aObject.setF(true);
        aObject.setG('g');
        aObject.setH((byte) 1);
        aObject.setI((short) 1);
        List<ItemVo> objects = new ArrayList<>();
        ItemVo itemVo = new ItemVo();
        itemVo.setA("1");
        itemVo.setB(1);
        objects.add(itemVo);
        aObject.setList(objects);
        return aObject;
    }

测试次数:执行100万次复制

运行设备CPU:Intel® Core™ i7-9700 CPU @ 3.00GHz 3.00 GHz 8核心8线程,内存16GB

Spring BeanUtils.copyProperties

BeanUtils是spring提供的一个工具类,底层通过带缓存的反射来实现的。性能高,虽然不是最高的咯,但也是非常高了。要性能很高使用后面的mapstruct。

maven依赖

版本spring-beans 5.2.3-RELEASE

百万次拷贝耗时1591ms

测试代码如下

    /**
     * test spring BeanUtil
     * test spring BeanUtil:1591 ms
     */
    @Test
    public void testBeanUtil() {
        ArrayList<AObject> aObjects = new ArrayList<>();
        for (int i = 0; i < max; i++) {
            aObjects.add(getA());
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        List<BObject> bObjects = aObjects.stream().map(a -> {
            BObject bObject = new BObject();
            BeanUtils.copyProperties(a, bObject);
            return bObject;
        }).collect(Collectors.toList());
        stopWatch.stop();
        System.out.println("test spring BeanUtil:" + stopWatch.getTotalTimeMillis() + " ms");
    }

Hutool BeanUtil.copyList

hutool工具类包是一个java常用工具类包,集合了非常多的工具类,但是呢要注意性能问题、锁问题,性能偏差,你翻看他的源码你会发现,老版本的里头还有ReentrantLock来锁属性,新版的撤销了这个还加了缓存,但是在高并发下一样又触发到了反射Field底层的一个synchronized锁,所以高性能系统、接口请记得压测、阅读源码查看实现方式确认方案是否可靠。

maven依赖

hutool 5.8.24版本

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.24</version>
</dependency>

百万次拷贝耗时8136ms

测试代码如下

/**
 * test Hutool BeanUtils
 * BeanUtil.copyToList:8136 ms
 */
@Test
public void testBeanUtils() {
    ArrayList<AObject> aObjects = new ArrayList<>();
    for (int i = 0; i < max; i++) {
        aObjects.add(getA());
    }
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    List<BObject> bObjects = BeanUtil.copyToList(aObjects, BObject.class);
    stopWatch.stop();
    System.out.println("BeanUtil.copyToList:" + stopWatch.getTotalTimeMillis() + " ms");
}

MapStruct

相对麻烦点,要写mapper接口,然后会在编译的时候生成接口实现类的字节码文件,相当于你定义接口,它帮你实现手写set方法代码去复制值,执行时没有用反射了,效率更高

maven依赖

mapstruct 1.4.2.Final版本

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>

额外需要配置maven的build,不配置这个执行的时候会报错找不到实现类,因为编译构建期间没有给你生成实现类的字节码,我这里还使用了lombok(1.18.6版本),不懂 lombok的可以百度下,很简单。

<build>
        <finalName>recovery-operation</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>

            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!-- See https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html -->
                    <!-- Classpath elements to supply as annotation processor path. If specified, the compiler   -->
                    <!-- will detect annotation processors only in those classpath elements. If omitted, the     -->
                    <!-- default classpath is used to detect annotation processors. The detection itself depends -->
                    <!-- on the configuration of annotationProcessors.                                           -->
                    <!--                                                                                         -->
                    <!-- According to this documentation, the provided dependency processor is not considered!   -->
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.4.2.Final</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.6</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>

    </build>

百万次拷贝耗时998ms

测试代码如下

/**
 * testMapperCopy
 * MapperCopy:998 ms
 */
@Test
public void testMapperCopy() {
    ArrayList<AObject> aObjects = new ArrayList<>();
    for (int i = 0; i < max; i++) {
        aObjects.add(getA());
    }
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    List<BObject> bObjects = aObjects.stream().map(ABMapper.INSTANCE::mapAtoB).collect(Collectors.toList());
    stopWatch.stop();
    System.out.println("MapperCopy:" + stopWatch.getTotalTimeMillis() + " ms");
}

FastJSON 序列化再反序列化

fastjson非常常用,但是2.x有个版本的隐藏坑就是环形引用依赖,建议序列化时带参数SerializerFeature.DisableCircularReferenceDetect禁用这个检测,全局禁用的方式为

JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();

maven依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.32</version>
</dependency>

百万次拷贝耗时2229ms

测试代码

/**
 * testJSON
 * JSON copy:1002 ms
 */
@Test
public void testJSON() {
    ArrayList<AObject> aObjects = new ArrayList<>();
    for (int i = 0; i < max; i++) {
        aObjects.add(getA());
    }
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    List<BObject> bObjects = aObjects.stream().map(aObject -> JSONObject.parseObject(JSONObject.toJSONString(aObject), BObject.class)).collect(Collectors.toList());
    stopWatch.stop();
    System.out.println("JSON copy:" + stopWatch.getTotalTimeMillis() + " ms");
}

测试说明

除了序列化反序列化的方式都能完成常用业务的深拷贝,但是你调试下会发现他们都是不完全深拷贝的,在多层嵌套的情况下并没有完全拷贝每个对象,这点需注意,若要完全深拷贝,请用序列化反序列化的方式去拷贝。

posted @ 2024-01-23 16:16  HumorChen99  阅读(184)  评论(0编辑  收藏  举报  来源