本文將討論以下4個(gè)問(wèn)題
1. java Cloneable接口實(shí)現(xiàn)深拷貝
2. java 序列化實(shí)現(xiàn)深拷貝
3. 號(hào)稱最快的深拷貝二方庫(kù)cloning源碼分析
4. 幾種拷貝方式速度的比較
深拷貝的概念本文就不說(shuō)了。在C++中實(shí)現(xiàn)深拷貝一般情況下重載賦值操作符 “=” 來(lái)實(shí)現(xiàn)同一個(gè)類的對(duì)象間的深拷貝,所以很自然的在java中我們也同樣可以定義一個(gè)copy函數(shù),在函數(shù)內(nèi)部為對(duì)象的每一個(gè)屬性作賦值操作。這種方式簡(jiǎn)單自然,但存在一個(gè)致命性的問(wèn)題:如果有一天在類中新增加了一個(gè)需要深拷貝的屬性,那么相應(yīng)的copy函數(shù)也得進(jìn)行修改,這種方法給類的可擴(kuò)展性帶來(lái)了極大的不方便。怎么解決這種問(wèn)題,且看接下來(lái)的1、2、3章節(jié)的實(shí)現(xiàn)方式和4節(jié)的速度測(cè)試。
1. java Cloneable接口實(shí)現(xiàn)深拷貝
這種方式,需要類實(shí)現(xiàn)Colneable接口 clone 函數(shù),在clone函數(shù)中調(diào)用super.clone。這種方式的深拷貝同樣會(huì)帶來(lái)另一個(gè)問(wèn)題,如果類中有其他類的對(duì)象作為屬性,則其他的類也需要重載并實(shí)現(xiàn)Cloneable接口。來(lái)一個(gè)例子,在下例中ComplexDO中包含了SimpleDO對(duì)象,要實(shí)現(xiàn)ComplexDO深拷貝,則需要先實(shí)現(xiàn)SimpleDO的clone接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class SimpleDO implements Cloneable, Serializable { private int x = 1 ; private String s = "simpleDO" ; @Override protected Object clone() throws CloneNotSupportedException { SimpleDO newClass = (SimpleDO) super .clone(); return newClass; } } public class ComplexDO implements Cloneable, Serializable { private int x = 1 ; private String s = "complex" ; private Integer a = 123 ; private Integer b = 1234 ; private Integer c = 1334455 ; private String s2 = "hehehe" ; private String s3 = "hahahaha" ; private Long id = 1233245L; private ArrayList<SimpleDO> l = new ArrayList<SimpleDO>(); @Override public Object clone() throws CloneNotSupportedException { ComplexDO newClass = (ComplexDO) super .clone(); newClass.l = new ArrayList<SimpleDO>(); for (SimpleDO simple : this .l) { newClass.l.add((SimpleDO) simple.clone()); } return newClass; } } |
需要注意的是很多文章說(shuō)String類型的對(duì)象賦值操作符是深拷貝,但是其實(shí)在java中使用賦值操作符的都屬于淺拷貝,但為什么這么明顯的錯(cuò)誤這么多的文章會(huì)非要說(shuō)這個(gè)是深拷貝呢?我的理解是String、類型的屬性都是基本類型,而且提供的方法只要是設(shè)計(jì)到內(nèi)部數(shù)據(jù)的變動(dòng)都會(huì)new一個(gè)新的對(duì)象出來(lái)。所以一個(gè)String的操作不會(huì)影響到其原先指向的內(nèi)存。所以一般說(shuō)String等基礎(chǔ)類的賦值操作為深拷貝。
由于這個(gè)原因,在使用String字符串拼接的時(shí)候,需要開(kāi)辟新的內(nèi)存,所以很多人建議用StringBuilder來(lái)代替String來(lái)做拼接,因?yàn)镾tringBuilder只有在內(nèi)置的char數(shù)組范圍不夠的時(shí)候才重新申請(qǐng)更大的內(nèi)存(對(duì)于現(xiàn)代JVM,會(huì)對(duì)代碼調(diào)優(yōu),String+String會(huì)被優(yōu)化成StringBuilder.append的相類似的指令)。與拼接相對(duì)的裁剪,在String有個(gè)subString函數(shù),當(dāng)使用subString函數(shù)時(shí),新String的內(nèi)部char數(shù)組和原String是否相同?這個(gè)比較有意思,感興趣的可以對(duì)比看看JDK1.6和JKD1.7的實(shí)現(xiàn)。
2. java 序列化實(shí)現(xiàn)深拷貝
這種方式的原理是利用java序列化,將一個(gè)對(duì)象序列化成二進(jìn)制字節(jié)流,然后對(duì)該字節(jié)流反序列化賦值給一個(gè)對(duì)象。代碼示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public Object seirCopy(Object src) { try { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(src); ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); ObjectInputStream in = new ObjectInputStream(byteIn); Object dest = in.readObject(); return dest; } catch (Exception e) { //do some error handler return null ; } } |
當(dāng)然,也可以選用json等序列化的庫(kù)來(lái)完成序列化,這種方式有效的規(guī)避了Cloneabel接口的可擴(kuò)展缺點(diǎn),一個(gè)函數(shù)就可以基本上適用于所有的類.缺點(diǎn)是相對(duì)內(nèi)存拷貝,序列化需要先將對(duì)象轉(zhuǎn)換成二進(jìn)制字節(jié)流,然后反序列化將該二進(jìn)制字節(jié)流重新拷貝到一塊對(duì)象內(nèi)存,相對(duì)慢點(diǎn)。
3. 號(hào)稱最快的深拷貝二方庫(kù)cloning源碼分析
在源碼中,核心的處理邏輯在Cloner類中,
分兩條遞歸鏈路:
- (1)deepClone->cloneInternal->fastClone->cloneInternal
- (2)deepClone->cloneInternal->cloneObject->cloneInternal
在(1)中fastClone完成的是繼承自IfastCloner接口類的對(duì)象,即都是些集合操作的拷貝;
在(2)中cloneObject完成的是通過(guò)反射機(jī)制拿到普通對(duì)象的每一個(gè)屬性,然后對(duì)使用Objenesis新生成對(duì)象的屬性賦值。
這種方式可擴(kuò)展性強(qiáng),不僅可以依靠其現(xiàn)有的代碼完成深拷貝,還可以自己定義一些克隆的方式和不需要克隆的類型,靈活性強(qiáng)。
4. 幾種拷貝方式速度的比較
上述3中模式都可以完成深拷貝,那種拷貝的方式速度最快是我們所關(guān)心的。
先上測(cè)試代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public void testCloneComplex() throws CloneNotSupportedException { final int copyCount = 1 ; List<ComplexDO> complexDOList = new ArrayList<ComplexDO>(copyCount * 3 ); final ComplexDO complex = new ComplexDO(); //調(diào)用二方庫(kù) long start = System.currentTimeMillis(); for ( int i = 0 ; i < copyCount; ++i) { final ComplexDO deepClone = cloner.deepClone(complex); complexDOList.add(deepClone); } long end = System.currentTimeMillis(); System.out.println( "deepClone cost time=" + (end-start)); //調(diào)用Cloneable接口實(shí)現(xiàn)的clone函數(shù) start = System.currentTimeMillis(); for ( int i = 0 ; i < copyCount; ++i) { final ComplexDO interfaceClone = (ComplexDO) complex.clone(); complexDOList.add(interfaceClone); } end = System.currentTimeMillis(); System.out.println( "interfaceClone cost time=" + (end-start)); //序列化與反序列化生成新對(duì)象 start = System.currentTimeMillis(); for ( int i = 0 ; i < copyCount; ++i) { final ComplexDO seirClone = seirCopy(complex); complexDOList.add(seirClone); } end = System.currentTimeMillis(); System.out.println( "seirClone cost time=" + (end-start)); } |
運(yùn)行結(jié)果的單位為毫秒(此數(shù)據(jù)忽略不計(jì)算java熱點(diǎn)和可能的gc)。
從這個(gè)表可以得出結(jié)論:
1、實(shí)現(xiàn)Cloneable接口的拷貝是最快的,因?yàn)樗簧婕暗搅藘?nèi)存拷貝,但是如果涉及的屬性為普通對(duì)象比較多的時(shí)候?qū)懫饋?lái)麻煩點(diǎn)
2、序列化/反序列化拷貝最慢
3、使用cloning庫(kù),由于使用了遞歸和反射機(jī)制相對(duì)Cloneable接口實(shí)現(xiàn)的拷貝要慢,但比序列化方式要快。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。