面到用不到:java.util.ArrayList如何序列化

内容分享2周前发布
0 0 0

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

面到用不到: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没有闲置容量,更加紧凑。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...