随笔分类
序列化
concept
序列化的原本意图是希望对一个 Java对象作一下 "变换",变成字节序列,这样一来方便 持久化
存储到磁盘,避免程序运行结束后对象就从内存里消失,另外变成字节序列后也更便于 网络运输和传播
*序列化:**把 Java对象转换为字节序列*
*反序列化:**从字节序列中还原对象*
序列化机制从某种意义上来说也弥补了平台化的一些差异,因为转换后的字节流可以在其它平台上进行反序列来恢复对象
来看个小例子:
import java.io.*;
import java.util.concurrent.TimeUnit;
/**
* 输出:Student{id=1, name='liangye', age=20}
*/
public class Student implements Serializable {
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
int id;
String name;
int age;
public static void serialize() throws IOException {
Student student = new Student();
student.setId(1);
student.setName("liangye");
student.setAge(20);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("H://student.txt")));
objectOutputStream.writeObject(student);
objectOutputStream.close();
}
public static void deserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("H://student.txt")));
Student object = (Student)objectInputStream.readObject();
System.out.println(object.toString());
}
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
serialize();
TimeUnit.MILLISECONDS.sleep(100);
deserialize();
}
}
探讨:
标识?
查看源码得知,Serializable实际上是个空接口,那么对象去实现这个接口有什么用呢?
public interface Serializable {
}
上面的代码,取消实现序列化接口的话,会报错:NotSerilizableException
Exception in thread "main" java.io.NotSerializableException: serial.Student
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
想一想,所谓的序列化可不可能只是一个 "标记"呢?
追随错误,定位到源码:
// remaining cases
// String类型可进行序列化
if (obj instanceof String) {
writeString((String) obj, unshared);
// 数组类型可进行序列化
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
// 枚举类型可进行序列化
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
// 实现了序列化接口的类的实例可进行序列化
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
真相了,Serializable果然只是一个 标识作用而已
如果一个对象既不是 字符串、数组、枚举,而且也没有实现 Serializable接口的话,在序列化时就会抛出 NotSerializableException异常
serialVersionUID ?
在 juc包下时,时常看到实现了序列化接口的类中都去定义了 serialVersionUID
的序列号
private static final long serialVersionUID = 7249069246763182397L (源自 ConcurrentHashMap)
这个序列化有什么作用?
修改上述的例子,先去将原本的对象序列化存储到磁盘,然后稍微改动下类中的信息:新增一个属性 num
int id;
String name;
int age;
long num;
此时再去进行反序列化时,报错了:抛出了 InvalidClassException
Exception in thread "main" java.io.InvalidClassException: serial.Student; local class incompatible: stream classdesc serialVersionUID = 4524075555910188401, local class serialVersionUID = 1374906223474824552
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at serial.Student.deserialize(Student.java:48)
at serial.Student.main(Student.java:55)
由报错信息可知:序列化前后的 serialVersionUID
并不兼容
从这地方最起码可以得出 两个重要信息:
-
serialVersionUID
是序列化前后的唯一标识符可将其看成是一个 "暗号",在反序列时,JVM会将字节流中的序列化号 ID和被序列化类中的序列化 ID进行对比,只有对比成功,才能去进行序列化,否则的话就回去报异常以来终止序列化
-
默认如果没有人显式定义过
serialVersionUID
,那编译器会为它自动声明一个!该序列化号的生成,和类的结构及其信息相关,若是更改了类的结构或者信息,则生成的会是一个新的
serialVersionUID
因此,为了 serialVersionUID
的确定性,在实现序列化接口的同时,最好也去定义一个 serialVersionUID
特殊?
-
凡是被
static
修饰的字段是不会被序列化的序列化的是实例,类中信息又怎会被实例化呢?
-
凡是被
transient
修饰的字段也是不会被序列化
当我们在序列化某个类的对象时,如果不希望某个字段被序列化 (如此子弹存放的是 隐私值),那个时候便可以去使用 transient
修饰符来修饰该字段
如:
private Integer id;
private String name;
int age;
private final static long serialVersionUID = 123654789;
运行结果:
Student{id=1, name='liangye', age=20, nickName='null'}
漏洞?
对象序列化成字节流,通过网络传输,若此期间它人非法取获取了中间字节流,然后加以伪造或者篡改,那么反序列化出来的对象就会有一定风险了
那么,此时我们所想的、所需要的便是如何 "受控地"进行反序列化?
思路 (未验证):可以去重写 readObject
方法,从而提供一些约束
(日后,再进行补充)