第一部分: What
Java 序列化是指把 Java 对象保存为二进制字节码的过程, Java 反序列化是指把二进制码重新转换成 Java 对象的过程
那么为什么需要序列化呢?
第一种情况是: 一般情况下 Java 对象的声明周期都比 Java 虚拟机的要短, 实际应用中我们希望在 JVM 停止运行之后能够持久化指定的对象, 这时候就需要把对象进行序列化之后保存
第二种情况是: 需要把 Java 对象通过网络进行传输的时候因为数据只能够以二进制的形式在网络中进行传输, 因此当把对象通过网络发送出去之前需要先序列化成二进制数据, 在接收端读到二进制数据之后反序列化成 Java 对象
第二部分: How
本部分以序列化到文件为例讲解 Java 序列化的基本用法
- package com.beautyboss.slogen;
- import java.io. * ;
- public class SerializableTest {
- public static void main(String[] args) throws Exception {
- FileOutputStream fos = new FileOutputStream("temp.out");
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- TestObject testObject = new TestObject();
- oos.writeObject(testObject);
- oos.flush();
- oos.close();
- FileInputStream fis = new FileInputStream("temp.out");
- ObjectInputStream ois = new ObjectInputStream(fis);
- TestObject deTest = (TestObject) ois.readObject();
- System.out.println(deTest.testValue);
- System.out.println(deTest.parentValue);
- System.out.println(deTest.innerObject.innerValue);
- }
- }
- class Parent implements Serializable {
- private static final long serialVersionUID = -4963266899668807475L;
- public int parentValue = 100;
- }
- class InnerObject implements Serializable {
- private static final long serialVersionUID = 5704957411985783570L;
- public int innerValue = 200;
- }
- class TestObject extends Parent implements Serializable {
- private static final long serialVersionUID = -3186721026267206914L;
- public int testValue = 300;
- public InnerObject innerObject = new InnerObject();
程序执行完用 vim 打开 temp.out 文件, 可以看到
- `0000000``: aced` `0005` `7372` `0020` `636f 6d2e` `6265` `6175` `....sr. com.beau`
- `0000010``:` `7479` `626f` `7373` `2e73 6c6f` `6765` `6e2e` `5465` `tyboss.slogen.Te`
- `0000020``:` `7374` `4f62 6a65` `6374` `d3c6 7e1c 4f13 2afe stObject..~.O.*.`
- `0000030``:` `0200` `0249` `0009` `7465` `7374` `5661` `6c75 654c ...I..testValueL`
- `0000040``: 000b 696e 6e65 724f 626a` `6563` `7474` `0023` `..innerObjectt.#`
- `0000050``: 4c63 6f6d 2f62` `6561` `7574` `7962` `6f73 732f Lcom/beautyboss/`
- `0000060``: 736c 6f67 656e 2f49 6e6e` `6572` `4f62 6a65 slogen/InnerObje`
- `0000070``:` `6374` `3b78` `7200` `1c63 6f6d 2e62` `6561` `7574` `ct;xr..com.beaut`
- `0000080``:` `7962` `6f73 732e 736c 6f67 656e 2e50` `6172` `yboss.slogen.Par`
- `0000090``: 656e 74bb 1eef 0d1f c950 cd02` `0001` `4900` `ent......P....I.`
- `00000a0: 0b70` `6172` `656e` `7456` `616c` `7565` `7870` `0000` `.parentValuexp..`
- `00000b0:` `0064` `0000` `012c` `7372` `0021` `636f 6d2e` `6265` `.d...,sr.!com.be`
- `00000c0:` `6175` `7479` `626f` `7373` `2e73 6c6f` `6765` `6e2e autyboss.slogen.`
- `00000d0: 496e 6e65 724f 626a` `6563` `744f 2c14 8a40 InnerObjectO,..@`
- `00000e0: 24fb` `1202` `0001` `4900` `0a69 6e6e` `6572` `5661` `$.....I..innerVa`
- `00000f0: 6c75` `6578` `7000` `0000` `c8 luexp....`
当程序运行时, 有关对象的信息就存储在了内存当中, 但是当程序终止时, 对象将不再继续存在我们需要一种储存对象信息的方法, 使我们的程序关闭之后他还继续存在, 当我们再次打开程序时, 可以轻易的还原当时的状态这就是对象序列化的目的
java 的对象序列化将那些实现了 Serializable 接口的对象转换成一个字节序列, 并且能够在以后将这个字节序列完全恢复为原来的对象, 甚至可以通过网络传播 这意味着序列化机制自动弥补了不同 OS 之间的差异.
如此, java 实现了轻量级持久性, 为啥是轻量级, 因为在 java 中我们还不能直接通过一个类似 public 这样的关键字直接使一个对象序列化, 并让系统自动维护其他细节问题因此我们只能在程序中显示地序列化与反序列化
对象序列化的概念加入到语言中是为了支持两种主要特性:
java 的远程方法调用(RMI), 它使存活于其他计算机上的对象使用起来就像存活于本机上一样当远程对象发送消息时, 需要通过对象序列化来传输参数和返回值
对于 Java Bean 来说, 对象序列化是必须的使用一个 Bean 时, 一般情况下是在设计阶段对它的状态信息进行配置这种状态信息必须保存下来, 并在程序启动的时候进行后期恢复, 这种具体工作就是由对象序列化完成的
使用对象实现 Serializable 接口(仅仅是一个标记接口, 没有任何方法)
序列化一个对象:
1. 创建某些 OutputStream 对象
2. 将其封装在一个 ObjectOutputStream 对象内
3. 只需调用 writeObject()即可将对象序列化
注: 也可以为一个 String 调用 writeObject(); 也可以用与 DataOutputStream 相同的方法写入所有基本数据类型(它们具有同样的接口)
反序列化
将一个 InputStream 封装在 ObjectInputStream 内, 然后调用 readObject()最后获得的是一个引用, 它指向一个向上转型的 Object, 所以必须向下转型才能直接设置它们
对象序列化不仅保存了对象的全景图, 而且能够追踪对象内所包含的所有引用, 并保存这些对象; 接着又能对对象内包含的每个这样的引用进行追踪; 以此类推这种情况有时被称为对象网
例子:
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out");
- out.writeObject(w);
- out.close();
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out");
- String s = (String)in.readObject();
在对一个 Serializable 对象进行还原的过程中, 没有调用任何构造器, 包括默认的构造器
整个对象都是通过 InputStream 中取得数据恢复而来的
寻找类: 必须保证 java 虚拟机能够找到相关的. class 文件找不到就会得到一个 ClassNotFOundExcption 的异常
序列化的控制通过实现 Externalizable 接口代替实现 Serializable 接口来对序列化过程进行控制
1. Externalizable 接口继承了 Serializable 接口, 增加了两个方法, writeExternal()和 readExternal(), 这两个方法会在序列化和反序列化还原的过程中被自动调用
2. Externalizable 对象, 在还原的时候所有普通的默认构造器都会被调用(包括在字段定义时的初始化)(只有这样才能使 Externalizable 对象产生正确的行为), 然后调用 readExternal().
3. 如果我们从一个 Externalizable 对象继承, 通常需要调用基类版本的 writeExternal()和 readExternal()来为基类组件提供恰当的存储和恢复功能
4. 为了正常运行, 我们不仅需要在 writeExternal()方法中将来自对象的重要信息写入, 还必须在 readExternal()中恢复数据
防止对象的敏感部分被序列化, 两种方式:
1. 将类实现 Externalizable, 在 writeExternal()内部只对所需部分进行显示的序列化
2. 实现 Serializable, 用 transient(瞬时)关键字 (只能和 Serializable 一起使用) 逐个字段的关闭序列化, 他的意思: 不用麻烦你保存或恢复数据我自己会处理
Externalizable 的替代方法
1. 实现 Serializable 接口, 并添加名为 writeObject()和 readObject()的方法, 这样一旦对象被序列化或者被反序列化还原, 就会自动的分别调用 writeObject()和 readObject()的方法 (它们不是接口的一部分, 接口的所有东西都是 public 的) 只要提供这两个方法, 就会使用它们而不是默认的序列化机制
2. 这两个方法必须具有准确的方法特征签名, 但是这两个方法并不在这个类中的其他方法中调用, 而是在 ObjectOutputStream 和 ObjectInputStream 对象的 writeObject()和 readObject()方法
[图片上传失败...(image-c92672-1517928660769)]
3. 技巧: 在你的 writeObject()和 readObject()内部调用 defaultWriteObject()和 defaultReadObject 来选择执行默认的 writeObject()和 readObject(); 如果打算使用默认机制写入对象的非 transient 部分, 那么必须调用 defaultwriteObject()和 defaultReadObject(), 且作为 writeObject()和 readObject()的第一个操作
使用持久性
1. 只要将任何对象序列化到单一流中, 就可以恢复出与我们写出时一样的对象网, 并且没有任何意外重复复制出的对象当然, 我们可以在写出第一个对象和写出最后一个对象期间改变这些对象的状态, 但是这是我们自己的事; 无论对象在被序列化时处于什么状态(无论它们和其他对象有什么样的连接关系), 我们都可以被写出
2. Class 是 Serializable 的, 因此只需要直接对 Class 对象序列化, 就可以很容易的保存 static 字段, 任何情况下, 这都是一种明智的做法但是必须自己动手去实现序列化 static 的值
使用 serializeStaticState()和 deserializeStaticState()两个 static 方法, 它们是作为存储和读取过程的一部分被显示的调用的
3. 安全问题: 序列化会将 private 数据保存下来, 对于你关心的安全问题, 应将其标记为 transient 但是这之后, 你还必须设计一种安全的保存信息的方法, 以便在执行恢复时可以复位那些 private 变量
来源: http://www.jianshu.com/p/949eb6c17fae