`
ttkktt
  • 浏览: 27350 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

深入了解java序列化之化不肯能为可能

阅读更多

昨天看到我的上一篇博文:深入谈谈java的深拷贝与浅拷贝(http://ttkktt.iteye.com/blog/391171)中的一个评论引发了今日我写这篇博文。

 

首先我们简单说下序列化/反序列化的一些基本概念,

  Java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

 

  Java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。、

 

  对象序列化包括如下步骤:

 

  1.创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;

 

  2.通过对象输出流的writeObject()方法写对象。

 

  对象反序列化的步骤如下:

 

  1.创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;

 

  2.通过对象输入流的readObject()方法读取对象

 

 

只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 ,但是仍可手工控制声明为static或transient的数据成员。序列化时,类的所有数据成员应可序列化除了声明为transient 或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient标记的成员。这样我们可以利用这点通过重写writeObject()/readObject()方法来做一些特别的性能优化操作和解决一些特别问题。

 

例如有些时候我们的应用开发完后发现某个程序写的一些对象在做远程调用时不能被序列化,原因是对象的某个成员不能被序列化。这时候要重新改动这个对象已经不太可能。这时候我们就可以重写writeObject()/readObject()来让这个不可序列化的成员能够被远程复制。

 

java.util.ArrayList的源码就是一个很好的例子:

public class ArrayList extends AbstractList implements List, Cloneable, java.io.
Serializable {
private transient Object elementData[];
private int size;
...

private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
    // Write out element count, and any hidden stuff
    s.defaultWriteObject();
   // Write out array length
    s.writeInt(elementData.length);
    // Write out all elements in the proper order.
    for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);
    }

    private synchronized void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();
    // Read in array length and allocate array
    int arrayLength = s.readInt();
    elementData = new Object[arrayLength];
    // Read in all elements in the proper order.
    for (int i=0; i<size; i++)
            elementData[i] = s.readObject();
    }
 

 

 

因为elementData数组中存储的“元素”其实仅是对这些元素的一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。对应的readObject()也按照writeObject()方法的顺序从输入流中读取。

 

如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现 Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现 Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。


 

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics