Java对象序列化小结

JavaMan_KA 2011-07-31 11:46:01
 序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。

  对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

  java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

  从上面的叙述中,我们知道了对象序列化是java编程中的必备武器,那么让我们从基础开始,好好学习一下它的机制和用法。

  java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。

  import java.io.FileInputStream;

  import java.io.FileOutputStream;

  import java.io.IOException;

  import java.io.ObjectInputStream;

  import java.io.ObjectOutputStream;

  import java.io.Serializable;

  public class SerialCtl implements Serializable {

  private String a;

  private transient String b;

  public SerialCtl(String aa, String bb) {

  a = "Not Transient: " + aa;

  b = "Transient: " + bb;

  }

  public String toString() {

  return a + " && " + b;

  }

  private void writeObject(ObjectOutputStream stream) throws IOException {

  stream.defaultWriteObject();

  stream.writeObject(b);//顺序有关

  stream.writeObject("附加信息");

  }

  private void readObject(ObjectInputStream stream) throws IOException,

  ClassNotFoundException {

  stream.defaultReadObject();

  b = (String) stream.readObject();//顺序有关

  System.out.println(b);

  System.out.println((String) stream.readObject());

  }

  public static void main(String[] args) throws IOException,

  ClassNotFoundException {

  SerialCtl sc = new SerialCtl("Test1", "Test2");

  System.out.println("Before: " + sc);

  FileOutputStream buf = new FileOutputStream("x.tmp");

  // ByteArrayOutputStream buf = new ByteArrayOutputStream();

  ObjectOutputStream o = new ObjectOutputStream(buf);

  o.writeObject(sc);

  o.flush();

  o.close();

  // Now get it back:

  ObjectInputStream in = new ObjectInputStream(new FileInputStream("x.tmp"));

  // ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));

  SerialCtl sc2 = (SerialCtl) in.readObject();

  System.out.println("After: " + sc2);

  }

  }

  完全定制序列化过程:

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

  import java.io.*;

  class Test implements Externalizable{ //Test类必须实现Externalizable接口

  private String letterstates = "fanruijun";

  private int num = 0;

  public Test(){

  }

  public void writeExternal(ObjectOutput out) throws IOException {

  out.writeObject(letterstates);

  out.write(88); //在序列化的数据最后加个88

  }

  public void readExternal(ObjectInput in) throws IOException,

  ClassNotFoundException {

  letterstates = (String)in.readObject();

  num = in.read(); //把数字88加进来

  }

  public void putOut(){ //测试

  System.out.println(letterstates +num);

  }

  }

  序列化上面的Test类:AppTest

  import java.io.*;

  public class AppTest {

  private void saveGame(){

  Test m = new Test();

  if (m != null){

  try{

  FileOutputStream ostream = new FileOutputStream("t.txt");

  ObjectOutputStream p = new ObjectOutputStream(ostream);

  p.writeObject(m); //writeExternal()自动执行

  p.flush();

  ostream.close();

  } catch (IOException ioe) {

  System.out.println ("Error saving file:");

  System.out.println (ioe.getMessage());

  }

  }

  }

  private void loadGame(){

  try{

  FileInputStream instream = new FileInputStream("t.txt");

  ObjectInputStream p = new ObjectInputStream(instream);

  Test m = (Test)p.readObject();//readExternal()自动执行

  m.putOut();

  instream.close();

  } catch (Exception e) {

  System.out.println ("Error loading file:");

  System.out.println (e.getMessage());

  }

  }

  public static void main(String[] args){

  new AppTest().saveGame();

  new AppTest().loadGame();

  }

  }

  serialVersionUID的作用

  设置 serialVersionUID默认的生成方式: private static final long serialVersionUID = 1L;

  serialVersionUID的作用:serialVersionUID 用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会出错。 在JDK中,可以利用JDK的bin目录下的serialver.exe工具产生这个serialVersionUID,对于Test.class,执行命令:serialver Test。

  为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入 private static final long serialVersionUID这个属性,具体数值自己定义。这样,即使某个类在与之对应的对象已经序列化出去后做了修改,该对象依然可以被正确反序列化。否则,如果不显式定义该属性,这个属性值将由JVM根据类的相关信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本不兼容而失败。

  不显式定义这个属性值的另一个坏处是,不利于程序在不同的JVM之间的移植。因为不同的编译器实现该属性值的计算策略可能不同,从而造成虽然类没有改变,但是因为JVM不同,出现因类版本不兼容而无法正确反序列化的现象出现。
...全文
48 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
lyhmy 2011-08-01
  • 打赏
  • 举报
回复
顶下。。
zhouxingyu896 2011-07-31
  • 打赏
  • 举报
回复
学习
学习

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