java 序列化和反序列化总结

序列化:将java对象转换为字节序列的过程叫做序列化

反序列化:将字节对象转换为java对象的过程叫做反序列化

通常情况下,序列化有两种用途:、

1) 把对象的字节序列永久的保存在硬盘中

2)在网络上传输对象的字节序列

相应的API

  java.io.ObjectOutputStream

          writeObject(Object obj)

  java.io.ObjectInputStream

          readObject()

只有实现了Serializable或者Externalizable接口的类的对象才能够被序列化。否则当调用writeObject方法的时候会出现IOException。

需要注意的是Externalizable接口继承自Serializable接口。两者的区别如下:

  仅仅实现Serializable接口的类可应采用默认的序列化方式。比如String类。

    假设有一个Customer类的对象需要序列化,如果这个类仅仅实现了这个接口,那么序列化和反序列化的方式如下:ObjectOutputStream采用默认的序列化方式,对于这个类的非static,非transient的实例变量进行序列化。ObjectInputStream采用默认的反序列化方式,对于这个类的非static,非transient的实例变量进行反序列化。

    如果这个类不仅实现了Serializable接口,而且定义了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。

  实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeExternal方法进行序列化,ObjectInputStream会调用相应的readExternal方法进行反序列化。

下面来看一个最简单的例子:

package com.java;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class simpleSerializableTest {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj"));
         
        String strObj="name";
        Customer customer=new Customer("rollen");
        //序列化,此处故意将同一对象序列化2次
        out.writeObject(strObj);
        out.writeObject(customer);
        out.writeObject(customer);
        out.close();
        //反序列化
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj"));
        String strobj1=(String)in.readObject();
        Customer cus1=(Customer)in.readObject();
        Customer cus2=(Customer)in.readObject();<br>            in.close();
        System.out.println(strobj1+": "+cus1);
        System.out.println(strObj==strobj1);
        System.out.println(cus1==customer);
        System.out.println(cus1==cus2);
    }
}
 
class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
 
    public Customer() {
        System.out.println("无参构造方法");
    }
 
    public Customer(String name) {
        System.out.println("有参构造方法");
        this.name = name;
    }
 
    public String toString() {
        return "[ "+name+" ]";
    }
     
}

输出结果为:

有参构造方法
name: [ rollen ]
false
false
true

可以看出,在进行反序列话的时候,并没有调用类的构造方法。而是直接根据他们的序列化数据在内存中创建新的对象。另外需要注意的是,如果由一个ObjectOutputStream对象多次序列化同一个对象,那么右一个objectInputStream对象反序列化后的也是同一个对象。(cus1==cus2结果为true可以看出)

  当ObjectOutputStream按照默认方式反序列化时,具有如下特点:
  1) 如果在内存中对象所属的类还没有被加载,那么会先加载并初始化这个类。如果在classpath中不存在相应的类文件,那么会抛出ClassNotFoundException;

  2) 在反序列化时不会调用类的任何构造方法。

注意:

  • 反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供Java对象所属的class文件,否则会引发ClassNotFoundException异常; 
  • 如果我们向文件中使用序列化机制写入了多个Java对象,使用反序列化机制恢复对象必须按照实际写入的顺序读取。
  • 当一个可序列化类有多个父类时(包括直接父类和间接父类),这些父类要么有无参的构造器,要么也是可序列化的—否则反序列化将抛出InvalidClassException异常。如果父类是不可序列化的,只是带有无参数的构造器,则该父类定义的Field值不会被序列化到二进制流中;

看一段代码,证明static是不会被序列化的:

package com.java;
 
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
 
public class SerializableServer {
    public void send(Object obj) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8000);
        while (true) {
            Socket socket = serverSocket.accept();
            ObjectOutputStream out = new ObjectOutputStream(
                    socket.getOutputStream());
            out.writeObject(obj);
            out.writeObject(obj);
            out.close();
            socket.close();
        }
    }
 
    public static void main(String[] args) throws Exception {
 
        Customer customer = new Customer("rollen", "male");
        new SerializableServer().send(customer);
    }
}
 
class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private static int count;
    private transient String sex;
 
    static {
        System.out.println("调用静态代码块");
    }
 
    public Customer() {
        System.out.println("无参构造方法");
    }
 
    public Customer(String name, String sex) {
        System.out.println("有参构造方法");
        this.name = name;
        this.sex = sex;
        count++;
 
    }
 
    public String toString() {
        return "[ " + count + " " + name + " " + sex + " ]";
    }
}
package com.java;
 
import java.io.ObjectInputStream;
import java.net.Socket;
 
public class SerializableClient {
    public void recive() throws Exception {
        Socket socket = new Socket("localhost", 8000);
        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
        Object obj1 = in.readObject();
        Object obj2 = in.readObject();
        System.out.println(obj1);
        System.out.println(obj1==obj2);
    }
 
    public static void main(String[] args) {
        try {
            new SerializableClient().recive();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  运行结果中,count的值为0.

我们来看另外一种情况:

class A implements Serializable{
    B b;
    //...
}
 
class B implements Serializable{
    //...
}
  当我们在序列化A的对象的时候,也会自动序列化和他相关联的B的对象。也就是说 在默认的情况下,对象输出流会对整个对象图进行序列化 。因此会导致出现下面的问题,看代码(例子中是使用双向列表作为内部结构的,只是给出了demo,并没有完整的实现,只是为了说明情况):

package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriListTest implements Serializable {
    private static final long serialVersionUID = 1L;
    private int size;
    private Node head = null;
    private Node end = null;
 
    private static class Node implements Serializable {
        private static final long serialVersionUID = 1L;
        String data;
        Node next;
        Node previous;
    }
 
    // 列表末尾添加一个字符串
    public void add(String data) {
        Node node = new Node();
        node.data = data;
        node.next = null;
        node.previous = end;
        if (null != end) {
            end.next = node;
        }
        size++;
        end = node;
        if (size == 1) {
            head = end;
        }
    }
 
    public int getSize() {
        return size;
    }
 
    // other methods...
 
    public static void main(String[] args) throws Exception {
        SeriListTest list = new SeriListTest();
        for (int i = 0; i < 10000; ++i) {
            list.add("rollen" + i);
        }
 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(list);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        list = (SeriListTest) in.readObject();
        System.out.println("size is :" + list.getSize());
    }
 
}

  这段代码会出现如下错误:

Exception in thread "main" java.lang.StackOverflowError
  at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
  at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
      ....

整个就是因为序列化的时候,对整个对象图进行序列化引起的问题。在这种情况下啊,我们需要自定义序列化的过程:

package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriListTest implements Serializable {
    private static final long serialVersionUID = 1L;
    transient private int size;
    transient private Node head = null;
    transient private Node end = null;
 
    private static class Node implements Serializable {
        private static final long serialVersionUID = 1L;
        String data;
        Node next;
        Node previous;
    }
 
    // 列表末尾添加一个字符串
    public void add(String data) {
        Node node = new Node();
        node.data = data;
        node.next = null;
        node.previous = end;
        if (null != end) {
            end.next = node;
        }
        size++;
        end = node;
        if (size == 1) {
            head = end;
        }
    }
 
    public int getSize() {
        return size;
    }
 
    // other methods...
 
    private void writeObject(ObjectOutputStream outStream) throws IOException {
        outStream.defaultWriteObject();
        outStream.writeInt(size);
        for (Node node = head; node != null; node = node.next) {
            outStream.writeObject(node.data);
        }
    }
 
    private void readObject(ObjectInputStream inStream) throws IOException,
            ClassNotFoundException {
        inStream.defaultReadObject();
        int count = inStream.readInt();
        for (int i = 0; i < count; ++i) {
            add((String) inStream.readObject());
        }
    }
 
    public static void main(String[] args) throws Exception {
        SeriListTest list = new SeriListTest();
        for (int i = 0; i < 10000; ++i) {
            list.add("rollen" + i);
        }
 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(list);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        list = (SeriListTest) in.readObject();
        System.out.println("size is :" + list.getSize());
    }
 
}

  运行结果为:10000

现在我们总结一下,在什么情况下我们需要自定义序列化的方式:

  1)为了确保序列化的安全性,对于一些敏感信息加密

  2)确保对象的成员变量符合正确的约束条件

  3)优化序列化的性能(之前的那个例子已经解释了这种情况)

下面我们来用例子解释一下这些:

先来看看:为了确保序列化的安全性,对于一些敏感信息加密

