1. 概述
开发中经常需要将内存中的对象写入文件,或从文件读取内容到对象。处理基本类型和字符串很简单,但遇到数据结构和对象就复杂了。
Java 中常用的 HashMap
就是个典型例子。本文介绍三种处理 HashMap
文件读写的方法:Java Properties
类、对象序列化、以及第三方库的 JSON 序列化。
2. 使用 Java Properties 类
属性文件是 HashMap
的常见应用场景,它存储字符串键值对作为应用配置。Java 的 Properties
类特别适合处理字符串类型的 HashMap
。比如创建学生数据映射:
Map<String, String> studentData = new HashMap<>();
studentData.put("student.firstName", "Henry");
studentData.put("student.lastName", "Winter");
Properties
类实现了 Map<Object, Object>
,所以能轻松接收 HashMap
的所有值:
Properties props = new Properties();
props.putAll(studentData);
创建临时文件并用 store
方法写入:
File file = File.createTempFile("student", ".data");
try (OutputStream output = Files.newOutputStream(file.toPath())) {
props.store(output, null);
}
此方法接收 OutputStream
(或 Writer
)和可选的注释字符串(这里传 null
)。生成的文件内容如下:
student.firstName: Henry
student.lastName: Winter
读取文件回 Properties
对象使用 load
方法:
Properties propsFromFile = new Properties();
try (InputStream input = Files.newInputStream(file.toPath())) {
propsFromFile.load(input);
}
由于 Properties
实际只包含字符串键值对,可通过流式处理还原原始 HashMap
:
HashMap<String, String> studentDataFromProps = propsFromFile.stringPropertyNames()
.stream()
.collect(Collectors.toMap(key -> key, props::getProperty));
assertThat(studentDataFromProps).isEqualTo(studentData);
✅ 优点:简单直接
❌ 限制:仅适用于字符串键值对的 HashMap
3. 对象序列化方案
Java 提供了 Serializable
接口实现对象与字节流的转换。先定义可序列化的 Student
类(按规范设置 serialVersionUID
):
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
// 标准getter/setter/equals/hashCode方法
}
创建学号到学生对象的映射:
Map<Integer, Student> studentData = new HashMap<>();
studentData.put(1234, new Student("Henry", "Winter"));
studentData.put(5678, new Student("Richard", "Papen"));
用 ObjectOutputStream
写入文件:
File file = File.createTempFile("student", ".data");
try (FileOutputStream fileOutput = new FileOutputStream(file);
ObjectOutputStream objectStream = new ObjectOutputStream(fileOutput)) {
objectStream.writeObject(studentData);
}
⚠️ 注意:生成的是二进制文件,人类不可读。验证文件正确性可用 ObjectInputStream
读取:
Map<Integer, Student> studentsFromFile;
try (FileInputStream fileReader = new FileInputStream(file);
ObjectInputStream objectStream = new ObjectInputStream(fileReader)) {
studentsFromFile = (HashMap<Integer, Student>) objectStream.readObject();
}
assertThat(studentsFromFile).isEqualTo(studentData);
⚠️ 踩坑点:需要强制类型转换,且类型安全需自行保障(示例中忽略未检查转换警告)
✅ 优点:核心Java特性,灵活性高
❌ 限制:类必须实现 Serializable
,且无法修改第三方类时不可用
4. JSON 库方案
JSON 是通用的键值数据格式,人类可读性强。学生数据的 JSON 示例:
{
1234: {
"firstName": "Henry",
"lastName": "Winter"
},
5678: {
"firstName": "Richard",
"lastName": "Papen"
}
}
下面用 Jackson 和 Gson 两个主流库实现 JSON 序列化。
4.1. Jackson 方案
Jackson 是 Java 中常用的 JSON 序列化库。创建 ObjectMapper
写入文件:
ObjectMapper mapper = new ObjectMapper();
File file = File.createTempFile("student", ".data");
try (FileOutputStream fileOutput = new FileOutputStream(file)) {
mapper.writeValue(fileOutput, studentData);
}
读取文件回 HashMap
需要类型信息:
Map<Integer, Student> mapFromFile;
try (FileInputStream fileInput = new FileInputStream(file)) {
TypeReference<HashMap<Integer, Student>> mapType
= new TypeReference<HashMap<Integer, Student>>() {};
mapFromFile = mapper.readValue(fileInput, mapType);
}
assertThat(mapFromFile).isEqualTo(studentData);
⚠️ 关键点:通过 TypeReference
指定泛型类型,否则无法正确反序列化。
✅ 优点:无需修改类定义(可移除 Serializable
)
❌ 限制:要求类有无参构造函数
4.2. Gson 方案
Gson 是另一个流行的 JSON 库。序列化代码:
Gson gson = new Gson();
File file = File.createTempFile("student", ".data");
try (FileWriter writer = new FileWriter(file)) {
gson.toJson(studentData, writer);
}
反序列化使用 TypeToken
提供类型信息:
Map<Integer, Student> studentsFromFile;
try (FileReader reader = new FileReader(file)) {
Type mapType = new TypeToken<HashMap<Integer, Student>>() {}.getType();
studentsFromFile = gson.fromJson(reader, mapType);
}
assertThat(studentsFromFile).isEqualTo(studentData);
✅ 优点:API 简洁,提供 InstanceCreator
接口处理无参构造问题
❌ 限制:同样依赖无参构造函数(但有补救方案)
5. 方案总结
三种 HashMap
文件读写方案对比:
场景 | 推荐方案 | 关键优势 | 主要限制 |
---|---|---|---|
纯字符串键值对 | Properties 类 |
简单直接 | 仅支持字符串 |
可修改的自定义类 | 对象序列化 | 核心Java特性 | 需实现 Serializable |
不可修改类/可读性需求 | JSON 库(Jackson/Gson) | 人类可读/无需修改类 | 依赖无参构造 |
选择建议:
- 配置文件场景 →
Properties
类 - 完全可控的内部类 → 对象序列化
- 第三方类/需要可读性 → JSON 库
所有示例代码可在 GitHub 获取。