幾款VO數(shù)據(jù)轉(zhuǎn)換工具性能剖析
前言
做后端開(kāi)發(fā)的各位小伙伴應(yīng)該對(duì)數(shù)據(jù)轉(zhuǎn)換都不陌生,在實(shí)際開(kāi)發(fā)中也肯定遇到過(guò)各種數(shù)據(jù)類型之間的轉(zhuǎn)換,但是在數(shù)據(jù)轉(zhuǎn)換的時(shí)候,往往是需要考慮性能問(wèn)題的,特別是在大批量數(shù)據(jù)轉(zhuǎn)換的時(shí)候更是如此,所以本著性能為王的原則,我們今天來(lái)評(píng)測(cè)幾種數(shù)據(jù)轉(zhuǎn)換方案,主要包括以下幾種:
getter/setter手動(dòng)賦值BeanUtilsBeanMap- 反射
我們先說(shuō)結(jié)論,這幾種方式性能排行依次是:getter/setter > 反射 > BeanMap > BeanUtil
數(shù)據(jù)轉(zhuǎn)換
因?yàn)閰⑴c公司的重構(gòu)項(xiàng)目,前段時(shí)間一直比較忙,工作重點(diǎn)主要是業(yè)務(wù)代碼編寫測(cè)試,最近稍微好一點(diǎn),主要在做一些優(yōu)化方面的工作,由于本次項(xiàng)目重構(gòu)主要是為了解決性能問(wèn)題,所以這次重構(gòu)對(duì)性能的要求也稍微高一點(diǎn),昨天在優(yōu)化數(shù)據(jù)轉(zhuǎn)換方案的時(shí)候,在自己的探索和不屑努力之下,終于找到了另外一種性能比較好的數(shù)據(jù)轉(zhuǎn)換方案,所以今天我們的內(nèi)容就是這幾種數(shù)據(jù)轉(zhuǎn)換方案的對(duì)比。
我們先看下兩個(gè)需要進(jìn)行轉(zhuǎn)換的VO,首先是entity,類似于數(shù)據(jù)庫(kù)查出來(lái)的數(shù)據(jù):
public?class?UserEntity?{
????/**
?????*?用戶?id
?????*/
????private?Long?id;
????/**
?????*?用戶名
?????*/
????private?String?userName;
????/**
?????*?昵稱
?????*/
????private?String?nickName;
???//?省略構(gòu)造方法和getter/setter方法
??}
然后是另一個(gè),類似于需要給前端返回的數(shù)據(jù):
public?class?UserVo?{
????/**
?????*?用戶?id
?????*/
????private?Long?id;
????/**
?????*?用戶名
?????*/
????private?String?userName;
????/**
?????*?昵稱
?????*/
????private?String?nickName;
????//?省略構(gòu)造方法和getter/setter方法
}
數(shù)據(jù)初始化:
?//?數(shù)據(jù)初始化
List?userEntityList?=?Lists.newArrayList();
for?(int?i?=?0;?i?10000;?i++)?{
????UserEntity?userEntity?=?new?UserEntity(123L?+?i,?"syske",?"云中志");
????userEntityList.add(userEntity);
}
為了方便測(cè)試,這里我先初始10000條數(shù)據(jù)。
getter/setter
這種方式是最直接、性能最好的方式,當(dāng)然也是兼容性最差的方式,因?yàn)檫@種方式兩個(gè)對(duì)象需要強(qiáng)依賴,如果字段增加需要修改轉(zhuǎn)換方法,對(duì)改動(dòng)不友好:
long?start1?=?System.currentTimeMillis();
List?userVoList?=?Lists.newArrayList();
for?(UserEntity?userEntity?:?userEntityList)?{
????UserVo?userVo?=?new?UserVo();
????userVo.setId(userEntity.getId());
????userVo.setUserName(userEntity.getUserName());
????userVo.setNickName(userEntity.getNickName());
????userVoList.add(userVo);
}
System.out.printf("getter/setter耗時(shí):%s\n",?System.currentTimeMillis()?-?start1);
BeanUtils
BeanUtils是spring-beans包下面的一個(gè)工具了,為我們提供了豐富的方法,這里我們主要測(cè)試的是copyProperties方法,這個(gè)方法有兩個(gè)參數(shù),第一個(gè)參數(shù)是源數(shù)據(jù),第二個(gè)是需要賦值的目標(biāo)數(shù)據(jù):
?long?start2?=?System.currentTimeMillis();
?List?userVoList2?=?Lists.newArrayList();
?for?(UserEntity?userEntity?:?userEntityList)?{
?UserVo?userVo?=?new?UserVo();
?BeanUtils.copyProperties(userEntity,?userVo);
?userVoList2.add(userVo);
?}
?System.out.printf("BeanUtils耗時(shí):%s\n",?System.currentTimeMillis()?-?start2);
這種方式就比較簡(jiǎn)潔了,簡(jiǎn)單來(lái)說(shuō)就是對(duì)兩個(gè)對(duì)象進(jìn)行屬性值拷貝,但是屬性必須要有setter方法(被賦值方)和getter方法(源數(shù)據(jù)),copyProperties方法提供了忽略屬性值的方式,除此外暫未發(fā)現(xiàn)有任何優(yōu)點(diǎn)。如果只是單個(gè)對(duì)象的轉(zhuǎn)換,而且不考慮性能的話,這種方式也比較方便。(起初我以為它至少可以提供無(wú)getter/setter方法的值拷貝,但是測(cè)試之后我發(fā)現(xiàn)我想多了,底層還是反射)。
BeanMap
BeanMap也是spring-beans包下面的,不過(guò)它屬于cglib包(一個(gè)強(qiáng)大的,高性能,高質(zhì)量的Code生成類庫(kù)),它主要提供了一種Bean轉(zhuǎn)Map的能力,最初我也是因?yàn)檫@個(gè)接觸這個(gè)包的,最后發(fā)現(xiàn)它還可以用來(lái)做類型轉(zhuǎn)換,而且性能也還不錯(cuò)。通過(guò)BeanMap來(lái)實(shí)現(xiàn)類型轉(zhuǎn)換的思路也很簡(jiǎn)單,就是分別將目標(biāo)vo和源vo分別轉(zhuǎn)為BeanMap,然后用源Vo的BeanMap覆蓋目標(biāo)vo的BeanMap,然后通過(guò)BeanMap的getBean方法從BeanMap中拿到賦值后的vo,下面是具體實(shí)現(xiàn):
?long?start3?=?System.currentTimeMillis();
List?userVoList3?=?Lists.newArrayList();
for?(UserEntity?userEntity?:?userEntityList)?{
????UserVo?userVo?=?new?UserVo();
????BeanMap?entityMap?=?BeanMap.create(userEntity);
????BeanMap?userVOMap?=?BeanMap.create(userVo);
????userVOMap.putAll(entityMap);
????userVoList3.add((UserVo)?userVOMap.getBean());
}
System.out.printf("BeanMap耗時(shí):%s\n",?System.currentTimeMillis()?-?start3);
這種方式的好處也是簡(jiǎn)潔,但是性能比BeanUtils好,和getter/setter比更靈活,缺點(diǎn)是不能忽略值,但是可以自己實(shí)現(xiàn),也不難。
反射
反射這種方式也算比較原始的解耦方式,缺點(diǎn)是稍微有點(diǎn)繁瑣,但是優(yōu)勢(shì)是性能比BeanUtils和BeanMap要好。
long?start4?=?System.currentTimeMillis();
List?userVoList4?=?Lists.newArrayList();
for?(UserEntity?userEntity?:?userEntityList)?{
????UserVo?userVo?=?new?UserVo();
????Class?extends?UserEntity>?eClass?=?userEntity.getClass();
????Class?extends?UserVo>?vClass?=?userVo.getClass();
????Field[]?fields?=?eClass.getDeclaredFields();
????Field[]?vClassDeclaredFields?=?vClass.getDeclaredFields();
????List?fieldList?=??Lists.newArrayList(vClassDeclaredFields);
????for?(Field?field?:?fields)?{
????????if?(fieldList.contains(field))?{
????????????String?name?=?field.getName().substring(0,?1).toUpperCase()?+?field.getName().substring(1);
????????????Method?setter?=?vClass.getMethod("set"?+?name,?field.getType());
????????????Method?getter?=?eClass.getMethod("get"?+?name,?null);
????????????setter.invoke(userVo,?getter.invoke(userEntity,?null));
????????}
????}
????userVoList4.add(userVo);
}
System.out.printf("反射耗時(shí):%s",?System.currentTimeMillis()?-?start4);
但是這種方式的缺點(diǎn)是如果兩個(gè)vo屬性不一致時(shí)需要單獨(dú)處理,我們可以看到代碼中有屬性字段的校驗(yàn)。
性能對(duì)比
下面我們就分別針對(duì)不同的數(shù)據(jù)量做一個(gè)簡(jiǎn)單的測(cè)試,對(duì)比下各種方案的性能,首先是三個(gè)字段在不同數(shù)據(jù)量下的性能比較:

