java 深度复制对象-阿里云开发者社区

开发者社区> 黄威的世界> 正文

java 深度复制对象

简介:
+关注继续查看

Java中如何深度拷贝对象呢?

The java.lang.Object root superclass defines a clone() method that will, assuming the subclass implements the java.lang.Cloneable interface, return a copy of the object. While Java classes are free to override this method to do more complex kinds of cloning, the default behavior of clone() is to return a shallow copy of the object. This means that the values of all of the origical object’s fields are copied to the fields of the new object.

 




 

A property of shallow copies is that fields that refer to other objects will point to the same objects in both the original and the clone. For fields that contain primitive or immutable values (intString,float, etc…), there is little chance of this causing problems. For mutable objects, however, cloning can lead to unexpected results. Figure 1 shows an example.

Java代码  收藏代码
  1. import java.io.IOException;  
  2. import java.io.ByteArrayInputStream;  
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.ObjectOutputStream;  
  5. import java.io.ObjectInputStream;  
  6.   
  7. /** 
  8.  * Utility for making deep copies (vs. clone()'s shallow copies) of  
  9.  * objects. Objects are first serialized and then deserialized. Error 
  10.  * checking is fairly minimal in this implementation. If an object is 
  11.  * encountered that cannot be serialized (or that references an object 
  12.  * that cannot be serialized) an error is printed to System.err and 
  13.  * null is returned. Depending on your specific application, it might 
  14.  * make more sense to have copy(...) re-throw the exception. 
  15.  * 
  16.  * A later version of this class includes some minor optimizations. 
  17.  */  
  18. public class UnoptimizedDeepCopy {  
  19.   
  20.     /** 
  21.      * Returns a copy of the object, or null if the object cannot 
  22.      * be serialized. 
  23.      */  
  24.     public static Object copy(Object orig) {  
  25.         Object obj = null;  
  26.         try {  
  27.             // Write the object out to a byte array  
  28.             ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  29.             ObjectOutputStream out = new ObjectOutputStream(bos);  
  30.             out.writeObject(orig);  
  31.             out.flush();  
  32.             out.close();  
  33.   
  34.             // Make an input stream from the byte array and read  
  35.             // a copy of the object back in.  
  36.             ObjectInputStream in = new ObjectInputStream(  
  37.                 new ByteArrayInputStream(bos.toByteArray()));  
  38.             obj = in.readObject();  
  39.         }  
  40.         catch(IOException e) {  
  41.             e.printStackTrace();  
  42.         }  
  43.         catch(ClassNotFoundException cnfe) {  
  44.             cnfe.printStackTrace();  
  45.         }  
  46.         return obj;  
  47.     }  
  48.   
  49. }  

 Unfortunately, this approach has some problems, too:

 

  1. It will only work when the object being copied, as well as all of the other objects references directly or indirectly by the object, are serializable. (In other words, they must implementjava.io.Serializable.) Fortunately it is often sufficient to simply declare that a given classimplements java.io.Serializable and let Java’s default serialization mechanisms do their thing.
  2. Java Object Serialization is slow, and using it to make a deep copy requires both serializing and deserializing. There are ways to speed it up (e.g., by pre-computing serial version ids and defining custom readObject() and writeObject() methods), but this will usually be the primary bottleneck.
  3. The byte array stream implementations included in the java.io package are designed to be general enough to perform reasonable well for data of different sizes and to be safe to use in a multi-threaded environment. These characteristics, however, slow downByteArrayOutputStream and (to a lesser extent) ByteArrayInputStream.

 

The first two of these problems cannot be addressed in a general way. We can, however, use alternative implementations of ByteArrayOutputStream and ByteArrayInputStream that makes three simple optimizations:

 

  1. ByteArrayOutputStream, by default, begins with a 32 byte array for the output. As content is written to the stream, the required size of the content is computed and (if necessary), the array is expanded to the greater of the required size or twice the current size. JOS produces output that is somewhat bloated (for example, fully qualifies path names are included in uncompressed string form), so the 32 byte default starting size means that lots of small arrays are created, copied into, and thrown away as data is written. This has an easy fix: construct the array with a larger inital size.
  2. All of the methods of ByteArrayOutputStream that modify the contents of the byte array aresynchronized. In general this is a good idea, but in this case we can be certain that only a single thread will ever be accessing the stream. Removing the synchronization will speed things up a little. ByteArrayInputStream‘s methods are also synchronized.
  3. The toByteArray() method creates and returns a copy of the stream’s byte array. Again, this is usually a good idea: If you retrieve the byte array and then continue writing to the stream, the retrieved byte array should not change. For this case, however, creating another byte array and copying into it merely wastes cycles and makes extra work for the garbage collector.

