转载自:http://blog.sina.com.cn/s/blog_6e47de3e01017x4v.html
又是一个困扰了很长时间的问题,唉,当初没有认真的去看书,现在才知道是为什么,好吧下面来简单聊聊,顺便提一提解决方法。
一.序列化和序列化编号 序列化,这个大家都很熟悉了,在使用写对象和读对象的时候我们经常要将该对象的类定义为可以被序列化的,这时候我们一般会考虑实现Serializable接口。但是Java在序列化对象的时候,为了保证同一个对象在反序列化之后依然指向同一片内存空间,使用了一些特殊机制,即序列化编号。 这种采用了序列化编号的序列化算法具体是这么进行的:它会在序列化成功后给所有对象赋予一个序列化编号值,所以在序列化时会首先检查该对象是否已经被序列化过,如果发现某一个对象未被序列化,则正常序列化将对象转换成字节输出出去,与此同时并产生一个序列化编号,标记该对象已经序列化过;如果已经被序列化,则序列化只是将序列化编号输出出去。 举一个简单的例子,对象A和对象B的同一个对象的引用,那么显然A和B指向同一个内存区域,当我们A = B的时候就做了这件事情。这时候如果改动A,则B也会跟着改动,就好比是一个东西有两个不同名字一样。这时候如果我们使用普通的序列化算法序列化A和B,在反序列化时虽然也能得到A和B,但是这时候A和B已经指向了不同的内存区域,A和B也不是同一个对象的引用了,所以需要使用序列化编号的机制,这样在反序列化的时候才能保证A和B是同一个对象的不同引用。
二.可变对象的序列化 上面简单说了以下序列化编号机制,这种机制虽然保证了不会导致引用的出错,但是会带来一个潜在问题,就是可变对象的序列化。比如对象A首先writeObject(A)序列化A一次,产生了一个序列化编号,readObject()可以正常获得对象A,在之后的操作中我们更改了A的某些属性值,这时我们再次writeObject(A)的,那么在readObject()的时候会发现得到的A与上一次一样,即A的属性值并未被更改,因此第二次writeObject仅仅只是输出了A的序列化编号,readObject()得到该序列化编号则去内存中寻找对应的区域,将其取出作为本次接收到的对象。 经过以上的描述,相信应该可以理解所谓的潜在问题是什么了,那么问题是找到了,该如何解决呢,我想到了曾经学习过的Java的克隆机制,下面也来简单讲讲Java的克隆。
三.Java的Clone Java的克隆分为浅克隆和深克隆 浅克隆就是说对象所包含的引用不会被克隆,仅仅只是克隆了自己。比如对象A中包含了某个对象B,那么对A克隆,可以得到一个对象C,它们的内容相同,而且指向了不同的内存区域,但是有一个例外A的对象B在克隆的时候会出现一个对象D,D是C的一个field,B和D是同一个对象的引用,它们指向同一个内存区域。 深克隆则是对象的所有引用也会被复制一遍,以上一个例子来说,B和D将会指向不同的内存区域。 从例子中可以看出来,深克隆和潜克隆的实现不太一样,深克隆需要一层一层的进行浅克隆,要实现A.Class的深克隆,显然要先实现B.Class的深克隆才行,代码的例子可以参考在文章最后给出的两个链接。
四.序列化克隆 前面所说的深克隆显然是比较繁琐的,一种简便的实现深克隆的方法就是利用序列化来实现,看来讲来讲去又回到了序列化上来,它的原理是我们序列化时写出去的本身就是原对象的一个拷贝,对象本身还在内存里,那么当我们反序列化接受回来的时候就可以得到该对象的一个拷贝了。这一点其实从序列化编号的机制里已经可以看出来,我们之所以要使用序列化编号的机制不就是为了保证反序列化回来的同一个对象的不同引用指向同一个内存位置吗,如果一个对象的不同引用反序列化回来的还是原来的自己,那么还何须序列化编号呢。 关键代码如下 public Object deepClone() { //将对象写到流里 ByteArrayOutoutStream bo=new ByteArrayOutputStream(); ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //从流里读出来 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=new ObjectInputStream(bi); return(oi.readObject()); } 当然用这种方法实现克隆需要保证所有类都已经实现了Serializable接口