【Java基础之三】Java序列化

一、序列化简介

序列化是一种对象持久化的手段。普遍应用在网络传输、RMI(Remote Method Invocation)等场景中。Java提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

类ObjectInputStream和ObjectOutputStream是高层次的数据流,它们包含序列化和反序列化对象的方法。ObjectOutputStream类包含很多写方法来写各种数据类型,但是一个特别的方法例外:

public final void writeObject(Object x) throws IOException

上面的方法序列化一个对象,并将它发送到输出流。相似的ObjectInputStream类包含如下反序列化一个对象的方法:

public final Object readObject() throws IOException, ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。

二、序列化简单例子

例子很简单,直接上代码,包含两个文件。

Employee.java代码:

package com.shuidishichuan.demo;

import java.io.Serializable;

public class Employee implements Serializable {

    public String name;
    public String address;
    public int SSN;
    public int number;

    public void mailCheck(){
        System.out.println("Mailing a check to " + name
                              + " " + address);
    }
}

SerializeDemo.java代码:

package com.shuidishichuan.demo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeDemo {

    public static void main(String[] args) {
        Employee ee = new Employee();
        ee.name = "Reyan Ali";
        ee.address = "Phokka Kuan, Ambehta Peer";
        ee.SSN = 11122333;
        ee.number = 101;

        try {
            //Serialize
            FileOutputStream fOutputStream = new FileOutputStream("tmp/employee.ser");
            ObjectOutputStream  objectOutputStream = new ObjectOutputStream(fOutputStream);
            objectOutputStream.writeObject(ee);
            objectOutputStream.flush();
            objectOutputStream.close();
            fOutputStream.close();

            //Deserialized
            FileInputStream  fFileInputStream = new FileInputStream("tmp/employee.ser");
            ObjectInputStream fObjectInputStream = new ObjectInputStream(fFileInputStream); 
            Employee deserializedEmployee = (Employee) fObjectInputStream.readObject();
            fObjectInputStream.close();
            fFileInputStream.close();

            System.out.println("Deserialized Employee...");
            System.out.println("Name: " + deserializedEmployee.name);
            System.out.println("Address: " + deserializedEmployee.address);
            System.out.println("SSN: " + deserializedEmployee.SSN);
            System.out.println("Number: " + deserializedEmployee.number);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出:

Deserialized Employee...
Name: Reyan Ali
Address: Phokka Kuan, Ambehta Peer
SSN: 11122333
Number: 101

三、序列化算法

序列化算法一般会按步骤做如下事情:
◆ 将对象实例相关的类元数据输出。
◆ 递归地输出类的超类描述直到不再有超类。
◆ 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
◆ 从上至下递归输出实例的数据

注释:所谓元数据主要指数据类型,对象类型等

四、影响序列化因素

4.1 transient关键字

当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Employee类中的SSN字段声明为transient,如下所示:

public transient int SSN;

重新运行程序,输出如下:

Deserialized Employee...
Name: Reyan Ali
Address: Phokka Kuan, Ambehta Peer
SSN: 0
Number: 101

注意,SSN字段已经被初始化为0.

4.2 writeObject()方法与readObject()方法

在Employee类中加入如下方法:

private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();
    out.writeInt(SSN);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    SSN = in.readInt();
}

运行,输出:

Deserialized Employee...
Name: Reyan Ali
Address: Phokka Kuan, Ambehta Peer
SSN: 11122333
Number: 101

很奇怪,定义的方法是private的,什么时候被调用的?以writeObject方法为例,设置断点后得出如下堆栈:

Employee.writeObject(ObjectOutputStream) line: 16   
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39  
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25  
Method.invoke(Object, Object...) line: 597  
ObjectStreamClass.invokeWriteObject(Object, ObjectOutputStream) line: 945   
ObjectOutputStream.writeSerialData(Object, ObjectStreamClass) line: 1461    
ObjectOutputStream.writeOrdinaryObject(Object, ObjectStreamClass, boolean) line: 1392   
ObjectOutputStream.writeObject0(Object, boolean) line: 1150 
ObjectOutputStream.writeObject(Object) line: 326    
SerializeDemo.main(String[]) line: 22   

从中可以看出,是采用Java反射的方式进行调用的,所以定义成private也是可以调用的。

4.3 Externalizable接口

继续在Employee类中加入如下方法:

public Employee(){
    System.out.println("Employee constructor ");
}

@Override
public void writeExternal(ObjectOutput out) throws IOException {

}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

}

运行,输出:

Employee constructor
Employee constructor
Deserialized Employee...
Name: null
Address: null
SSN: 0
Number: 0

从该结果,一方面可以看出Employee对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这次序列化过程调用了Employee类的无参构造器。

Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。

另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Employee类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

将代码做如下添加,如下:

public Employee(){
    System.out.println("Employee constructor ");    
}

@Override
public void writeExternal(ObjectOutput out) throws IOException {
    out.writeObject(name);
    out.writeObject(address);
    out.writeInt(SSN);
    out.writeInt(number);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    name = (String) in.readObject();
    address = (String) in.readObject();
    SSN = in.readInt();
    number = in.readInt();
}

运行,输出:

Employee constructor 
Employee constructor 
Deserialized Employee...
Name: Reyan Ali
Address: Phokka Kuan, Ambehta Peer
SSN: 11122333
Number: 101

嘿嘿,见证奇迹的时刻。

五、单例模式序列化-readResolve()

添加如下单例类继续进行测试:

package com.shuidishichuan.demo;

import java.io.Serializable;

public class Singleton implements Serializable {

    private static Singleton sInstanceSingleton = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance(){
        return sInstanceSingleton;
    }

}
package com.shuidishichuan.demo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeDemo {

    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();

        try {
            //Serialize
            FileOutputStream fOutputStream = new FileOutputStream("tmp/employee.ser");
            ObjectOutputStream  objectOutputStream = new ObjectOutputStream(fOutputStream);
            objectOutputStream.writeObject(instance);
            objectOutputStream.flush();
            objectOutputStream.close();
            fOutputStream.close();

            //Deserialized
            FileInputStream  fFileInputStream = new FileInputStream("tmp/employee.ser");
            ObjectInputStream fObjectInputStream = new ObjectInputStream(fFileInputStream); 
            Singleton deserializedSingleton = (Singleton) fObjectInputStream.readObject();
            fObjectInputStream.close();
            fFileInputStream.close();

            System.out.println(deserializedSingleton==Singleton.getInstance());

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行,输出:

false

将单例类Singleton添加如下方法:

private Object readResolve() throws ObjectStreamException {
    return sInstanceSingleton;
}

运行,输出:

true

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

六、参考文献

JAVA序列化机制的深入研究
深入分析Java的序列化与反序列化
理解Java对象序列化
Java 序列化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值