介绍Java对象序列化使用基础


  序列化的过程便是对象写入字节流和从字节流中读取对象 。将对象状态转换成字节流之后, 可以用java.io包中的各种字节流类将其 保留到文件中,管道到另一线程中或通过网络衔接将对象数据发送到另一主机 。对象序列化 性能十分 方便、 壮大,在RMI、Socket、JMS、EJB都有 利用 。对象序列化问题在网络编程中并不是最 冲动人心的课题,但却相当主要, 存在许多有用 意思 。

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

  2.Java对象序列化不只保留一个对象的数据,并且递归 保留对象 引用的每个对象的数据 。 可以将整个对象 品位写入字节流中, 可以 保留在文件中或在网络衔接上传递 。利用对象序列化 可以进行对象的“深复制”,即复制对象 本身及 引用的对象 本身 。序列化一个对象可能得到整个对象序列 。

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

  Java序列化 比较 方便,通常不需求编写 保留和 复原对象状态的定制代码 。 惟独求实现接口(java.io.Serializable)的类对象 可以转换成字节流或从字节流 复原,不需求在类中添加任何代码 。惟独极少数状况下才需求定制代码 保留或 复原对象状态 。这里要 留神:不是每个类都可序列化,有些类是不能序列化的,例如 波及线程的类与特定JVM有十分复杂的关系 。

  序列化机制:

  序列化分为两大 部分:序列化和反序列化 。序列化是这个过程的第一 部分,将数据分解成字节流,以便存储在文件中或在网络上传输 。反序列化便是 打开字节流并重构对象 。对象序列化不 惟独将 根本数据类型转换成字节 示意,有时还要 复原数据 。 复原数据要求有 复原数据的对象实例 。ObjectOutputStream中的序列化过程与字节流衔接,包括对象类型和版本信息 。反序列化时,JVM用头信息生成对象实例, 而后将对象字节流中的数据复制到对象数据成员中 。下面我们分两大 部分来 阐述:

   解决对象流:(序列化过程和反序列化过程)

  java.io包有两个序列化对象的类 。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象 。

  我们先了解ObjectOutputStream类吧 。ObjectOutputStream类 扩大DataOutput接口 。

  writeObject() 步骤是最主要的 步骤,用于对象序列化 。假如对象包括 其余对象的 引用,则writeObject() 步骤递归序列化这些对象 。每个ObjectOutputStream 保护序列化的对象 引用表, 预防发送同一对象的多个拷贝 。(这点很主要)由于writeObject() 可以序列化整组 穿插 引用的对象, 因此同一ObjectOutputStream实例可能不小心被 申请序列化同一对象 。这时,进行反 引用序列化,而不是再次写入对象字节流 。

  下面,让我们从例子中来了解ObjectOutputStream这个类吧 。

  

