什么是序列化
序列化,可以简单地理解为轻量级的持久化(persistence
),而且是针对对象实例(instance
)的持久化。
整个序列化的过程就是:将对象信息转化为二进制数据序列,里面保存了对象的类型信息、引用类型信息、对象状态信息(我理解为成员变量的值)。而反序列化,就是根据流(stream
)中读取的字节序列信息,重新包装一个对象,他的状态和序列化之前的对象一样。
序列化的整个过程由虚拟机实现,因此即使在不同的虚拟机上,仍然可以做到对象还原。因此,序列化主要用于1.RMI 2.JavaBean的状态保存。
示例代码——使用序列化存取对象
- 对象必须实现
Serializable
接口,这是标记接口,没有任何方法,但是必须实现这个接口才能使用序列化机制 - 通过
ObjectOutputStream
的writeObject(...)
写入对象 - 通过
ObjectInputStream
的readObject(...)
读取对象
序列化工具类
/**
* @author luzj
* @description:
* 1. 处理对象的序列化以及反序列化的工具
* 2. T:待序列化对象的对象的类型
* @date 2018/3/2
*/
public class SerialUtil<T> {
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public SerialUtil(String path) {
this.path = path;
}
/**
* 序列化对象
* @param obj
*/
public void serialObj(T obj) {
FileOutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
outputStream = new FileOutputStream(this.getPath());
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println(obj);
try {
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (outputStream != null)
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 反序列化对象
*/
public void deSerialObj() {
FileInputStream in = null;
ObjectInputStream objectInputStream = null;
T obj = null;
try {
in = new FileInputStream(this.getPath());
objectInputStream = new ObjectInputStream(in);
obj = (T) objectInputStream.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (objectInputStream != null)
objectInputStream.close();
if (in != null)
in.close();
if (obj != null)
System.out.println(obj);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
用于序列化的类
public class MySerial implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
public int code;
//transient变量不会参与序列化
public transient int SNN;
public MySerial(String name) {
this.name = name;
}
public MySerial(String name, int code) {
this.name = name;
this.code = code;
this.SNN = 40;
}
@Override
public String toString() {
return "name:" + name + "code:" + code + "SNN:" + SNN + ",hashcode:" + this.hashCode();
}
}
测试
String path = "src/main/resource/myserial.ser";//本地存储地址
SerialUtil<MySerial> serialSerialUtil = new SerialUtil<>(path);
MySerial mySerial = new MySerial("ada wong", 233);
serialSerialUtil.serialObj(mySerial);
System.err.println("=======================================");
serialSerialUtil.deSerialObj();
输出结果
![](https://img.haomeiwen.com/i3763618/b0183d28e17a42cf.png)
为什么会抛出ClassNotFoundException
我们知道,序列化很多时候会用于两个虚拟机之间传输对象。这个过程或许通过磁盘IO,或许通过网络,都没关系。
在反序列化的时候,虚拟机或重组序列化对象,此时如果本地虚拟机如果找不到对应类,就会报出ClassNotFoundException
,因此在调用readObject()
时需要捕获该异常。
比如上面的Myserial
类,本地要一个,远程主机也必须有一个。否则就会报异常。
serialVersionUID的作用
每一个序列化对象会有一个serialVersionUID
,那么他的作用是什么?我们知道,序列化很多时候会用于RMI,本地一个类,远程也必须有一个同功能的类。
可是,如果两个类的serialVersionUID
不一样,就会报出反序列化失败的提示信息。
因此,互相传输的两个虚拟机的类UID必须相同
继承关系中的序列化
想象这样一种场景,我们序列化的对象有一个父类,但是父类没有实现Serialization
接口,这时候会出现什么情况呢?
答案是,子类从父类中继承的属性不会被序列化。
示例代码
// 父类,未序列化
public class Person{
public String name;
public int code;
@Override
public String toString() {
return "name:"+name+",code:"+code+",hashcode:"+this.hashCode();
}
}
//子类,序列化
public class Nancy extends Person implements Serializable {
public int age;
@Override
public String toString() {
return "name:"+name+",code:"+code+",age:"+age+",hashcode"+this.hashCode();
}
}
//测试父子序列化Nancy
String path = "src/main/resource/nancy.ser";
SerialUtil<Nancy> serialUtil = new SerialUtil<>(path);
Nancy nancy = new Nancy();
nancy.age = 123;
nancy.name= "nancy";
nancy.code = 213;
serialUtil.serialObj(nancy);
System.err.println("============================================");
serialUtil.deSerialObj();
测试结果
![](https://img.haomeiwen.com/i3763618/10eba207480b9f0e.png)
很容易看到,父类的属性值并没有很好还原,只是被重新初始化了。
另外补充一下,如果一个属性被transient
修饰,也不会参与序列化。
控制序列化域反序列化的过程
我刚喜欢称他为序列化的前处理。
首先,我们必须在序列化对象里面添加两个方法:
private void readObject(ObjectInputStream inputStream)
private void writeObject(ObjectOutputStream objectOutputStream)
严格记住这两个方法签名,必须按照格式写。让人困惑的是,这两个方法不是定义在接口里,还是私有方法,怎么调用呢?实际上他是根据反射搜索出方法,在调用。
这里对参考文档的示例代码做一些改进,使看起来更加的形象。
示例代码
/**
* 1.对序列化过程控制
* 2.加密某个字段
* @param objectOutputStream
*/
private void writeObject(ObjectOutputStream objectOutputStream) {
System.err.println("序列化:");
ObjectOutputStream.PutField putField;
try {
putField = objectOutputStream.putFields();
System.out.println("原密码是:" + this.password);
password = XOREncrypt.xOREnc(password);
putField.put("password", password);
System.out.println("加密后的密码:" + password);
objectOutputStream.writeFields();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 1.反序列化控制过程
* 2.解密password
* @param inputStream
*/
private void readObject(ObjectInputStream inputStream) {
try {
// inputStream.defaultReadObject();
System.err.println("反序列化:");
ObjectInputStream.GetField getField;
getField = inputStream.readFields();
password = (String) getField.get("password", "");
System.out.println("加密密码:" + password);
password = XOREncrypt.xOREnc(password);
System.out.println("解密密码:" + password);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//测试
String path = "src/main/resource/encrypt.ser";
String password = "ada wong is my wife";
SerialUtil<EncryptSerial> serialSerialUtil = new SerialUtil<>(path);
EncryptSerial encryptSerial = new EncryptSerial(password);
serialSerialUtil.serialObj(encryptSerial);
System.out.println("============================================");
serialSerialUtil.deSerialObj();
- readObject(...)控制反序列化过程
- writeObject(...)控制序列化过程
-
XOREncrypt
是我个人写的加密工具类,读者完全可以实现一个自己的工具类 - 这两个方法完全取代了原来的序列化处理机制,如果在执行自定义序列化处理方法前,想执行默认的序列化机制,可以加入一行代码
inputStream.defaultReadObject()
即可。
测试结果
![](https://img.haomeiwen.com/i3763618/ffc60bba4f7f5f64.png)
代码详情
参考文章
《java编程思想第四版》
网友评论