package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriDemo1 implements Serializable {
    private String name;
    transient private String password; // 注意此处的transient
 
    public SeriDemo1() {
    }
 
    public SeriDemo1(String name, String password) {
        this.name = name;
        this.password = password;
    }
 
    // 此处模拟对密码进行加密,进行了简化
    private String change(String password) {
        return password + "rollen";
    }
 
    private void writeObject(ObjectOutputStream outStream) throws IOException {
        outStream.defaultWriteObject();
        outStream.writeObject(change(password));
    }
 
    private void readObject(ObjectInputStream inStream) throws IOException,
            ClassNotFoundException {
        inStream.defaultReadObject();
        String strPassowrd = (String) inStream.readObject();
        //此处模拟对密码解密
        password = strPassowrd.substring(0, strPassowrd.length() - 6);
    }
 
    @Override
    public String toString() {
        return "SeriDemo1 [name=" + name + ", password=" + password + "]";
    }
 
    public static void main(String[] args) throws Exception {
        SeriDemo1 demo = new SeriDemo1("hello", "1234");
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        demo = (SeriDemo1) in.readObject();
        System.out.println(demo);
    }
}

  然后我们看看:确保对象的成员变量符合正确的约束条件。比如一般情况下我们会在构造函数中对于参数进行合法性检查,但是默认的序列化并不会调用类的构造函数,直接由对象的序列化数据来构造出一个对象,这个我们就有可能提供遗传非法的序列化数据,来构造一个不满足约束条件的对象。

为了避免这种情况,我们可以自定义反序列话的方式。比如在readObject方法中,进行检查。当数据不满足约束的时候(比如年龄小于0等等不满足约束的情况),可以抛出异常之类的。

接下来我们看看readResolve()方法在单例模式中的使用:

单例模式大家应该都清楚,我就不多说了,看看下面的代码:

package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ReadResolveDemo implements Serializable {
    private static final long serialVersionUID = 1L;
 
    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return new ReadResolveDemo();
    }
    public static void main(String[] args) throws Exception {
        ReadResolveDemo demo=ReadResolveDemo.getInstance();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
        System.out.println(demo==demo1); //false
    }
}

  本来单例模式中,只有一个实例,但是在序列化的时候,无论采用默认的方式,还是自定义的方式,在反序列化的时候都会产生一个新的对象,所以上面的程序运行输出false。

因此可以看出反序列化打破了单例模式只有一个实例的约定,为了避免这种情况,我们可以使用readReslove

无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。

package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ReadResolveDemo implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();
 
    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return INSTANCE;
    }
 
    private Object readResolve() {
        return INSTANCE;
    }
 
    public static void main(String[] args) throws Exception {
        ReadResolveDemo demo = ReadResolveDemo.getInstance();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
        System.out.println(demo == demo1); // true
    }
}

  最后我们简单的说一下实现Externalizable接口。 实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。

注意在对实现了这个接口的对象进行反序列化的时候,会先调用类的不带参数的构造函数,这个和之前的默认反序列化方式是不一样的。

例子如下:

package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
 
public class ExternalizableDemo implements Externalizable {
    private String name;
    static {
        System.out.println("调用静态代码块");
    }
 
    public ExternalizableDemo() {
        System.out.println("调用默认无参构造函数");
    }
 
    public ExternalizableDemo(String name) {
        this.name = name;
        System.out.println("调用有参构造函数");
    }
 
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
    }
 
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        name = (String) in.readObject();
    }
 
    @Override
    public String toString() {
        return "[" + name + "]";
    }
 
    public static void main(String[] args) throws Exception {
        ExternalizableDemo demo = new ExternalizableDemo("rollen");
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        demo = (ExternalizableDemo) in.readObject();
        System.out.println(demo);
    }
}

  输出:

调用静态代码块
调用有参构造函数
调用默认无参构造函数
[rollen]

SerialVersionUID的用途:

  类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高哦啊serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:
  1) 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
  2) 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

案例1 - 如果对象引用了其他对象,那该如何

我们已经看过最简单的序列化例子,现在看看,如何处理对象中引用了其他对象的场合。我们该如何序列化?引用对象也会被序列化吗?对的,你不需要显式的序列化引用对象。当你序列化任何对象,如果它包含引用对象,那么Java序列化会自动序列化该对象的整个对象图。例如,Employee现在引用了一个address对象,并且Address也引用了其他对象(例如,Home),那么当你序列化Employee对象的时候,所有其他引用对象,例如address和home将会被自动地被序列化。让我们来创建Address类,并它Address的对象作为引用,添加到employee类中。

