序列化
前记:
- 在开发中经常见过某个类实现了“Serializable"接口,这样就可以将对象变成字节流的形式传输了,觉得非常地神奇
- 参考了《Java核心技术》卷II,对其中原理进行理解,并写了一些小例子
序列化内幕
- 序列化名称由来:每个对象都是用一个序列号(来代替其内存中的地址)保存的,所以称为对象的序列化
- 序列化算法:
- 如果是第一次遇到,保存其对象数据
- 如果对象之前被保存过,则只要打上相同的序列号
- 反序列化算法:
- 如果是第一次遇到其序列号,则构建类
- 如果是对象之前已经被构建过,则获取与这个序列号相关联的对象引用
- 序列化文件:用类似于class文件的特殊格式进行存储。是内存无关的。
序列化对象流
-
序列化对象需要实现(标记为)Serializable接口
-
ObjectInputStrem 和 ObjectInputStream对象。writeObject 和 readObject 方法。
-
标记为
transient
的域,会在序列化时跳过。 -
修改默认的序列化机制一:只需要保存和加载自身的数据域,而不用关心超类。
public class TestSerialization implements Serializable { String name; transient Double value; private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { objectInputStream.defaultReadObject(); value = objectInputStream.readDouble();// 注意:ObjectInputStream 实现了二进制数据DataInputSream 接口 } private void writeObject(ObjectOutputStream objectOutputStream) throws IOException { objectOutputStream.defaultWriteObject(); objectOutputStream.writeDouble(value); } }//class TestSerialization public class TestMain { public static void main(java.lang.String[] args) throws Exception { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\aj\\Desktop\\out.txt")); TestSerialization testSerialization = new TestSerialization(); testSerialization.name = "abc"; testSerialization.value = 33.4; objectOutputStream.writeObject(testSerialization);//通过源码可以看出:writeObject方法,会调用自定义的writeObject方法。 objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:\\Users\\aj\\Desktop\\out.txt")); TestSerialization o = (TestSerialization)objectInputStream.readObject(); System.out.println("o.name = " + o.name); System.out.println("o.value = " + o.value); } } o.name = abc o.value = 33.4
-
修改默认的序列化机制一:对包括超类数据在内的整个对象的存储和恢复负责。
readExternal 和 writeExternal 方法。
-
单例与序列化的冲突:
- 背景:序列化会产生一个全新的克隆对象,会破坏单例对象
- 解决方案:在单例对象中定义一个protected Object readResolve() 方法。在对象完成序列化后会调用它。而返回的值会真正成为序列化的结果。(针对读)
-
序列化的版本管理:
- 背景:序列化时,会根据对象的域、方法名等进行签名(指纹)。如果读的时候,类的结构与运行时不一样了,会出现指纹不一样的问题。
- 解决方案:在可序列化对象中加入:
public static final long serialVersionUID = xxxxxx;
。(不加的话,如果指纹不一样,将报错。) - 如果文件中的id与运行时类中定义的id一致。那么对象输入流会尽力将流对象转换成这个类当前的版本。转换规则在P83页。
-
克隆中的使用:
- 序列化是深拷贝Deep Copy
- 重写clone()方法,配合可利用 ByteArrayOutputStream作为中转站,写出深拷贝代码。
- 缺点:比普通的重写clone()方法,慢得多