[关闭]
@linux1s1s 2015-05-06T17:53:50.000000Z 字数 3564 阅读 1933

Java 序列化

Java


标记接口

  1. public interface Serializable{}

正如你看到的,序列化就是这么简单,这个简单的外观,却隐藏着巨大的知识点

什么是序列化

Java的"对象序列化"能让你将一个实现了Serializable接口的对象转换成byte流,这样日后要用这个对象时候,你就能把这些byte数据恢复出来,并据此重新构建那个对象了。

为什么要序列化

简单说就是为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存Object States,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。

什么情况下需要序列化

序列化发生了什么

  1. FileOutputStream fs = new FileOutputStream("foo.ser");
  1. ObjectOutputStream os = new ObjectOutputStream(fs);
  1. os.writeObject(myObject1);
  1. os.close();
  1. Foo myFoo = new Foo();
  2. myFoo .setWidth(37);
  3. myFoo.setHeight(70);

当通过下面的代码序列化之后,MyFoo对象中的width和Height实例变量的值(37,70)都被保存到foo.ser文件中,这样以后又可以把它从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对象。

要想序列化对象,你必须先创建一个OutputStream,然后把它嵌进ObjectOutputStream。这时,你就能用writeObject()方法把对象写入OutputStream了。

writeObject()方法负责写入特定类的对象的状态,以便相应的 readObject()方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

读的时候,你得把InputStream嵌到ObjectInputStream里面,然后再调用readObject()方法。不过这样读出来的,只是一个Object的reference,因此在用之前,还得先下传。readObject() 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。  defaultReadObject()方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

Demo

  1. public class Box implements Serializable {
  2. private static final long serialVersionUID = -3450064362986273896L;
  3. private int width;
  4. private int height;
  5. public static void main(String[] args) {
  6. Box myBox=new Box();
  7. myBox.setWidth(50);
  8. myBox.setHeight(30);
  9. try {
  10. FileOutputStream fs=new FileOutputStream("F:\\foo.ser");
  11. ObjectOutputStream os=new ObjectOutputStream(fs);
  12. os.writeObject(myBox);
  13. os.close();
  14. FileInputStream fi=new FileInputStream("F:\\foo.ser");
  15. ObjectInputStream oi=new ObjectInputStream(fi);
  16. Box box=(Box)oi.readObject();
  17. oi.close();
  18. System.out.println(box.height+","+box.width);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. public int getWidth() {
  24. return width;
  25. }
  26. public void setWidth(int width) {
  27. this.width = width;
  28. }
  29. public int getHeight() {
  30. return height;
  31. }
  32. public void setHeight(int height) {
  33. this.height = height;
  34. }
  35. }

为什么有些类不会序列化

注意事项

serialVersionUID

序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:

  1. ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。

此篇博文参考并修改自:http://www.cnblogs.com/gw811/archive/2012/10/10/2718331.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注