Java 将创建出来的对象,存放在 JVM 的对内存中,只有在 JVM 运行的时候,这些对象才会存在,一旦 JVM 停止运行,这些对象的状态也就随之消失了。
但是在一些应用场景中,我们需要将这些对象进行持久化,并且需要在使用的时候能够重新读取对象信息,比如说在 RPC 调用的时候,需要将对象通过网络进行传输,此时就需要下将对象记性序列化进行传输,再将其反序列化进行处理。
序列化(Serialization)是指将对象的状态信息,转换成可以可以存储或传输的形式。在网络传输过程中,可以是字节或是XML等格式。
- 序列化:Java对象转换为字节序列。
- 反序列化:字节序列恢复为原先的Java对象。
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。
Java提供了下面这些类来进行序列化和反序列化
java.io.Serializable
java.io.Externalizable
ObjectOutput
ObjectInput
ObjectOutputStream
ObjectInputStream
Serializable 接口
java.io.Serializable 是一个没有任何方法或者字段的接口类,仅用于标识这个类可以被序列化。如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成 java.io.Serializable
接口。
Java在进行序列化之前,会检查类是否实现了Serializable接口,没有实现就会报Serializable 接口就会报 NotSerializableException 异常。
往序列化的方法底层看到 java.io.ObjectOutputStream#writeObject0
接口,它对被序列化对象的类型进行了判断,不是字符串、数组、枚举,也没有实现 Serializable 的接口都会抛出 NotSerializableException
异常。
Externalizable 接口
Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()
与readExternal()
。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()
与readExternal()
方法。
serialVersionUID 的作用
反序列化的时候可能遇到 InvalidClassException 异常,说是序列化的时候字节码文件的 serialVersionUID 和本地类的 serialVersionUID 不一致。这是由于反序列化的时候对类进行了修改,比如加上一个 toString 方法打印对象信息。
使用 ObjectOutputStream、ObjectInputStream 进行序列化
public void serialize() {
User user = new User("深页", 18, "杭州");
try (
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.txt"));
) {
oos.writeObject(user);
System.out.println("Serialized data is saved");
} catch (IOException e) {
e.printStackTrace();
}
}
public void deserialize() {
try (
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.txt"));
) {
User user = (User) ois.readObject();
System.out.println(user);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
static、transient 修饰不想被序列化的变量
被 static 和 transient 修饰的字段是不会被序列化的:
- 序列化保存的是对象的状态而非类的状态,所以会忽略 static 静态域;
- 序列化某个类的对象时,不希望某个字段被序列化,可以用 transient 修饰变量