java 序列化

Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。

为什么需要序列化与反序列化

我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
实现序列化的要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。
只有实现了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方法进行反序列化。
把Java对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为Java对象的过程称为对象的反序列化。
java中引入序列化机制主要是为了支持两种重要技术:RMI和JavaBean技术。

只有实现了Serializable和Externalizable接口的类的对象才能被序列化。
      1)Serializable接口可使类中的所有成员变量自动被序列化(transient和static修饰的变量除外),默认的
           序列化方式会序列化整个对象图,这需要递归遍历对象图。如果对象图很复杂,递归遍历操作需要消耗很多
           的空间和时间,它的内部数据结构为双向列表。在应用时,如果对某些成员变量都改为transient类型,将
           节省空间和时间,提高序列化的性能。
      2)Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化
           的行为,即Externalizable对象默认情况下不保存任何它的字段,而仅实现Serializable接口的类可以
           采用默认的序列化方式 。
   那我们如何对一个Serializable对象的序列化和反序列化行为进行控制呢?
      1)加transient修饰符,这样改变量就不会被序列化了
      2)添加(不是“实现”和“重载” )writeObject和readObject方法: private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectOutputStream stream) throws IOException;

     这样一旦对象被序列化或者被反序列化,就会自动分别调用这两个方法,而不会调用默认的序列化和反序列化方法。(这一点确实感觉有点奇怪,或者叫混乱!)
    当ObjectOutputStream对一个Serializable对象进行序列化时,如果该对象具有writeObject()方法,那么就会执行这一方法,否则就按默认方式序列化。在该对象的writeObjectt()方法中,可以先调用ObjectOutputStream的defaultWriteObject()方法,使得对象输出流先执行默认的序列化操作。同理可得出反序列化的情况,不过这次是defaultReadObject()方法。
对Serializable对象反序列化时,并不会调用任何构造函数 ,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。
对Externalizable对象反序列化时,会先调用类的不带参数的构造方法 ,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
对象序列化包括如下步骤:
  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
  2) 通过对象输出流的writeObject()方法写对象。

对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数)。否则编译的时候就会报错。在反串行化的时候,默认构造函数会被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。这是为什么呢?这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。

serialVersionUID
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)


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

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可以看出)

看一段代码,证明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:
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]
以上部分转自http://www.cnblogs.com/rollenholt/archive/2012/11/26/2789445.html

你可能感兴趣的:(java 序列化)