一、序列化:
1、 序列化是把对象转换成有序字节序列的过程,以便在网络上传输或者进行数据持久化。
说明:在网络传输过程中不能直接对Java对象、图片、音频等进行传输,而是要将其转换成可用于网络传输的二进制流时才能进行传输;
网络传输:包括通过二进制流与浏览器进行交互时,通过RPC进行远程调用时(对象跨平台跨语言传输,也即从windows上序列化的对象可到linux上反序列化,用c#序列化的对象可以被java反序列化)。
数据持久化:包括将内存中的对象持久化到本地(保存在本地文件中)、或保存到Redis、数据库时(此时就需要将对象序列化为二进制流)。
2、 序列化后的字节流保存了Java对象的状态以及相关的描述信息。
描述信息包括:该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
3、 在传递和保存对象的时候,能够保证对象的完整性和可传递性。
4、 序列化机制的核心作用就是对象状态的保存与重建。
5、 Java类需不需要实现Serializable接口?序列化的常见形式:
1)转换成二进制字节流的形式,主要将对象序列化成流的形式,用于数据存储(需要实现Serializable接口);
例:把对象存储在Redis服务器中、数据库中、本地文件中,RPC形式的远程方法调用(微服务使用Dubbo);
2)JSON序列化器,主要将对象序列化成字符串,用于数据传输;(不需要实现Serializable接口)
例:后端暴露的接口返回的JSON格式对象、HTTP形式的远程方法调用(微服务使用的Feign);
二、反序列化:
1、反序列化是把字节序列恢复成对象的过程。
说明:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象;
三、如何实现:
1、 序列化和反序列化整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
注意:Java中如果一个类的对象要想序列化成功,必须满足两个条件:
1)该类必须实现java.io.Serializable
或java.io.Externalizable
接口;
2)该类的所有属性必须是可序列化的,如果一个属性不是可序列化的,则该属性必须(使用transient
关键字)注明是短暂的,或static
修饰的静态属性。
说明:在 Java 中实现了Serializable接口后, JVM会在底层帮我们实现序列化和反序列化, 如果我们不实现Serializable接口,那自己去实现一套序列化和反序列化。
2、 类java.io.ObjectInputStream
和java.io.ObjectOutputStream
是高层次的数据流,它们包含反序列化和序列化对象的方法。
说明:ObjectInputStream表示对象输入流,它的writeObject(Object object)方法可以对参数执行的object对象进行序列化,把得到的字节序列写到一个目标输出流中;ObjectInputStream表示对象输入流,它的readObject()方法可以从输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
1)若User类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化:
ObjectOutputStream采用默认的序列化方式,对User对象的非transient的实例变量进行序列化,ObjectInputStream采用默认的反序列化方式,对User对象的非transient的实例变量进行反序列化。
2)若User类实现了Externalnalizable接口,且User类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化:
ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化,ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化。
3)若User类实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out),则采用以下方式进行序列化与反序列化:
ObjectOutputStream调用User对象的writeObject(ObjectOutputStream out)的方法进行序列化;ObjectInputStream会调用User对象的readObject(ObjectInputStream in)的方法进行反序列化。
实例:User对象,实现Serializable接口,实现默认序列化:
import java.io.Serializable;
@Data
public class User implements Serializable {
Integer id;
String name;
String phone;
}
序列化与反序列化:
import java.io.*;
public class userDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建对象
User user1 = new User(1, "雪松", "13260356396"); //被序列化的对象
User user2; //反序列化的对象
//序列化
getSerial(user1);
//反序列化
user2 = backSerial();
System.out.println(user2.getName());
}
//序列化
static void getSerial(User user1) throws IOException {
FileOutputStream fos = new FileOutputStream("obj.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user1);
oos.flush();
oos.close();
}
//反序列化
static User backSerial() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("obj.out");
ObjectInputStream ois = new ObjectInputStream(fis);
return (User) ois.readObject();
}
}
四、注意事项:
1、 序列化时,只对对象的状态进行保存,而不管对象的方法;
2、 当一个父类实现序列化,子类自动实现序列化,不需要显示实现Serializable接口;
3、 当一个对象的实例变量引用其它对象(深拷贝),序列化该对象时也把引用对象进行序列化;
4、 并不是所有对象都可以序列化,原因:
1)安全方面的原因,比如一个对象拥有private
、public
等field,对于一个要传输的对象,比如写到文件或者RMI传输等,在序列化进行传输的过程中,这个对象的private
域是不被保护的;
2)资源分配方面的原因,比如socket
,thread
类,如果可以序列化,进行传输或者保存,也无法对他们进行重新资源分配,而且,也没有必要这样实现。
5、 为什么还要指定serialVersionUID的值?
1)序列化运行时使用一个称为serialVersionUID的版本号与每个可序列化类相关联,该序列化号在反序列化过程中用于验证反序列化对象的发送者和接收者是否为该对象加载了序列化兼容的类。
2)如果不显示的指定serialVertionUID,JVM在序列化时会根据属性自动生成一个serialVersionUID,然后与属性一起序列化,再进行持久化或网络传输。在反序列化时,JVM 会再根据属性自动生成一个新版serialVersionUID,然后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID进行比较,如果相同则反序列化成功,否则报错。
3)在实际开发中,不显示指定serialVersionUID的情况会导致什么问题?
如果我们的类写完后不再修改,那当然不会有问题。但这在实际开发中是不可能的,我们的类会不断迭代,一旦类被修改了,那旧对象反序列化就会报错。所以在实际开发中, 我们都会显示指定一个serialVersionUID,值是多少无所谓,只要不变就行。
显示地定义serialVersionUID有两种用途:在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
6、 声明为static
或transient
类型的成员数据不能被序列化。
1)为什么static修饰的成员不会被序列化?
因为序列化是针对对象而言的,而static
属性优先于对象存在, 随着类的加载而加载, 所以不会被序列化.
2)看到这个结论,是不是有人会问,serialVersionUID也被static修饰,为什么serialVersionUID会被序列化?
其实serialVersionUID
属性并没有被序列化,JVM在序列化对象时会自动生成一个serialVersionUID
,然后将我们显示指定的serialVersionUID
属性值赋给自动生成的serialVersionUID
。
7、 如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因。