0.1) 本文描述转自 core java volume 2, 旨在理解 java流与文件——对象流和序列化 的相关知识;
0.2) for source code , please visit https://github.com/pacosonTang/core-java-volume/blob/master/coreJavaAdvanced/chapter1/ObjectStreamTest.java and https://github.com/pacosonTang/core-java-volume/blob/master/coreJavaAdvanced/chapter1/SerialCloneTest.java
1.1)problem + solution
1.2)保存数据对象 的步骤:
Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
oos.writeObject(harry);
oos.writeObject(carl);
Attention)
1.3)有一种case 需要考虑: 当一个对象被多个对象共享时, 作为它们各自状态的一部分,会发生什么呢?(如两个经理共用一个秘书harry, 上面的源代码就是这种 case )
Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
carl.setSecretary(harry);
Manager tony = new Manager("Tony Tester", 40000, 1990, 3, 15);
tony.setSecretary(harry);
1.3.1)solution: 每个对象都是用一个序列号保存的, 这就是这种机制称为对象序列化的原因。 下面是其算法(Alg):
A1)对你遇到的每一个对象引用都关联一个序列号;
A5) 当遇到 “与之前保存过的序列号为x的对象相同”标记时, 获取与这个顺序号相关联的对象引用;
Attention)序列化的另一种重要应用是: 通过网络将对象集合传送到另一台计算机上。 正如文件中保存原生的内存地址毫无意义一样, 这些地址对于在不同处理器之间的通信也毫无意义的。因为序列化用序列号代替了内存地址, 所以它允许将对象集合从一台机器传送到另一台机器;(干货——序列化应用于代替内存地址来表示对象集合 便于对象集合在网络间的传输)
2.1)当存储一个对象时,这个对象所属的类也必须存储。这个类的描述(Descriptions)包含:
2.2)指纹: 是通过对类、超类、接口、域类型和方法签名按照规范方式排序的, 然后将安全散列算法(SHA) 应用于这些数据而获得的;(干货——指纹定义)
72
2 字节的类名长度
类名
8字节长的指纹
1字节长的标志
2字节长的数据域描述符的数量
数据域描述符
78(结束标记)
超类类型(如果没有就是70)
标志字节是由在 java.io.ObjectStreamConstants 中定义的3位掩码构成的;
1 字节长的类型编码
2 字节长的域名长度
域名
类名(如果域是对象)
其中类型编码是下列取值之一:
B byte
C char
D double
F float
I int
J long
L 对象
S short
Z boolean
[ 数组
Attention)你应该记住: (干货——需要记住的对象流的key point)
3.1)problem+solution (干货——某些数据域及其所属类是不可以序列化的)
3.2)序列化机制为单个类提供了一种方式,去向默认的读写行为添加验证或任何其他想要的行为, 可序列化的类可以定义具有下列签名的方法:readObject 和 writeObject;
Warning)
4.1) 当类型安全的枚举实现 Serializable 接口时, 你必须牢记存在一种重要变化, 此时,默认的序列化机制是不适用的。 假设我们写出一个 Orientation 类型的值, 并将其再次读回:
Orientation original = Orientation.HORIZONTAL;
ObjectOutputStream oos = ..;
oos.write(original);
oos.close();
ObjectInputStream ois = ...;
Orientation saved = (Orientation) ois.read();
4.2)problem: 下面的测试 if(saved === Orientation.HORIZONTAL) 将失败;
4.3)solution:为解决这个问题,必须定义一种称为 readResolve 的特殊序列化方法。 如果定义了 readResolve 方法, 在对象被序列化后就会调用它。 它必须返回这个对象, 而该对象之后会成为 readObject 的返回值;
Attention)向遗留代码中所有类型安全的枚举以及向所有支持单例设计模式的类中添加readResolve 方法; (干货——添加readResolve方法的条件)
5.1)如果使用序列化来保存对象,就要考虑在程序演化时会有什么问题?
5.2)无论类的定义产生什么样的变化,它的SHA指纹也会跟着变化, 而我们都知道对象流将拒绝读入具有不同指纹的对象。但是类可以表明它对其早期版本保持兼容, 要想这样做, 就必须先获得这个类的早期版本的指纹。
5.3)如果这个类只有方法产生了变化,那么在读入新对象数据时是不会有任何问题的。但是, 如果数据域发生了变化, 那么就有可能有问题了;
5.4)对象流会将这个类当期版本的数据域和流中版本的数据域进行比较;
5.5)看个荔枝:
5.5.3)以上的忽略和置空真的安全吗? 视情况而定;
5.5.3.1)丢掉数据域看起来是无害的, 因为接收者仍然可以拥有它知道如何处理的所有数据, 但是将数据域设置为null, 却可能并不那么安全了;
6.1)序列化是一种很有用 的用法: 提供了一种克隆对象的简便途径, 只要对应的类是可序列化的即可;
6.2)做法很简单:直接将对象序列化到输入流中, 然后将其读回。