案例2:如果我们不能访问引用对象的源代码(例如,你不能访问上面的Address类的源码)

如果我们不能访问到address类,那么我们该如何在Address类中实现serializable 接口?是否有另外的途径来实现呢?对的,你可以创建另外一个类,并继承Address,然后让它继承serializable 接口,但是对于下面的情况,这个方案会失败:

  • 如果引用类被定义为final
  • 如果引用类引用了另外一个非可序列化的对象

那么,我们该如何序列化Employee对象?解决的办法是,标记transient。如果你不需要序列化任何字段,只需把它标记为transient。

在Employee类中,标记了address为transient之后,运行程序。你会得到nullPointerException,因为在反序列化过程中,Address引用将会是null。

案例3 - 如果我仍然需要保存引用对象的状态呢?(例如address对象)

如果你在反序列化过程中,标记了address为transient,它将会返回null结果。但是如果你仍然需要保存它的状态,你就需要序列化address对象。 Java序列化提供一个机制,如果你有特定签名的private方法,那么它们就会在序列化和反序列化过程中被调用,所以我们将重写Employee类的writeObject和readObject方法,然后它们就会在Employee对象序列化/反序列化过程中被调用。

序列化中的继承:

现在我们看看继承是如何影响序列化的。不管父类是不是可序列化,这将引出很多个例子。如果父类是非可序列化的,我们将如何处理,并且它是如何工作的。让我们看看例子。

我们将创建一个Person.java,作为 Employee的父类。

案例4: 如果父类是可序列化的

如果父类可序列化,那么所有的继承类将是可序列化的。

案例5: 如果父类为非可序列化呢?

如果父类为非可序列化的 ,那么我们的处理办法会很不一样。

  • 如果父类为非可序列化的,那么它必然不会有参数构造函数。

案例6 - 如果父类是可序列化,但你不需要继承类为可序列化

如果你不希望继承类为可序列化,那么你需要实现 writeObject() 和readObject() 方法,并且需要抛出NotSerializableException 异常。

案例7 - 可否序列化静态变量?

不能。因为静态变量是类级别的,不是对象级别的,当你序列化一个对象的时候,是不能序列化静态变量。

总结:
序列化是java对象的values/states转化为字节并在网络中传输或者保存它的过程。另外反序列化是把字节码翻译为对应的对象的过程。
序列化的好处是,JVM的独立性,也就是说,一个对象可以在一个平台中被序列化,然后在另外一个不同的平台反序列化。
如果你需要序列化任何对象,你必须实现标记接口Serializable。
Java中的标记接口(Marker interface)就是没有字段或者方法的接口,或者更简单的说,空接口
serialVersionUID 是用于保证同一个对象(在序列化中会被用到)可以在Deserialization过程中被载入。serialVersionUID 是用于对象的版本控制。
当你需要序列化任何包含引用对象的对象,那么Java会自动序列化该对象的整个对象图。
如果你不希望序列化某个字段,你可以标记它为trasient
如果父类为可序列化,那么它的继承类也将是可序列化的。
如果父类为非可序列化,那么在反序列化过程中,所有继承于父类的实例变量值将会通过调用非可序列化的构造器来初始化。
如果你需希望子类为可序列化的,那么你需要实现writeObject() 和 readObject() 方法,并在这两个方法中抛出NotSerializableException异常
你不能序列化静态变量。

 参考资料:

1.java序列化高级认识

转载自:

http://www.cnblogs.com/rollenholt/archive/2012/11/26/2789445.html

http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html

参考:

http://www.cnblogs.com/rollenholt/archive/2012/11/12/2766676.html(序列化概述)

http://blog.csdn.net/p106786860/article/details/17953223(比较全面)

http://www.ibm.com/developerworks/cn/java/j-lo-serial/(IBM:序列化实战问题及案例分析)

http://www.oschina.net/translate/serialization-in-java(如上7个案例进行详细分析)

http://www.infoq.com/cn/articles/cf-java-object-serialization-rmi(RMI与对象序列化)

http://blog.csdn.net/moreevan/article/details/6698529(对象序列化中的小问题)

http://blog.csdn.net/kaoa000/article/details/8490065(对象序列化总结)

http://blog.csdn.net/mazhimazh/article/details/20292331(对象序列化中的小问题)

http://zhupan.iteye.com/blog/26447(对象序列化要点总结)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值