一 Java序列化和反序列化介绍
java序列化是在JDK 1.1中引入的,是Java内核的重要特性之一。
Java序列化将一个对象转换为流,也即一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、
有关对象的类型的信息和存储在对象中数据的类型。将序列化对象写入文件之后,可以从文件中读取出来,
并且对它进行反序列化。反序列化则是将对象流转换为实际程序中使用的Java对象的过程。
也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的
平台上反序列化该对象。序列化可以用于轻量级的持久化、通过 Sockets 进行传输、或者用于 Java RMI。
可序列化的对象需要实现java.io.Serializable接口或者java.io.Externalizable接口。
以实现Serializable接口为例,Serializable仅是一个标记接口,并不包含任何需要实现的具体方法。
实现该接口只是为了声明该Java类的对象是可以被序列化的。
实际的序列化和反序列化工作是通过ObjectOuputStream和ObjectInputStream来完成的。
ObjectOutputStream的writeObject方法可以把一个Java对象写入到流中,
ObjectInputStream的readObject方法可以从流中读取一个Java对象。
在写入和读取的时候,虽然用的参数或返回值是单个对象,但实际上操纵的是一个对象图,
包括该对象所引用的其它对象,以及这些对象所引用的另外的对象。
Java 会自动帮你遍历对象图并逐个序列化。除了对象之外,Java中的基本类型和数组
也是可以通过 ObjectOutputStream 和 ObjectInputStream 来序列化的。
注意,一个类的对象要想序列化成功,必须满足两个条件:
1)该类必须直接或间接实现 java.io.Serializable 对象。
2)该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
如果你想知道一个Java标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单,
只需要查看该类有没有直接或间接的实现java.io.Serializable接口。
二 序列化对象
ObjectOutputStream类用来序列化一个对象,如下的 SerializeDemo例子实例化了一个Employee对象,
并将该对象序列化到一个文件中。该程序执行后,就创建了一个名为employee.ser文件。
该程序没有任何输出,但是你可以通过代码研读来理解程序的作用。
注意:当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个.ser 扩展名。
Employee实体对象:
package com.lanhuigu.io.serializable;
import java.io.Serializable;
/**
* 实现序列化接口--Serializable
*/
public class Employee implements Serializable {
private static final long serialVersionUID = 8182014220967586096L;
/** 姓名 */
public String name;
/** 地址 */
public String address;
/** 年龄 */
public transient int age;
/** 编号 */
public int number;
}
关于serialVersionUID:
如果可序列化类未显式声明serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认serialVersionUID值,
如“Java(TM)对象序列化规范”中所述。不过,强烈建议所有可序列化类都显式声明serialVersionUID值,
原因是计算默认的serialVersionUID对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,
这样在反序列化过程中可能会导致意外的InvalidClassException。因此,为保证serialVersionUID值跨不同
java编译器实现的一致性,序列化类必须声明一个明确的serialVersionUID值。
还强烈建议使用 private 修饰符显示声明serialVersionUID(如果可能),
原因是这种声明仅应用于直接声明类--serialVersionUID 字段作为继承成员没有用处。
数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,
但是数组类没有匹配 serialVersionUID 值的要求。
SerializeDemo序列化例子:
package com.lanhuigu.io.serializable;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* 序列化
*/
public class SerializeDemo {
public static void main(String [] args) {
Employee e = new Employee();
e.name = "yi";
e.address = "beijing";
e.age = 27;
e.number = 101;
try (FileOutputStream fileOut = new FileOutputStream("C:\\mycode\\employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("序列化数据保存在: C:\\mycode\\employee.ser");
} catch(IOException i) {
i.printStackTrace();
}
}
}
该程序运行后,序列化对象保存位置C:\\mycode\\employee.ser。
三 反序列化对象
下面例子将C:\\mycode\\employee.ser对象反序列化为Employee对象。
package com.lanhuigu.io.serializable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 反序列化
*/
public class DeserializeDemo {
public static void main(String [] args) {
Employee e = null;
try (
FileInputStream fileIn = new FileInputStream("C:\\mycode\\employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)
) {
e = (Employee) in.readObject();
in.close();
fileIn.close();
} catch(IOException i) {
i.printStackTrace();
return;
} catch(ClassNotFoundException c) {
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("===反序列化===");
System.out.println("name: " + e.name);
System.out.println("address: " + e.address);
System.out.println("age: " + e.age);
System.out.println("number: " + e.number);
}
}
四 序列化总结
1)序列化就是对象转换为字节流和字节流转换为对象的过程,含序列化和反序列化两个过程。
2)序列化对象需要直接或间接实现Serializable接口。
3)序列化和反序列化工作是通过ObjectOuputStream和ObjectInputStream来生成流,并将流反序列化为对象。
4)声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态, transient代表对象的临时数据。