这是面试里会问到但现实中不会在意的小细节问题,今天拿java.util.ArrayList的序列化开题。

什么是序列化?为什么需要它?
想象一下,你有一个装满数据的ArrayList,目前需要:
- 保存到文件中,下次启动程序还能用
- 通过网络发送给另一台计算机
- 在不同的JVM之间传递数据
这就是序列化的用武之地:将对象转换为字节序列,实现”持久化”或”网络传输”。
先来一下实战演示:完整的序列化过程
示例代码
import java.io.*;
import java.util.ArrayList;
public class ArrayListSerializationDemo {
// 自定义可序列化的学生类
static class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
public static void main(String[] args) {
// 创建ArrayList并添加元素
ArrayList<Student> studentList = new ArrayList<>(100); // 初始容量100
studentList.add(new Student("Alice", 20));
studentList.add(new Student("Bob", 22));
studentList.add(new Student("Charlie", 21));
System.out.println("原始列表:");
System.out.println("大小: " + studentList.size());
System.out.println("内容: " + studentList);
// 序列化到文件
String filename = "students.ser";
serializeList(studentList, filename);
// 从文件反序列化
ArrayList<Student> deserializedList = deserializeList(filename);
System.out.println("
反序列化后的列表:");
System.out.println("大小: " + deserializedList.size());
System.out.println("内容: " + deserializedList);
// 验证是否相等
System.out.println("
两个列表是否相等: " +
studentList.equals(deserializedList));
}
// 序列化方法
private static void serializeList(ArrayList<Student> list, String filename) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filename))) {
oos.writeObject(list);
System.out.println("
序列化完成: " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
// 反序列化方法
@SuppressWarnings("unchecked")
private static ArrayList<Student> deserializeList(String filename) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filename))) {
return (ArrayList<Student>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
运行结果分析
原始列表:
大小: 3
内容: [Student{name='Alice', age=20}, Student{name='Bob', age=22}, Student{name='Charlie', age=21}]
序列化完成: students.ser
反序列化后的列表:
大小: 3
内容: [Student{name='Alice', age=20}, Student{name='Bob', age=22}, Student{name='Charlie', age=21}]
两个列表是否相等: true
发现:虽然原始ArrayList容量是100,但序列化文件只存储了3个实际元素!
ArrayList的序列化特殊性
问题:为什么不直接序列化整个数组?
ArrayList底层使用数组elementData存储元素,但这个数组常常有”闲置容量”:
// ArrayList内部结构
public class ArrayList<E> {
transient Object[] elementData; // 注意:transient关键字!
private int size; // 实际元素个数
}
// 示例:一个实际只有3个元素的ArrayList
ArrayList<String> list = new ArrayList<>(10); // 容量10
list.add("A");
list.add("B");
list.add("C");
// elementData.length = 10,但实际只有3个元素有用
如果直接序列化整个elementData数组,会浪费大量空间存储空位置!
解决方案:自定义序列化机制
ArrayList通过重写writeObject和readObject方法实现智能序列化:
1. writeObject:智能写入
// ArrayList序列化方法的简化版逻辑
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// 第一步:写入默认的序列化头信息
s.defaultWriteObject();
// 第二步:只写入实际元素个数(size),而不是数组长度
s.writeInt(size);
// 第三步:只序列化实际存在的元素(0到size-1)
for (int i = 0; i < size; i++) {
s.writeObject(elementData[i]); // 只写有数据的部分
}
}
2. readObject:准确重建
// ArrayList反序列化方法的简化版逻辑
private void readObject(java.io.ObjectOutputStream s)
throws java.io.IOException, ClassNotFoundException {
// 第一步:读取默认的序列化头信息
s.defaultReadObject();
// 第二步:读取实际元素个数
int capacity = s.readInt();
// 第三步:创建大小刚好的数组(避免空间浪费)
elementData = new Object[capacity];
// 第四步:逐个读取元素,准确重建
for (int i = 0; i < capacity; i++) {
elementData[i] = s.readObject();
}
}
深入原理:transient关键字的作用
public class ArrayList<E> {
// transient表明:这个字段不要使用默认的序列化机制
transient Object[] elementData;
// 但是size字段会被正常序列化
private int size;
}
默认序列化会序列化整个elementData数组(包括空位置),使用transient+自定义序列化,可以只存有效数据,反序列化时重建的ArrayList没有闲置容量,更加紧凑。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...