// 序列化 today's date 到一个文件中.

  FileOutputStream f = new FileOutputStream("tmp");

  ObjectOutputStream s = new ObjectOutputStream(f);

  s.writeObject("Today");

  s.writeObject(new Date());

  s.flush();

  现在,让我们来了解ObjectInputStream这个类 。它与ObjectOutputStream 类似 。它 扩大DataInput接口 。ObjectInputStream中的 步骤镜像DataInputStream中读取Java 根本数据类型的公开 步骤 。readObject() 步骤从字节流中反序列化对象 。每次调用readObject() 步骤都返回流中下一个Object 。对象字节流并不传输类的字节码,而是包括类名及其签名 。readObject()收到对象时,JVM装入头中指定的类 。假如找不到这个类,则readObject()抛出ClassNotFoundException,假如需求传输对象数据和字节码,则 可以用RMI框架 。ObjectInputStream的其余 步骤用于定制反序列化过程 。

  例子如下:

 //从文件中反序列化 string 对象和 date 对象 FileInputStream in = new FileInputStream("tmp"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject();

  定制序列化过程:

  序列化通常 可以自动 实现,但有时可能要对这个过程进行操纵 。java 可以将类申明为serializable,但仍可手工操纵申明为static或transient的数据成员 。

  例子:一个十分 方便的序列化类 。

 public class simpleSerializableClass implements Serializable

  {

  String sToday="Today:";

  transient Date dtToday=new Date();

  }

  序列化时,类的全部数据成员应可序列化除了申明为transient或static的成员 。将变量申明为transient告诉JVM我们会负责将变元序列化 。将数据成员申明为transient后,序列化过程就 无奈将其加进对象字节流中,没有从transient数据成员发送的数据 。后面数据反序列化时,要重建数据成员(由于它是类定义的一 部分),但不包括任何数据,由于这个数据成员不向流中写入任何数据 。记住,对象流不序列化static或transient 。我们的类要用writeObject()与readObject() 步骤以 解决这些数据成员 。 使用writeObject()与readObject() 步骤时,还要 留神按写入的顺序读取这些数据成员 。

  对于如何 使用定制序列化的 部分代码如下:

 //重写writeObject() 步骤以便 解决transient的成员 。

  public void writeObject(ObjectOutputStream outputStream) throws IOException

  {

  outputStream.defaultWriteObject();

  //使定制的writeObject() 步骤 可以利用自动序列化中内置的逻辑 。

  outputStream.writeObject(oSocket.getInetAddress());

  outputStream.writeInt(oSocket.getPort());

  }

  //重写readObject() 步骤以便 接纳transient的成员 。

  private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException

  {

  inputStream.defaultReadObject();//defaultReadObject()补充自动序列化

  InetAddress oAddress=(InetAddress)inputStream.readObject();

  int iPort =inputStream.readInt();

  oSocket = new Socket(oAddress,iPort);

  iID=getID();

  dtToday =new Date();

  }

   彻底定制序列化过程:

  假如一个类要 彻底负责自己的序列化,则实现Externalizable接口而不是Serializable接口 。Externalizable接口定义包括两个 步骤writeExternal()与readExternal() 。利用这些 步骤 可以操纵对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中, 而后类 彻底负责序列化和 复原数据成员,除了头以外, 根本没有自动序列化 。这里要 留神了 。申明类实现Externalizable接口会有重大的安全风险 。writeExternal()与readExternal() 步骤申明为public, 歹意类 可以用这些 步骤读取和写入对象数据 。假如对象包括敏感信息,则要分外小心 。这包括 使用安全套接或加密整个字节流 。

   解决对象流:(序列化过程和反序列化过程)

  java.io包有两个序列化对象的类 。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象 。

  我们先了解ObjectOutputStream类吧 。ObjectOutputStream类 扩大DataOutput接口 。

  writeObject() 步骤是最主要的 步骤,用于对象序列化 。假如对象包括 其余对象的 引用,则writeObject() 步骤递归序列化这些对象 。每个ObjectOutputStream 保护序列化的对象 引用表, 预防发送同一对象的多个拷贝 。(这点很主要)由于writeObject() 可以序列化整组 穿插 引用的对象, 因此同一ObjectOutputStream实例可能不小心被 申请序列化同一对象 。这时,进行反 引用序列化,而不是再次写入对象字节流 。

  下面,让我们从例子中来了解ObjectOutputStream这个类吧 。

// 序列化 today's date 到一个文件中.

  FileOutputStream f = new FileOutputStream("tmp");

  ObjectOutputStream s = new ObjectOutputStream(f);

  s.writeObject("Today");

  s.writeObject(new Date());

  s.flush();

  现在,让我们来了解ObjectInputStream这个类 。它与ObjectOutputStream 类似 。它 扩大DataInput接口 。ObjectInputStream中的 步骤镜像DataInputStream中读取Java 根本数据类型的公开 步骤 。readObject() 步骤从字节流中反序列化对象 。每次调用readObject() 步骤都返回流中下一个Object 。对象字节流并不传输类的字节码,而是包括类名及其签名 。readObject()收到对象时,JVM装入头中指定的类 。假如找不到这个类,则readObject()抛出ClassNotFoundException,假如需求传输对象数据和字节码,则 可以用RMI框架 。ObjectInputStream的其余 步骤用于定制反序列化过程 。