An optimized implementation of ByteArrayOutputStream is shown in Figure 4.

Java代码  收藏代码
  1. import java.io.OutputStream;  
  2. import java.io.IOException;  
  3. import java.io.InputStream;  
  4. import java.io.ByteArrayInputStream;  
  5.   
  6. /** 
  7.  * ByteArrayOutputStream implementation that doesn't synchronize methods 
  8.  * and doesn't copy the data on toByteArray(). 
  9.  */  
  10. public class FastByteArrayOutputStream extends OutputStream {  
  11.     /** 
  12.      * Buffer and size 
  13.      */  
  14.     protected byte[] buf = null;  
  15.     protected int size = 0;  
  16.   
  17.     /** 
  18.      * Constructs a stream with buffer capacity size 5K  
  19.      */  
  20.     public FastByteArrayOutputStream() {  
  21.         this(5 * 1024);  
  22.     }  
  23.   
  24.     /** 
  25.      * Constructs a stream with the given initial size 
  26.      */  
  27.     public FastByteArrayOutputStream(int initSize) {  
  28.         this.size = 0;  
  29.         this.buf = new byte[initSize];  
  30.     }  
  31.   
  32.     /** 
  33.      * Ensures that we have a large enough buffer for the given size. 
  34.      */  
  35.     private void verifyBufferSize(int sz) {  
  36.         if (sz > buf.length) {  
  37.             byte[] old = buf;  
  38.             buf = new byte[Math.max(sz, 2 * buf.length )];  
  39.             System.arraycopy(old, 0, buf, 0, old.length);  
  40.             old = null;  
  41.         }  
  42.     }  
  43.   
  44.     public int getSize() {  
  45.         return size;  
  46.     }  
  47.   
  48.     /** 
  49.      * Returns the byte array containing the written data. Note that this 
  50.      * array will almost always be larger than the amount of data actually 
  51.      * written. 
  52.      */  
  53.     public byte[] getByteArray() {  
  54.         return buf;  
  55.     }  
  56.   
  57.     public final void write(byte b[]) {  
  58.         verifyBufferSize(size + b.length);  
  59.         System.arraycopy(b, 0, buf, size, b.length);  
  60.         size += b.length;  
  61.     }  
  62.   
  63.     public final void write(byte b[], int off, int len) {  
  64.         verifyBufferSize(size + len);  
  65.         System.arraycopy(b, off, buf, size, len);  
  66.         size += len;  
  67.     }  
  68.   
  69.     public final void write(int b) {  
  70.         verifyBufferSize(size + 1);  
  71.         buf[size++] = (byte) b;  
  72.     }  
  73.   
  74.     public void reset() {  
  75.         size = 0;  
  76.     }  
  77.   
  78.     /** 
  79.      * Returns a ByteArrayInputStream for reading back the written data 
  80.      */  
  81.     public InputStream getInputStream() {  
  82.         return new FastByteArrayInputStream(buf, size);  
  83.     }  
  84.   
  85. }  

 

Figure 4. Optimized version of ByteArrayOutputStream

 

The getInputStream() method returns an instance of an optimized version ofByteArrayInputStream that has unsychronized methods. The implementation ofFastByteArrayInputStream is shown in Figure 5.

Java代码  收藏代码
  1. import java.io.InputStream;  
  2. import java.io.IOException;  
  3.   
  4. /** 
  5.  * ByteArrayInputStream implementation that does not synchronize methods. 
  6.  */  
  7. public class FastByteArrayInputStream extends InputStream {  
  8.     /** 
  9.      * Our byte buffer 
  10.      */  
  11.     protected byte[] buf = null;  
  12.   
  13.     /** 
  14.      * Number of bytes that we can read from the buffer 
  15.      */  
  16.     protected int count = 0;  
  17.   
  18.     /** 
  19.      * Number of bytes that have been read from the buffer 
  20.      */  
  21.     protected int pos = 0;  
  22.   
  23.     public FastByteArrayInputStream(byte[] buf, int count) {  
  24.         this.buf = buf;  
  25.         this.count = count;  
  26.     }  
  27.   
  28.     public final int available() {  
  29.         return count - pos;  
  30.     }  
  31.   
  32.     public final int read() {  
  33.         return (pos < count) ? (buf[pos++] & 0xff) : -1;  
  34.     }  
  35.   
  36.     public final int read(byte[] b, int off, int len) {  
  37.         if (pos >= count)  
  38.             return -1;  
  39.   
  40.         if ((pos + len) > count)  
  41.             len = (count - pos);  
  42.   
  43.         System.arraycopy(buf, pos, b, off, len);  
  44.         pos += len;  
  45.         return len;  
  46.     }  
  47.   
  48.     public final long skip(long n) {  
  49.         if ((pos + n) > count)  
  50.             n = count - pos;  
  51.         if (n < 0)  
  52.             return 0;  
  53.         pos += n;  
  54.         return n;  
  55.     }  
  56.   
  57. }  

 Figure 5. Optimized version of ByteArrayInputStream.

 

