多平台统一管理软件接口,如何实现多平台统一管理软件接口
207
2023-07-21
探讨java深拷贝
本文将讨论以下4个问题
1. java Cloneable接口实现深拷贝
2. java 序列化实现深拷贝
3. 号称最快的深拷贝二方库cloning源码分析
4. 几种拷贝方式速度的比较
深拷贝的概念本文就不说了。在C++中实现深拷贝一般情况下重载赋值操作符 “=” 来实现同一个类的对象间的深拷贝,所以很自然的在java中我们也同样可以定义一个copy函数,在函数内部为对象的每一个属性作赋值操作。这种方式简单自然,但存在一个致命性的问题:如果有一天在类中新增加了一个需要深拷贝的属性,那么相应的copy函数也得进行修改,这种方法给类的可扩展性带来了极大的不方便。怎么解决这种问题,且看接下来的1、2、3章节的实现方式和4节的速度测试。
1. java Cloneable接口实现深拷贝
这种方式,需要类实现Colneable接口 clone 函数,在clone函数中调用super.clone。这种方式的深拷贝同样会带来另一个问题,如果类中有其他类的对象作为属性,则其他的类也需要重载并实现Cloneable接口。来一个例子,在下例中ComplexDO中包含了SimpleDO对象,要实现ComplexDO深拷贝,则需要先实现SimpleDO的clone接口:
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
@Override
public Object clone() throws CloneNotSupportedException {
xitkNQ ComplexDO newClass = (ComplexDO) super.clone();
newClass.l = new ArrayList for (SimpleDO simple : this.l) { newClass.l.add((SimpleDO) simple.clone()); } return newClass; } } 需要注意的是很多文章说String类型的对象赋值操作符是深拷贝,但是其实在java中使用赋值操作符的都属于浅拷贝,但为什么这么明显的错误这么多的文章会非要说这个是深拷贝呢?我的理解是String、类型的属性都是基本类型,而且提供的方法只要是设计到内部数据的变动都会new一个新的对象出来。所以一个String的操作不会影响到其原先指向的内存。所以一般说String等基础类的赋值操作为深拷贝。 由于这个原因,在使用String字符串拼接的时候,需要开辟新的内存,所以很多人建议用StringBuilder来代替String来做拼接,因为StringBuilder只有在内置的char数组范围不够的时候才重新申请更大的内存(对于现代JVM,会对代码调优,String+String会被优化成StringBuilder.append的相类似的指令)。与拼接相对的裁剪,在String有个subString函数,当使用subString函数时,新String的内部char数组和原String是否相同?这个比较有意思,感兴趣的可以对比看看JDK1.6和JKD1.7的实现。 2. java 序列化实现深拷贝 这种方式的原理是利用java序列化,将一个对象序列化成二进制字节流,然后对该字节流反序列化赋值给一个对象。代码示例: 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; } } 当然,也可以选用json等序列化的库来完成序列化,这种方式有效的规避了Cloneabel接口的可扩展缺点,一个函数就可以基本上适用于所有的类.缺点是相对内存拷贝,序列化需要先将对象转换成二进制字节流,然后反序列化将该二进制字节流重新拷贝到一块对象内存,相对慢点。 3. 号称最快的深拷贝二方库cloning源码分析 在源码中,核心的处理逻辑在Cloner类中, 分两条递归链路: (1)deepClonhttp://e->cloneInternal->fastClone->cloneInternal (2)deepClone->cloneInternal->cloneObject->cloneInternal 在(1)中fastClone完成的是继承自IfastCloner接口类的对象,即都是些集合操作的拷贝; 在(2)中cloneObject完成的是通过反射机制拿到普通对象的每一个属性,然后对使用Objenesis新生成对象的属性赋值。 这种方式可扩展性强,不仅可以依靠其现有的代码完成深拷贝,还可以自己定义一些克隆的方式和不需要克隆的类型,灵活性强。 4. 几种拷贝方式速度的比较 上述3中模式都可以完成深拷贝,那种拷贝的方式速度最快是我们所关心的。 先上测试代码: public void testCloneComplex() throws CloneNotSupportedException { final int copyCount = 1; List final ComplexDO complex = new ComplexDO(); //调用二方库 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)); //调用Cloneable接口实现的clone函数 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)); //序列化与反序列化生成新对象 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)); } 运行结果的单位为毫秒(此数据忽略不计算java热点和可能的gc)。 从这个表可以得出结论: 1、实现Cloneable接口的拷贝是最快的,因为他只涉及到了内存拷贝,但是如果涉及的属性为普通对象比较多的时候写起来麻烦点 2、序列化/反序列化拷贝最慢 3、使用cloning库,由于使用了递归和反射机制相对Cloneable接口实现的拷贝要慢,但比序列化方式要快。 以上就是本文的全部内容,希望对大家的学习有所帮助。
for (SimpleDO simple : this.l) {
newClass.l.add((SimpleDO) simple.clone());
}
return newClass;
}
}
需要注意的是很多文章说String类型的对象赋值操作符是深拷贝,但是其实在java中使用赋值操作符的都属于浅拷贝,但为什么这么明显的错误这么多的文章会非要说这个是深拷贝呢?我的理解是String、类型的属性都是基本类型,而且提供的方法只要是设计到内部数据的变动都会new一个新的对象出来。所以一个String的操作不会影响到其原先指向的内存。所以一般说String等基础类的赋值操作为深拷贝。
由于这个原因,在使用String字符串拼接的时候,需要开辟新的内存,所以很多人建议用StringBuilder来代替String来做拼接,因为StringBuilder只有在内置的char数组范围不够的时候才重新申请更大的内存(对于现代JVM,会对代码调优,String+String会被优化成StringBuilder.append的相类似的指令)。与拼接相对的裁剪,在String有个subString函数,当使用subString函数时,新String的内部char数组和原String是否相同?这个比较有意思,感兴趣的可以对比看看JDK1.6和JKD1.7的实现。
2. java 序列化实现深拷贝
这种方式的原理是利用java序列化,将一个对象序列化成二进制字节流,然后对该字节流反序列化赋值给一个对象。代码示例:
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;
}
}
当然,也可以选用json等序列化的库来完成序列化,这种方式有效的规避了Cloneabel接口的可扩展缺点,一个函数就可以基本上适用于所有的类.缺点是相对内存拷贝,序列化需要先将对象转换成二进制字节流,然后反序列化将该二进制字节流重新拷贝到一块对象内存,相对慢点。
3. 号称最快的深拷贝二方库cloning源码分析
在源码中,核心的处理逻辑在Cloner类中,
分两条递归链路:
(1)deepClonhttp://e->cloneInternal->fastClone->cloneInternal
(2)deepClone->cloneInternal->cloneObject->cloneInternal
在(1)中fastClone完成的是继承自IfastCloner接口类的对象,即都是些集合操作的拷贝;
在(2)中cloneObject完成的是通过反射机制拿到普通对象的每一个属性,然后对使用Objenesis新生成对象的属性赋值。
这种方式可扩展性强,不仅可以依靠其现有的代码完成深拷贝,还可以自己定义一些克隆的方式和不需要克隆的类型,灵活性强。
4. 几种拷贝方式速度的比较
上述3中模式都可以完成深拷贝,那种拷贝的方式速度最快是我们所关心的。
先上测试代码:
public void testCloneComplex() throws CloneNotSupportedException {
final int copyCount = 1;
List
final ComplexDO complex = new ComplexDO();
//调用二方库
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));
//调用Cloneable接口实现的clone函数
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));
//序列化与反序列化生成新对象
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));
}
运行结果的单位为毫秒(此数据忽略不计算java热点和可能的gc)。
从这个表可以得出结论:
1、实现Cloneable接口的拷贝是最快的,因为他只涉及到了内存拷贝,但是如果涉及的属性为普通对象比较多的时候写起来麻烦点
2、序列化/反序列化拷贝最慢
3、使用cloning库,由于使用了递归和反射机制相对Cloneable接口实现的拷贝要慢,但比序列化方式要快。
以上就是本文的全部内容,希望对大家的学习有所帮助。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~