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");
}
测试说明
除了序列化反序列化的方式都能完成常用业务的深拷贝,但是你调试下会发现他们都是不完全深拷贝的,在多层嵌套的情况下并没有完全拷贝每个对象,这点需注意,若要完全深拷贝,请用序列化反序列化的方式去拷贝。
本文来自博客园,作者:HumorChen99,转载请注明原文链接:https://www.cnblogs.com/HumorChen/p/18039423