关于Java的序列化的文章在网上已经够多了,在这里写关于Java序列化的文章是对自己关于这方面的的一种总结,结合以前的开发经验与网上的资料,写了这篇文章,对自己是有着巩固记忆的作用,也希望能够对大家有一定帮助。

一、什么是序列化(Serialization)?

  序列化是Java提供的一种机制,将对象转化成字节序列,在字节序列中保存了对象的数据、对象的类型的信息与存储在对象中的数据的类型。序列化实际上就是将保存对象的"状态",可以方便以后的程序使用或者通过网络传输到另一台主机使用。一般来说,对象的生成周期取决于程序是否在执行,而序列化能够将对象保存在磁盘或网络中,这样对象就能生存在程序的调用之间。

 

二、序列化的目的

  用序列化来保存对象的状态,主要是为了支持两大特性:

  1. Java的远程方法调用(RMI),它能够存活于其他机器上的对象使用起来就像存活于本地一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
  2. 在Java Beans中,必须使用对象序列化。

三、序列化基本实例

  Java中实现序列化最基本的方法是实现Serializable接口,Serializable接口是标记接口,不包含任何方法。要序列化一个对象,必须先创建某些OutoutStream对象,然后封装到ObjectOutputStream对象内,其中ObjectOutputStream提供writeObject()方法,调用该方法即可将对象序列化。

  反序列化是指将对象序列化生成的字节序列还原成对象,需要将InputStream对象封装在ObjectInputStream对象中,然后调用readObject即可读出对象。

  下面是对象序列化的基本实例:



class Pet implements Serializable {
    private String name;

    public Pet(String name) {
        this.name = name;
    }

    public String toString() {
        return name; } } public class Person implements Serializable { private static final long serialVersionUID = 1L; private int age; private String name; private Pet ownPet; public Person(String name, int age, Pet ownPet) { this.name = name; this.age = age; this.ownPet = ownPet; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + ", ownPet=" + ownPet + "]"; } public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { Person p1 = new Person("person1 ", 47, new Pet("dog")); Person p2 = new Person("person2 ", 34, new Pet("cat")); // 对象序列化 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream( "person.out")); System.out.println("Save objects:"); out.writeObject(p1); out.writeObject(p2); out.close(); // 对象反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream( "person.out")); System.out.println("Recovering objects:"); p1 = (Person) in.readObject(); p2 = (Person) in.readObject(); System.out.println(p1); System.out.println(p2); } }



  上述程序结果是:



Save objects:
Recovering objects:
Person [age=47, name=person1 , ownPet=dog]
Person [age=34, name=person2 , ownPet=cat]



  从上面的结果看出,对象序列化不仅将调用writeObject()的对象序列化,而且通过追踪调用writeObject()的对象的引用,并保存那些引用的对象,所以对象序列化能够自动保存对象的相关引用对象,能够保证对象信息的完整性,因此能够使用对象序列化进行对象的深度复制。

  注意事项:实现Serializable接口对象反序列化并没有调用构造器,是直接将字节序列还原成对象。在序列化过程中必须保证classpath中必须有该类,在本实例即Person.class,否则抛出ClassNotFoundException。

三、实现Externalizable接口

  序列化的另一种方法是实现Externalizable接口,Externalizable接口提供以下默认方法:



void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;



  用户必须使用自己实现这两个方法,在方法中可以控制序列化,如可以让一些成员属性不进行序列化,实例如下:



public class Test implements Externalizable { private int i; private String s; public Test() { System.out.println("Test default constructor"); } public Test(String x, int a) { System.out.println("Test(String x, int a)"); s = x; i = a; } public String toString() { return s + i; } public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Blip3.writeExternal"); // You must do this:  out.writeObject(s); out.writeInt(i); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Blip3.readExternal"); // You must do this: s = (String) in.readObject(); i = in.readInt(); } public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { System.out.println("Constructorint objects:"); Test test = new Test("A String ", 47); System.out.println(b3); // 对象序列化 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream( "test.out")); System.out.println("Save objects:"); out.writeObject(b3); out.close(); // 对象反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream( "test.out")); System.out.println("Recovering test:"); b3 = (Test) in.readObject(); } }



  上述程序运行结果为:


Constructorint objects:
Test(String x, int a)
A String 47
Save objects:
Test.writeExternal
Recovering test:
Test default constructor
Test.readExternal


  从结果中能够看出为了能实现对象的序列化,不仅必须在writeExternal()方法中写入对象成员变量,而且要在readExternal()方法中读取成员变量。在对象的反序列化过程中调用了默认的构造方法。

四、transient关键词

  transient能够对成员变量进行控制,防止敏感信息泄漏,当然上面的实现Externalizable的方法也可以完成该功能,但是必须自己控制所有的属性进行序列化,比较复杂。而采用transient(瞬时)关键词就能关闭该字段的序列化,如下实例所示:



public class Logon implements Serializable {
    private Date date = new Date();
    private String username;
    private transient String password;

    public Logon(String name, String password) {
        this.username = name; this.password = password; } public String toString() { return "Logon info: \n username: " + username + "\n date: " + date + "\n password: " + password; } public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { Logon a = new Logon("Hulk", "myLittlePony"); System.out.println("logon a =" + a); ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream( "out/Logon.out")); o.writeObject(a); o.close(); // 获取序列化的数据 ObjectInputStream in = new ObjectInputStream(new FileInputStream( "out/Logon.out")); System.out.println("Recovering object at " + new Date()); a = (Logon) in.readObject(); System.out.println("logon a =" + a); } }

  上述程序的结果为:



logon a = Logon info: 
 username: Hulk
 date: Wed Jan 20 21:55:16 CST 2016
 password: myLittlePony
Recovering object at Wed Jan 20 21:55:16 CST 2016
logon a = Logon info: 
 username: Hulk
 date: Wed Jan 20 21:55:16 CST 2016
 password: null



  从结果中,可知对password字段进行了隐藏,没有进行序列化。

五、代替实现Externalizable

  在对象序列化过程中对序列化控制,如果采用实现Externalizable接口的方式,必须实现每一个属性的序列化控制,太过复杂,那如何替代它呢?通过实现Serializable接口并添加两个方法,只要提供这两个方法,就会使用它们而不是默认的序列化机制。这两个方法签名如下所示:



private void writeObject(ObjectOutputStream stream) throws IOException

  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException



  在上面两个方法中能够调用默认的处理方法,也可以自己控制属性的序列化,实例代码如下:



public class SerialCtl implements Serializable {
    private String a;
    private transient String b;

    public SerialCtl(String aa, String bb) {
        this.a = "Not trnsient: " + aa;
        this.b = "Transient: " + bb; } public String toString() { return a + "\n" + b; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(b); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); b = (String) stream.readObject(); } public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { SerialCtl sc = new SerialCtl("Test1", "Test2"); System.out.println("Before:\n " + sc); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(buf); o.writeObject(sc); // 获取序列化的数据 ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( buf.toByteArray())); sc = (SerialCtl) in.readObject(); System.out.println("After:\n " + sc); } }



  上述程序的结果:



Before: Not trnsient: Test1 Transient: Test2 After: Not trnsient: Test1 Transient: Test2



六、总结

  综上所述,最简单的序列化就是实现Serializable接口,如果为了敏感信息的安全,可以使用transient关键字。如果想近一步控制序列化过程,建议实现Serializable接口并添加两个序列化方法,并自定义实现方法。