赞
踩
1️⃣序列化过程:是指把一个 Java 对象变成二进制内容,实质上就是一个 byte[]。因为序列化后可以把 byte[] 保存到文件中,或者把 byte[] 通过网络传输到远程(IO),如此就相当于把 Java 对象存储到文件或者通过网络传输出去了。
2️⃣一个 Java 对象要能序列化,必须实现一个特殊的java.io.Serializable接口,它的定义如下:
public interface Serializable {}
Serializable 没有定义任何方法,它是一个空接口。这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。
反序列化过程:把一个二进制内容(也就是 byte[])变回 Java 对象。有了反序列化,保存到文件中的 byte[] 又可以“变回” Java 对象,或者从网络上读取 byte[] 并把它“变回” Java 对象。以下是一些使用序列化的示例:
当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。当两个 Java 进程进行通信时,需要 Java 序列化与反序列化实现进程间的对象传送。换句话说,一方面,发送方需要把这个 Java 对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出 Java 对象。
优点:
注意事项:
Java 对象在网络上传输或者持久化存储到文件中时,需要进行序列化处理。当且仅当对象的类实现java.io.Serializable时,该对象才有资格进行序列化,然而真正的序列化动作不需要靠它完成。序列化是一个标记接口(不包含任何方法),该接口告诉Java虚拟机(JVM)该类的对象已准备好写入持久性存储或通过网络进行读取。
序列化算法一般会按步骤做如下事情:
默认情况下,JVM 负责编写和读取可序列化对象的过程。序列化/反序列化功能通过对象流类的以下两种方法公开:
1️⃣ObjectOutputStream.writeObject(Object)
:将可序列化的对象写入输出流。如果要序列化的某些对象未实现java.io.Serializable,则此方法将引发NotSerializableException。
按照提示,由源码一直跟到ObjectOutputStream的writeObject0()底层:
2️⃣ObjectInputStream.readObject()
:从输入流读取,构造并返回一个对象。如果找不到序列化对象的类,则此方法将引发ClassNotFoundException。readObject() 返回一个 Object 类型的对象,因此需要将其强制转换为可序列化的类,在这种情况下为String类。
如果序列化使用的类有问题,则这两种方法都将引发InvalidClassException,如果发生 I/O 错误,则将引发IOException。NotSerializableException和InvalidClassException是IOException的子类。
举个例子,对 Student 类对象序列化到一个名为 student.txt 的文本文件中,然后再通过文本文件反序列化成 Student 类对象:
①Student 类:
@Data
public class Student implements Serializable {
private String name;
private Integer age;
private Integer score;
@Override
public String toString() {
return "Student:" + '\n' +
"name = " + this.name + '\n' +
"age = " + this.age + '\n' +
"score = " + this.score + '\n'
;
}
// ... 其他省略 ...
}
②序列化
public static void serialize() throws IOException {
Student student = new Student();
student.setName("new");
student.setAge(18);
student.setScore(100);
ObjectOutputStream objectOutputStream =
new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
objectOutputStream.writeObject( student );
objectOutputStream.close();
System.out.println("序列化成功!已经生成student.txt文件");
System.out.println("============================");
}
③反序列化
public static void deserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream =
new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
Student student = (Student) objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列化结果为:");
System.out.println( student );
}
④运行结果:
序列化成功!已经生成student.txt文件
============================
反序列化结果为:
Student:
name = new
age = 18
score = 100
import java.io.*; import java.util.*; public class Student extends Person implements Serializable { public static final long serialVersionUID = 1234L; private long studentId; private String name; private transient int age; public Student(long studentId, String name, int age) { super(); this.studentId = studentId; this.name = name; this.age = age; System.out.println("Constructor"); } public String toString() { return String.format("%d - %s - %d", studentId, name, age); } }
如上代码:
serialVersionUID 是一个常数,用于唯一标识可序列化类的版本。从输入流构造对象时,JVM 在反序列化过程中检查此常数。如果正在读取的对象的 serialVersionUID 与类中指定的序列号不同,则 JVM 抛出InvalidClassException。这是为了确保正在构造的对象与具有相同 serialVersionUID 的类兼容。
serialVersionUID 是可选的。如果不显式声明,Java 编译器将自动生成一个。为何要必须显式声明 serialVersionUID?
原因是:自动生成的 serialVersionUID 是基于类的元素(成员变量、方法和构造函数等)计算的。如果这些元素之一发生更改,serialVersionUID 也将更改。想象一下这种情况:
这就是为什么建议为可序列化类显式添加 serialVersionUID 的原因。
上面 Student 类的成员变量 age 被标记为 transient,JVM 在序列化过程中会跳过瞬态变量。这意味着在序列化对象时不会存储 age 变量的值。因此,如果成员变量不需要序列化,则可以将其标记为瞬态。以下代码将 Student 对象序列化为名为“ students.ser”的文件:
String filePath = "students.ser";
Student student = new Student(123, "John", 22);
try (
FileOutputStream fos = new FileOutputStream(filePath);
ObjectOutputStream outputStream = new ObjectOutputStream(fos);
) {
outputStream.writeObject(student);
} catch (IOException ex) {
System.err.println(ex);
}
请注意,在序列化对象之前,变量 age 的值为 22。下面的代码从文件中反序列化 Student 对象:
String filePath = "students.ser";
try (
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream inputStream = new ObjectInputStream(fis);
) {
Student student = (Student) inputStream.readObject();
System.out.println(student);
} catch (ClassNotFoundException ex) {
System.err.println("Class not found: " + ex);
} catch (IOException ex) {
System.err.println("IO error: " + ex);
}
此代码输出如下:
1个
123 - John - 0
1️⃣序列化一个对象时,它所引用的所有其他对象也会被序列化,依此类推,直到序列化完整的对象树为止。
2️⃣如果超类实现 Serializable,则其子类会自动执行。
3️⃣反序列化可序列化类的实例时,构造函数将不会运行。
4️⃣如果超类未实现 Serializable,则在反序列化子类对象时,超类构造函数将运行。
5️⃣静态变量未序列化,因为它们不是对象本身的一部分。
6️⃣如果序列化集合或数组,则每个元素都必须可序列化。单个不可序列化的元素将导致序列化失败(NotSerializableException)。
7️⃣JDK 中的可序列化类包括原始包装器(Integer,Long,Double等)、String、Date、collection 类等。对于其他类,请查阅相关的 Javadoc 来了解它们是否可序列化。
8️⃣相关接口及类:
①java.io.Serializable
②java.io.Externalizable
③ObjectOutput
④ObjectInput
⑤ObjectOutputStream
⑥ObjectInputStream
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。