1.什么是序列化?
将数据对象转换为二进制流的过程称为序列化(Serialization),反之将二进制流恢复为数据对象的过程称为反序列化(DEserialization)。
内存中的数据对象只有转换为二进制流才可以进行数据持久化和网络传输。序列化需要保留充分的信息以恢复数据对象,但是为了节约存储空间和网络宽带,序列化后的二进制流又要尽可能小。序列化常见的使用场景是RPC框架的数据传输。
常见的序列化有以下三种:
- Java序列化:不支持跨语言,兼容性较差
- Hessian序列化:支持跨语言
- Json序列化
2.Java中实现序列化
在Java中我们可以通过对象流ObjectOutputStream
和ObjectInputStream
结合Serializable
或者Externalizable
实现序列化和反序列化。
2.1.Serializable接口
实体类:
public class User1 implements Serializable {
private static final long serialVersionUID = 2021097002352557981L;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
通过对象流实现序列化和反序列化:
public class SerializableDemo1 {
public static void main(String[] args) {
User2 user = new User2();
user.setName("pikachues");
user.setAge(18);
System.out.println(user);
// 序列化
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile1.txt"));
oos.writeObject(user);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("tempFile1.txt"));
User2 readUser = (User2)ois.readObject();
System.out.println(readUser);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
输出:
User{name='pikachues', age=18}
User{name='pikachues', age=18}
总结几个点:
-
通过实现
Serializable
为开启序列化 -
serialVersionUID:如果未设置编译器会根据类名、接口名、属性名等来动态生成,源码修改
serialVersionUID
会发生变化,因此需要设置确保兼容性。 -
当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出
NotSerializableException
。 -
如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成
java.io.Serializable
接口。 -
如果我们不想让某个属性实例化,可以加
transient
修饰,或者用static
修饰。
2.2.Externalizable接口
实现Serializable
接口是默认将所有属性实例化,如果我们要定制实例化,除了用transient
修饰变量不序列化,我们还可以通过实现Externalizable
来定制序列化。
实体类:
public class User2 implements Externalizable {
private static final long serialVersionUID = -2807033361161051869L;
private String name;
private String pass;
private int age;
// 省略set get
@Override
public String toString() {
return "User2{" +
"name='" + name + '\'' +
", pass='" + pass + '\'' +
", age=" + age +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(pass);
out.writeInt(age);
}
/**
* 调用被序列化类的无参构造器去创建一个新的对象
* @param in
* @throws IOException
* @throws ClassNotFoundException
*/
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 读写顺序保持一致
name = (String)in.readObject();
pass = (String)in.readObject();
age = in.readInt();
}
}
通过对象流实现序列化和反序列化:
public class ExternalizableDemo1 {
public static void main(String[] args) {
User2 user = new User2();
user.setName("pikachues");
user.setPass("123");
user.setAge(18);
System.out.println(user);
// 序列化
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile2.txt"));
user.writeExternal(oos);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("tempFile2.txt"));
User2 readUser = new User2();
readUser.readExternal(ois);
System.out.println(readUser);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Externalizable继承了Serializable,该接口中定义了两个抽象方法:
writeExternal()
:实现序列化细节readExternal()
:实现反序列化细节- 这两个方法中读写顺序应该保持一致。
3.Java中防止序列化破坏单例模式
实体类:
public class Singleton implements Serializable {
private static final long serialVersionUID = 3809856658206678778L;
private static volatile Singleton singleton;
public Singleton() {
}
public static Singleton getSingleton(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}
序列化反序列化测试:
public class SerializableDemo1 {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile3.txt"));
oos.writeObject(Singleton.getSingleton());
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("tempFile3.txt"));
Singleton newSingleton = (Singleton)ois.readObject();
System.out.println(newSingleton==Singleton.getSingleton());
} catch (Exception e) {
e.printStackTrace();
}finally {
if(ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果为false,这里可以看出序列化会破坏单例模式,要想防止序列化破坏反射只需要在对应的序列化实体类中添加readResolve
方法
public class Singleton implements Serializable {
private static final long serialVersionUID = 3809856658206678778L;
private static volatile Singleton singleton;
public Singleton() {
}
public static Singleton getSingleton(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}
这时候再对以上序列化反序列化进行测试结果为true。
4.总结
- 序列化是将数据对象转换成二进制数据,反序列化是将二进制数据转换成对象
- Java中通过类实现
Serializable
开启序列化 - Java中序列化会破坏单例模式,可以在实现类中添加
readResolve
方法。
参考:
-
《码出高效》
-
《Java成神之路》
-
https://www.hollischuang.com/archives/1144