Java中序列化和反序列化的区别
序列化和反序列化的定义
序列化(Serialization)与反序列化(Deserialization)是编程中常见的两个概念,他们主要涉及到将数据结构或对象状态转换为可以存储或传输的格式,以及将存储或传输的格式转换回原始的数据结构或对象状态的过程。
这两个过程在数据持久化,网络通信,对象深拷贝等多个场景中发挥着重要作用。
一.序列化(Serialization)
序列化是指将数据结构或对象状态转换成可以存储或传输的格式的过程。这个格式通常是和平台无关的,比如JSON,XML,二进制格式等,以便可以在不同的系统或环境中进行交换。序列化后的数据可以存储在文件中,或者通过网络发送给其他系统。
为什么需要序列化?
- 数据持久化:将对象状态保存到文件中,以便在程序关闭或系统重启后能够重新加载这些数据。
- 网络传输:在网络通信中,数据需要在客户端和服务端之间传输,序列化可以将对象转换成字节流,便于在网络中传输。
- 对象深拷贝:通过序列化一个对象,然后立即进行反序列化,可以实现对象的深拷贝。
二.反序列化(Deserialization)
反序列化是序列化的逆过程,即将序列化后的数据(如JSON,XML,二进制格式等)转换回原始的数据结构或对象状态。这个过程允许对象在需要时重新构建对象,并使用其原始数据。
为什么需要反序列化?
- 数据恢复:在程序启动时,从文件中读取序列化后的数据,反序列化成对象,以恢复程序运行前的状态。
- 数据接收:在网络通信中,接收到的序列化数据需要被反序列化成对象,以便在本地系统中使用。
三.Java的序列化
Java序列化是指将Java对象的状态转换为可以保存或传输的格式的过程。Java提供了多种序列化的方式,以满足不同的需求和场景。
1.实现Serialization接口:
- 特点:这是Java默认的序列化方式,通过实现java.io.Serialization接口来标记一个类可以被序列化。这种序列化是隐式的,会自动序列化所有非static和transient关键字修饰的成员变量。
- 使用场景:适用于大多数需要序列化的场景,特别是当对象的状态需要被持久化或在网络间传输时。
2.实现Externalizable接口:
- 特点:与Serialization接口类似,但Externalizable接口提供了更高的灵活性。实现该类接口的类必须手动实现writeExternal()和readExternal()方法,以控制序列化和反序列化的过程。
- 使用场景:适用于需要精确控制序列化过程的场景,比如只序列化对象的某些部分,或者需要对序列化数据进行加密等。
3.使用JSON序列化库:
- 常用库:如Jackson,Gson等。
- 特点:将对象转换为JSON格式的字符串进行序列化,可以跨语言,跨平台进行数据交换。JSON格式易于阅读和编写,也易于机器解析和生成。
- 使用场景:适用于需要与其它语言或系统交互的场景,或者当对象需要被存储为文本格式时。
4.使用XML序列化库:
- 常用库:如JAXN,XStream等。
- 特点:将对象序列化为二进制格式进行传输和存储,效率较高。这些库通常提供了丰富的数据结构和高效的编码解码算法。
- 使用场景:适用于对性能要求较高的场景,比如需要频繁序列化和反序列化大量数据的系统。
5.其他序列化方式:
- Hessian序列化:Hessian是一种通过网络传输和存储Java对象的二进制格式,可以跨语言,跨平台,效率较高。
- Kryo序列化:Kryo是一种高性能的Java序列化库,序列化和反序列化速度都很快,但只能用于Java环境。
Java提供了多种序列化方式,每种方式都有其特点和适用场景。在实际应用中,可以根据具体需求和场景选择合适的方式进行Java对象的序列化。需要注意的是,除了Java默认的序列化方式外,其他方式通常需要引入相应的第三方库。
四.Java默认序列化与反序列化示例
我们先将一些Java的变量和一个Car对象序列化到 d盘的一个名为 data.dat 的文件中。
/**
* ObjectOutputStream 的使用,完成数据的序列化
* ObjectOutputStream 是序列化
*/
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
//序列化后,保存到文本格式不是纯文本txt,而是按照它的格式来保存
String filePath = "d:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeInt(100);//int -> Integer(实现了Serializable)
oos.writeBoolean(true);//boolean -> Boolean(实现了 Serializable)
oos.writeChar('o');//char -> Character(实现了Serializable)
oos.writeDouble(4.4);//double -> Double(实现了 Serializable)
oos.writeUTF("对象输出流");//String (实现了Serializable)
oos.writeObject(new Car("xiaomi su7","Pro","Gulf Blue","300,000"));
oos.close();
System.out.println("数据以序列化的形式保存完成");
/*
如果Car没有实现 Serializable接口,会报错如下:
Exception in thread "main" java.io.NotSerializableException: org.example.io.demo.lession20.Car
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at org.example.io.demo.lession20.ObjectOutputStreamDemo.main(ObjectOutputStreamDemo.java:24)
*/
}
}
@Data
class Car implements Serializable {
private String name;
private String type;
private static String color ;
private transient String price ;
public Car(String name,String type,String color,String price){
this.name = name;
this.type = type;
this.color = color;
this.price = price;
}
}
执行main方法后,会在d盘根目录下看到一个 data.dat的文件。
下面我们将这个文件反序列化至程序中并打印出来:
/**
* 读取序列化文件,并通过反序列化恢复数据
* ObjectInputStream 是反序列化
*/
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//指定反序列化的文件
String filePath = "d:\\data.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//读取(即反序列化)的顺序需要和保存数据(即序列化)的顺序一致,否则会有异常
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject().toString());
ois.close();
}
}
输出结果为:
100
true
o
4.4
对象输出流
Car(name=xiaomi su7, type=Pro, price=null)
Java序列化和反序列化注意事项:
- 读写顺序要一致。
- 序列化和反序列化对象要实现Serialization接口。
- 序列化中的类中,建议添加SerialVersionUID,为了提高版本的兼容性。
- 序列化对象时,默认将里面所有的属性都进行序列化,除了stati,transient修饰的成员。
- 序列化对象时,要求里面属性的类型也需要实现序列化接口。
- 序列化具备可继承性,也就是如果某类已经了序列化,则它的所有子类也默认实现了序列化。
五.static 和 transient 在序列化中的区别
static关键字:
- 定义:static关键字用于修饰类的成员变量和方法,使其成为类变量或类方法,而不是实例变量或实例方法。类变量属于类本身,而非类的任何特定实例。
- 序列化行为:在序列化过程中,被static修饰的变量(即类变量)不会被序列化。这是因为static变量是类级别的,他们不属于任何特定的对象实例,而是由所有的实例共享。因此,序列化一个对象时,不会包含其类变量的值,因为类变量的值在JVM中是全局的,与特定的序列化实例无关。
- 反序列化影响:饭序列化一个对象时,被static修饰的变量会保持其在JVM中的当前值,而不是从序列化数据中恢复。这是因为静态变量是全局的,不受单个对象实例的序列化/反序列化过程影响。
Transient关键字:
- 定义:transient关键字用于修饰类的成员变量,以指示改变量在序列化时不应被序列化。
- 序列化行为:当一个对象被序列化时,所有非transient修饰的成员变量都会被序列化,而transient修饰的变量则会被忽略。这意味着,在序列化后的数据中,不会包含任何被transieent修饰的变量的值。
- 反序列化影响:在序列化过程中,被transient修饰的变量将不会被恢复其序列化前的值。相反,它们会被初始化位默认值(例如,数值类型的变量会被初始化为0或false,对象类型的变量会被初始化为null)。因此,开发者需要在反序列化后,根据需要手动为这些变量赋值。
关键字 | 定义 | 序列化行为 | 反序列化影响 |
---|---|---|---|
static | 修饰类变量或类方法,属于类本身 | 不被序列化 | 保持JVM中的当前值 |
transient | 修饰成员变量,指示在序列化时忽略该变量 | 被忽略,不被序列化 | 被初始化为默认值 |