【JAVA】序列化与反序列化
0x01 概述
什么是序列化,简单的来说,序列化就是为了保存对象的状态;而反序列化就是把保存的对象状态再读出来。
使用场景:
- 当想把内存中的对象状态保存到一个文件或者数据库中的时候
- 当想用套接字在网络上传送对象的时候
- 当想通过RMI传输对象的时候
0x02 Java支持序列化种类
Java支持的序列化有三种
- 自定义实现Serializable接口的类
- Java的基本类型
- Java自带的实现了Serializable接口的类
下面用程序展示着三种情况
支持自定义实现Serializable接口的类:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerialTest1 {
private static final String TMP_FILE = ".serialtest1.txt";
public static void main (String args[]) {
testWrite();
testRead();
}
private static void testWrite() {
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(TMP_FILE));
Box box = new Box("desk", 80, 48);
out.writeObject(box);
System.out.println("testWrite box:" + box);
out.close();
}catch (Exception ex) {
ex.printStackTrace();
}
}
private static void testRead() {
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(TMP_FILE));
Box box = (Box)in.readObject();
System.out.println("testRead box:" + box);
in.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
class Box implements Serializable {
private int width;
private int height;
private String name;
public Box (String name, int width, int height) {
this.name = name;
this.height = height;
this.width = width;
}
public String toString () {
return "[" + name + ": (" + width + ", " + height + ") ]";
}
}
运行结果:
支持java的基本类型和自带的实现了Serializable接口的类
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
public class SerialTest2 {
private static final String TMP_FILE = ".serialabletest2.txt";
public static void main(String[] args) {
testWrite();
testRead();
}
private static void testWrite() {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(TMP_FILE));
out.writeBoolean(true);
out.writeByte((byte)65);
out.writeChar('a');
out.writeInt(20131015);
out.writeFloat(3.14F);
out.writeDouble(1.414D);
HashMap map = new HashMap();
map.put("one", "red");
map.put("two", "green");
map.put("three", "blue");
out.writeObject(map);
out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static void testRead() {
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream(TMP_FILE));
System.out.printf("boolean:%b\n" , in.readBoolean());
System.out.printf("byte:%d\n" , (in.readByte()&0xff));
System.out.printf("char:%c\n" , in.readChar());
System.out.printf("int:%d\n" , in.readInt());
System.out.printf("float:%f\n" , in.readFloat());
System.out.printf("double:%f\n" , in.readDouble());
HashMap map = (HashMap) in.readObject();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
System.out.printf("%-6s -- %s\n" , entry.getKey(), entry.getValue());
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果
这里HashMap是java.util包中定义的类,它属于java自带的实现Serializable接口的类,它的接口声明如下:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
0x03 序列化中的特例
从上面说得,我们知道序列化/反序列化,只支持保存/恢复对象状态,即仅支持保存/恢复类的成员变量,但不支持保存类的成员方法,但是,序列化是不是对类的所有的成员变量的状态都能保存呢?答案是否定的。
- 序列化对static和transient变量,是不会自动进行状态保存的。
transient的作用就是,用transient声明的变量,不会被自动序列化。 - 对于Socket, Thread类,不支持序列化。若实现序列化的接口中,有Thread成员;在对该类进行序列化操作时,编译会出错!
static与transient
首先说一下序列化对static和transient的处理吧,我们将之前的代码中Box类修改一下
class Box implements Serializable {
private static int width;
private transient int height;
private String name;
public Box(String name, int width, int height) {
this.name = name;
this.width = width;
this.height = height;
}
public String toString() {
return "["+name+": ("+width+", "+height+") ]";
}
}
将成员变量的类型修改为static和transient,运行一下,结果:
前面说得,序列化不对static和transient变量进行状态保存的。因此,testWrite()中保存Box对象时,不 会保存width和height的值。但是为什么testRead()读出来的Box对象中width=80,而height=0呢?
对于height,因为Box对象中height是int类型,而int类型默认是0,因此height为0.
而对于width,它是static类型,而static类型意味着所有Box对象都公用一个heith值,而在testWrite()中,我们已经将其初始化为80,因此,我们通过序列化读出来width也是80.
那么,如果我们想要保存static或transient变量,也是可以的,只要重写两个方法writeObject()和readObject()即可。
还是Box类
class Box implements Serializable {
private static int width;
private transient int height;
private String name;
public Box(String name, int width, int height) {
this.name = name;
this.width = width;
this.height = height;
}
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeInt(height);
out.writeInt(width);
}
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject();
height = in.readInt();
width = in.readInt();
}
public String toString() {
return "["+name+": ("+width+", "+height+") ]";
}
}
在writeObject()方法中,out.defaultWriteObject()是使定制的writeObject()方法可以利用自动序列化中内置的逻辑
在readObject()方法中,in.defaultReadObject()也是使定制的readObject()方法可以利用自动序列化中内置的逻辑。
Socket、Thread类
在Box类中添加
private Thread thread = new Thread() {
public void run() {
System.out.println("Serializable thread");
}
};
运行发现,直接编译报错!
事实证明,不能对Thread进行序列化,若希望程序能便宜通过,我们对Thread变量添加static或transient修饰符即可。
0x04 完全定制序列化过程Externalizable
如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。
Externalizable接口定义包括两个方法writeExternal()与readExternal()。需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。
实例:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.Serializable;
import java.io.Externalizable;
import java.io.IOException;
import java.lang.ClassNotFoundException;
public class ExternalizableTest1 {
private static final String TMP_FILE = ".externalizabletest1.txt";
public static void main(String[] args) {
testWrite();
testRead();
}
private static void testWrite() {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(TMP_FILE));
Box box = new Box("desk", 80, 48);
out.writeObject(box);
System.out.println("testWrite box: " + box);
out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static void testRead() {
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream(TMP_FILE));
Box box = (Box) in.readObject();
System.out.println("testRead box: " + box);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Box implements Externalizable {
private int width;
private int height;
private String name;
public Box() {
}
public Box(String name, int width, int height) {
this.name = name;
this.width = width;
this.height = height;
}
public void writeExternal(ObjectOutput out) throws IOException {
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
public String toString() {
return "["+name+": ("+width+", "+height+") ]";
}
}