Figure 6 shows a version of a deep copy utility that uses these classes:

Java代码  收藏代码
  1. import java.io.IOException;  
  2. import java.io.ByteArrayInputStream;  
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.ObjectOutputStream;  
  5. import java.io.ObjectInputStream;  
  6.   
  7. /** 
  8.  * Utility for making deep copies (vs. clone()'s shallow copies) of  
  9.  * objects. Objects are first serialized and then deserialized. Error 
  10.  * checking is fairly minimal in this implementation. If an object is 
  11.  * encountered that cannot be serialized (or that references an object 
  12.  * that cannot be serialized) an error is printed to System.err and 
  13.  * null is returned. Depending on your specific application, it might 
  14.  * make more sense to have copy(...) re-throw the exception. 
  15.  */  
  16. public class DeepCopy {  
  17.   
  18.     /** 
  19.      * Returns a copy of the object, or null if the object cannot 
  20.      * be serialized. 
  21.      */  
  22.     public static Object copy(Object orig) {  
  23.         Object obj = null;  
  24.         try {  
  25.             // Write the object out to a byte array  
  26.             FastByteArrayOutputStream fbos =   
  27.                     new FastByteArrayOutputStream();  
  28.             ObjectOutputStream out = new ObjectOutputStream(fbos);  
  29.             out.writeObject(orig);  
  30.             out.flush();  
  31.             out.close();  
  32.   
  33.             // Retrieve an input stream from the byte array and read  
  34.             // a copy of the object back in.   
  35.             ObjectInputStream in =   
  36.                 new ObjectInputStream(fbos.getInputStream());  
  37.             obj = in.readObject();  
  38.         }  
  39.         catch(IOException e) {  
  40.             e.printStackTrace();  
  41.         }  
  42.         catch(ClassNotFoundException cnfe) {  
  43.             cnfe.printStackTrace();  
  44.         }  
  45.         return obj;  
  46.     }  
  47.   
  48. }  

 

Figure 6. Deep-copy implementation using optimized byte array streams

 

The extent of the speed boost will depend on a number of factors in your specific application (more on this later), but the simple class shown in Figure 7 tests the optimized and unoptimized versions of the deep copy utility by repeatedly copying a large object.

 

Java代码  收藏代码
  1. import java.util.Hashtable;  
  2. import java.util.Vector;  
  3. import java.util.Date;  
  4.   
  5. public class SpeedTest {  
  6.   
  7.     public static void main(String[] args) {  
  8.         // Make a reasonable large test object. Note that this doesn't  
  9.         // do anything useful -- it is simply intended to be large, have  
  10.         // several levels of references, and be somewhat random. We start  
  11.         // with a hashtable and add vectors to it, where each element in  
  12.         // the vector is a Date object (initialized to the current time),  
  13.         // a semi-random string, and a (circular) reference back to the  
  14.         // object itself. In this case the resulting object produces  
  15.         // a serialized representation that is approximate 700K.  
  16.         Hashtable obj = new Hashtable();  
  17.         for (int i = 0; i < 100; i++) {  
  18.             Vector v = new Vector();  
  19.             for (int j = 0; j < 100; j++) {  
  20.                 v.addElement(new Object[] {   
  21.                     new Date(),   
  22.                     "A random number: " + Math.random(),  
  23.                     obj  
  24.                  });  
  25.             }  
  26.             obj.put(new Integer(i), v);  
  27.         }   
  28.   
  29.         int iterations = 10;  
  30.   
  31.         // Make copies of the object using the unoptimized version  
  32.         // of the deep copy utility.  
  33.         long unoptimizedTime = 0L;  
  34.         for (int i = 0; i < iterations; i++) {  
  35.             long start = System.currentTimeMillis();  
  36.             Object copy = UnoptimizedDeepCopy.copy(obj);  
  37.             unoptimizedTime += (System.currentTimeMillis() - start);  
  38.   
  39.             // Avoid having GC run while we are timing...  
  40.             copy = null;  
  41.             System.gc();  
  42.         }  
  43.   
  44.   
  45.         // Repeat with the optimized version  
  46.         long optimizedTime = 0L;  
  47.         for (int i = 0; i < iterations; i++) {  
  48.             long start = System.currentTimeMillis();  
  49.             Object copy = DeepCopy.copy(obj);  
  50.             optimizedTime += (System.currentTimeMillis() - start);  
  51.   
  52.             // Avoid having GC run while we are timing...  
  53.             copy = null;  
  54.             System.gc();  
  55.         }  
  56.   
  57.         System.out.println("Unoptimized time: " + unoptimizedTime);  
  58.         System.out.println("  Optimized time: " + optimizedTime);  
  59.     }  
  60.   
  61. }  

 Figure 7. Testing the two deep copy implementations.

 