三個(gè)字段進(jìn)行數(shù)據(jù)轉(zhuǎn)換時(shí),我們可以得出以下結(jié)論:
- 在十萬(wàn)條數(shù)據(jù)的數(shù)據(jù)之前,
getter/setter性能變化不大,一直表現(xiàn)很優(yōu)秀,大概是BeanUtils的30倍; BeanUntils和BeanMap差距也不是特別大,差距最大也就兩倍左右;反射和getter/setter的性能差異大概是3倍;- 綜合極值來(lái)看,
getter/setter的性能差異大概是50倍,性能急劇變化發(fā)生在數(shù)據(jù)由10萬(wàn)變?yōu)?code style="background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(255,100,65);">100萬(wàn)的時(shí)候;反射的性能差異差不多是90倍,性能是從10000條的時(shí)候發(fā)生變化的;BeanMap和BeanUtils性能變化不到,差不多7倍左右
下面我們?cè)倏聪伦侄螖?shù)量增多的情況:

相比于3個(gè)字段,12個(gè)字段的性能并沒(méi)有發(fā)生太大改變,變化比較大的是反射這種方式,其他三種方式并沒(méi)有太大變化,甚至還出現(xiàn)性能更好的情況,但是再100萬(wàn)數(shù)據(jù)量的時(shí)候,反射性能比BeanMap差,不過(guò)也能想明白,畢竟字段越多,反射需要循環(huán)的次數(shù)就越多,所以性能會(huì)下降。好了,關(guān)于測(cè)試我們就到這里吧。
結(jié)語(yǔ)
介于時(shí)間的關(guān)系,我們今天的內(nèi)容就先到這里,感興趣的小伙伴可以自己測(cè)試下,總體來(lái)說(shuō),我們的預(yù)期目標(biāo)算是達(dá)成了。最后,希望通過(guò)今天的性能測(cè)試,能夠讓各位小伙伴重視開(kāi)發(fā)過(guò)程中的性能問(wèn)題,找到更適合的方案。好了,各位小伙伴,晚安吧!
- END -