Java对象序列化使用基础


    所谓对象序列化便是将对象的状态转换成字节流,以后 可以通过这些值再生成 雷同状态的对象 。这个过程也 可以通过网络实现, 可以先在Windows机器上 缔造一个对象,对其序列化, 而后通过网络发给一台Unix机器, 而后在那里精确无误地再一次" 拆卸" 。像RMI、Socket、JMS、EJB它们中的一种,彼此为何 可以传递Java对象,固然都是对象序列化机制的 功绩 。  

    Java对象序列化机制普通来讲有两种 用处:

    Java的JavaBeans: Bean的状态信息通常是在设计时配置的,Bean的状态信息必须被存起来,以 容易程序运行时能 复原这些状态信息,这需求将对象的状态 保留到文件中,而后 可以通过读入对象状态来再一次 构造对象, 复原程序状态 。

    RMI同意象在本机上一样操作远程机器上的对象;或 使用套接字在网络上 传递对象的程序来说,这些都是需求实现serializaiton机制的 。

    我们通过让类实现Java.io.Serializable 接口 可以将类序列化 。这个接口是一个创造者(marker)接口 。也便是说,关于要实现它的类来说,该接口不需求实现任何 步骤 。它主要用来 告诉Java 虚构机(JVM),需求将一个对象序列化 。

    关于这个,有几点我们需求明确:

    并非全部类都 可以序列化,在cmd下,我们输入serialver Java.net.Socket, 可以得到socket是不是可序列化的信息,实际上socket是不可序列化的 。

    Java有众多 根底类已经实现了serializable接口, 比方string,vector等 。然而 比方hashtable就没有实现serializable接口 。

    将对象读出或者写入流的主要类有两个: ObjectOutputStream与ObjectInputStream .ObjectOutputStream 提供用来将对象写入输出流的writeObject 步骤, ObjectInputStream提供从输入流中读出对象的readObject 步骤 。 使用这些 步骤的对象必须已经被序列化的 。也便是说,必须已经实现 Serializable接口 。假如你想writeobject一个hashtable对象,那么,会得到一个 异样 。

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

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

    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, 歹意类 可以用这些 步骤读取和写入对象数据 。假如对象包括敏感信息,则要分外小心 。这包括 使用安全套接或加密整个字节流 。到此为至,我们学习了序列化的 根底 部分 常识 。