A few notes about this test:

 

  • The object that we are copying is large. While somewhat random, it will generally have a serialized size of around 700 Kbytes.
  • The most significant speed boost comes from avoid extra copying of data inFastByteArrayOutputStream. This has several implications:

    1. Using the unsynchronized FastByteArrayInputStream speeds things up a little, but the standard java.io.ByteArrayInputStream is nearly as fast.
    2. Performance is mildly sensitive to the initial buffer size in FastByteArrayOutputStream, but is much more sensitive to the rate at which the buffer grows. If the objects you are copying tend to be of similar size, copying will be much faster if you initialize the buffer size and tweak the rate of growth.
  • Measuring speed using elapsed time between two calls to System.currentTimeMillis() is problematic, but for single-threaded applications and testing relatively slow operations it is sufficient. A number of commercial tools (such as JProfiler) will give more accurate per-method timing data.
  • Testing code in a loop is also problematic, since the first few iterations will be slower until HotSpot decides to compile the code. Testing larger numbers of iterations aleviates this problems.
  • Garbage collection further complicates matters, particularly in cases where lots of memory is allocated. In this example, we manually invoke the garbage collector after each copy to try to keep it from running while a copy is in progress.

 

These caveats aside, the performance difference is sigificant. For example, the code as shown in Figure 7 (on a 500Mhz G3 Macintosh iBook running OSX 10.3 and Java 1.4.1) reveals that the unoptimized version requires about 1.8 seconds per copy, while the optimized version only requires about 1.3 seconds. Whether or not this difference is signficant will, of course, depend on the frequency with which your application does deep copies and the size of the objects being copied.

For very large objects, an extension to this approach can reduce the peak memory footprint by serializing and deserializing in parallel threads. See "Low-Memory Deep Copy Technique for Java Objects" for more information.

 

转:http://javatechniques.com/blog/faster-deep-copies-of-java-objects/

参考:http://alvinalexander.com/java/java-deep-clone-example-source-code

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9495 0
Java 目录和文件的复制
1.复制一个目录及其子目录、文件到另外一个目录 //复制一个目录及其子目录、文件到另外一个目录 public void copyFolder(File src, File dest) throws IOException {   if (src.
739 0
Java 文件复制
摘要 尽管Java提供了一个可以处理文件的IO操作类。 但是没有一个复制文件的方法。 复制文件是一个重要的操作,当你的程序必须处理很多文件相关的时候。 然而有几种方法可以进行Java文件复制操作,下面列举出4中最受欢迎的方式。
1068 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13183 0
Java对象的深克隆与浅克隆(对象复制)(上)
Java对象的深克隆与浅克隆(对象复制)(上)
7 0
C++返回值为对象时复制构造函数不执行怎么破
  先说点背景知识,调用复制构造函数的三种情况:   1.当用类一个对象去初始化另一个对象时。   2.如果函数形参是类对象。   3.如果函数返回值是类对象,函数执行完成返回调用时。   在辅导学生上机时,有同学第3点提出异议。有教材上的例题为证: #include &lt;iostream&gt; using namespace std; class Point //Point
1002 0
Android 开发中的代码片段(2)复制对象之间的属性值
前言 开发中会遇到这样的一个情况,我们得到一个dto对象,里面有几十个属性值,需要将这几十个属性值的N个通过VO传输另外一个地方,一般我们的做法是: 创建VO类,new vo() 对象,通过vo.set(dto.get)的方式不断的设置值。
861 0
+关注
黄威的世界
我是一个热衷IT技术的人,希望自己不断地设计开发出对别人非常有用的软件。有近7年的java开发经验(包括2年Android开发经验)和一年左右的linux使用经验。擅长Java Web后台开发 ,喜欢研究新的各种实用技术
668
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载