Java序列化(Serialization)

  关于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接口并添加两个序列化方法,并自定义实现方法。

 

posted @ 2016-01-20 22:11  苍穹2018  阅读(533)  评论(0编辑  收藏  举报