1. Overview
1.概述
When developing software, it’s often necessary to write an object in memory to a file and, conversely, to read file contents into an object. This is simple to do with primitive and String values but becomes more complicated when dealing with data structures and objects.
在开发软件时,经常需要将内存中的对象写入文件,反之,也需要将文件内容读入对象。这在处理基元值和 String 值时很简单,但在处理数据结构和对象时就变得复杂了。
One common Java data structure is the HashMap. In this tutorial, we’ll cover three methods for reading and writing files with HashMap data: Java Properties, Java object serialization, and JSON serialization using third-party libraries.
HashMap 是一种常见的 Java 数据结构。在本教程中,我们将介绍使用 HashMap 数据读写文件的三种方法:Java 属性、Java 对象序列化和使用第三方库的 JSON 序列化。
2. Using the Java Properties Class
2.使用 Java Properties 类
A common application of a map is a properties file, which contains key-value pairs of Strings representing application configuration. The Java Properties class is well-suited to working with String-based HashMaps. For example, let’s create a map of student data:
映射的一个常见应用是属性文件,其中包含代表应用程序配置的 String 的键值对。Java Properties 类非常适合与基于 String 的 HashMaps 一起使用。例如,让我们创建一个学生数据映射:
Map<String, String> studentData = new HashMap<>();
studentData.put("student.firstName", "Henry");
studentData.put("student.lastName", "Winter");
The Properties class implements Map<Object, Object>, so it’s easy to read in all values from a HashMap:
Properties 类实现了 Map<Object, Object>,因此很容易从 HashMap 中读入所有值:
Properties props = new Properties();
props.putAll(studentData);
We can create a temporary file and use the store method to write the Properties object to the file:
我们可以创建一个临时文件,并使用 store 方法将 Properties 对象写入该文件:
File file = File.createTempFile("student", ".data");
try (OutputStream output = Files.newOutputStream(file.toPath())) {
props.store(output, null);
}
This method takes an OutputStream (or Writer) and an optional String for adding comments to the file. Here, we can pass in null. If we view the file we created, we can see our student data:
该方法接收一个 OutputStream(或 Writer)和一个可选的 String 用于向文件添加注释。在这里,我们可以输入 null。如果我们查看创建的文件,就可以看到我们的学生数据:
student.firstName: Henry
student.lastName: Winter
To read the file content back into a Properties object, we can use the load method:
要将文件内容读回 Properties 对象,我们可以使用 load 方法:
Properties propsFromFile = new Properties();
try (InputStream input = Files.newInputStream(file.toPath())) {
propsFromFile.load(input);
}
Since Properties implements Map<Object, Object> (but contains only String keys and values), we can get our original map back by streaming stringPropertyNames and collecting the results back into a map:
由于 Properties 实现了 Map<Object,Object>(但只包含 String 键和值),因此我们可以通过流式传输 stringPropertyNames 并将结果收集到 map 中,从而返回原始 map:
HashMap<String, String> studentDataFromProps = propsFromFile.stringPropertyNames()
.stream()
.collect(Collectors.toMap(key -> key, props::getProperty));
assertThat(studentDataFromProps).isEqualTo(studentData);
Using Properties is straightforward, but only if we’re dealing with a HashMap that has String keys and values. For any other mapping, we’ll need to use another strategy.
使用 Properties 非常简单,但前提是我们处理的是具有 String 键和值的 HashMap 。对于其他映射,我们需要使用另一种策略。
3. Working with Object Serialization
3.使用对象序列化
Java provides Serializable, an interface for converting objects to and from a byte stream. Let’s define a custom class, Student, that contains student data. We’ll have it implement Serializable (and set a serialVersionUID as recommended in the documentation):
Java 提供了 Serializable 接口,用于将对象转换为字节流或从字节流转换为字节流。让我们定义一个包含学生数据的自定义类 学生。我们将让它实现 Serializable (并按照文档中的建议设置 serialVersionUID ):
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
// Standard getters, setters, equals and hashCode methods
}
Next, we’ll create a mapping of student ID to a Student instance:
接下来,我们将创建学生 ID 到 Student 实例的映射:
Map<Integer, Student> studentData = new HashMap<>();
studentData.put(1234, new Student("Henry", "Winter"));
studentData.put(5678, new Student("Richard", "Papen"));
Then, we can use an ObjectOutputStream to write the HashMap to a file:
然后,我们可以使用 ObjectOutputStream 将 HashMap 写入文件:
File file = File.createTempFile("student", ".data");
try (FileOutputStream fileOutput = new FileOutputStream(file);
ObjectOutputStream objectStream = new ObjectOutputStream(fileOutput)) {
objectStream.writeObject(studentData);
}
This file will contain binary data and is not human-readable. To verify our file is correct, we can read it back into a HashMap using an ObjectInputStream:
该文件将包含二进制数据,人类无法读取。要验证文件是否正确,我们可以使用 ObjectInputStream 将其读回 HashMap 中:
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);
Note that we had to cast the result of readObject back to a HashMap<Integer, Student>. We ignore the unchecked cast warning here since the file will only ever contain our HashMap, but type safety should be considered for operational code.
请注意,我们必须将 readObject 的结果转回 HashMap<Integer, Student>。我们在此忽略了未选中的转换警告,因为文件中永远只包含我们的 HashMap ,但在操作代码中应考虑类型安全。
Serializable provides a relatively simple way to serialize and deserialize a HashMap from a file, but it may not always be possible to serialize a class in this way – particularly if we’re dealing with a class that we can’t modify. Luckily, there is still another option.
Serializable 提供了一种从文件序列化和反序列化 HashMap 的相对简单的方法,但并不总能以这种方式序列化类,尤其是当我们处理的是一个无法修改的类时。幸运的是,我们还有另一种选择。
4. Using JSON Libraries
4.使用 JSON 库
JSON is a widely-used format for specifying key-value pairs of data. There are several open-source libraries available for working with JSON in Java. One advantage of JSON is that it’s human-readable – a JSON representation of our map of student data from above looks like this:
JSON 是一种广泛使用的格式,用于指定键值对数据。有几个开源库可用来在 Java 中处理 JSON。JSON 的一个优点是可由人类读取–上面的学生数据地图的 JSON 表示形式如下所示:
{
1234: {
"firstName": "Henry",
"lastName": "Winter"
},
5678: {
"firstName": "Richard",
"lastName": "Papen"
}
}
Here, we’ll use two of the most well-known libraries, Jackson and Gson, to convert our HashMap to and from a JSON file.
在这里,我们将使用两个最著名的库,即 Jackson 和 Gson,将 HashMap 转换为 JSON 文件,或从 JSON 文件转换为 HashMap 。
4.1. Jackson
4.1.Jackson
Jackson is a common option for JSON serialization in Java. We’ll only cover the basics necessary to serialize our simple data structure – for more information, see our Jackson tutorials.
Jackson 是 Java 中 JSON 序列化的常用选项。我们将只介绍序列化简单数据结构所需的基础知识,如需了解更多信息,请参阅 Jackson 教程。
Using the same map of student data above, we can create a Jackson ObjectMapper and use it to write our HashMap as JSON to a file:
使用上述相同的学生数据映射,我们可以创建一个 Jackson ObjectMapper 并使用它将我们的 HashMap 以 JSON 格式写入文件:
ObjectMapper mapper = new ObjectMapper();
File file = File.createTempFile("student", ".data");
try (FileOutputStream fileOutput = new FileOutputStream(file)) {
mapper.writeValue(fileOutput, studentData);
}
Similarly, we can use ObjectMapper to read our file back into a new HashMap instance:
同样,我们可以使用 ObjectMapper 将文件读回一个新的 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);
Because HashMap is a parameterized type, we had to create a TypeReference so Jackson knows how to deserialize our JSON file back into a HashMap<Integer, Student>.
由于 HashMap 是一种参数化类型,我们必须创建一个 TypeReference 以便 Jackson 知道如何将 JSON 文件反序列化为 HashMap<Integer, Student> 。
No special interfaces or class modifications were necessary to convert our Student class to JSON – we could even drop the use of the Serializable interface. However, it’s also important to note here that during deserialization, Jackson requires the class to have a default no-argument constructor. Though many classes provide one, this requirement can be an issue if the class can’t be changed.
将我们的 Student 类转换为 JSON 不需要特殊的接口或类修改,我们甚至可以放弃使用 Serializable 接口。不过,这里还需要注意的是,在反序列化过程中,Jackson 要求类具有默认的无参数构造函数。虽然许多类都提供了无参数构造函数,但如果类无法更改,这一要求就会成为一个问题。
4.2. Gson
4.2 Gson
Gson is another common choice for JSON serialization in Java.
Gson 是 Java 中 JSON 序列化的另一个常见选择。
We’ll again use our map from above and define a Gson instance to serialize it to a JSON file:
我们将再次使用上面的 map,并定义一个 Gson 实例,将其序列化为 JSON 文件:
Gson gson = new Gson();
File file = File.createTempFile("student", ".data");
try (FileWriter writer = new FileWriter(file)) {
gson.toJson(studentData, writer);
}
Reading the file back into a HashMap instance is simple:
将文件读回 HashMap 实例非常简单:
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);
Similar to Jackson, Gson needs type information to deserialize the parameterized HashMap – this is provided in the form of a Java Reflection API Type using Gson’s TypeToken.
与 Jackson 类似,Gson 也需要类型信息来反序列化参数化的 HashMap – 这是以 Java Reflection API Type 的形式提供的,使用的是 Gson 的 TypeToken。
Gson has the same requirement of a default constructor but provides the InstanceCreator interface to assist in the case where one is not provided.
Gson 对默认构造函数有相同的要求,但提供了 InstanceCreator 接口,以便在未提供构造函数的情况下提供帮助。
5. Summary
5.总结
In this tutorial, we discussed three methods for writing to and reading from a file with HashMap data.
在本教程中,我们讨论了使用 HashMap 数据写入和读出文件的三种方法。
For simple mappings of Strings, Java Properties offers us a straightforward solution. Object serialization is another core Java feature that provides us with more flexibility for classes that we can modify. For cases where we can’t edit the class in question (or if we need a human-readable format), open-source libraries like Jackson and Gson provide useful tools for JSON serialization.
对于 Strings 的简单映射,Java Properties 为我们提供了直接的解决方案。对象序列化是 Java 的另一个核心功能,它为我们提供了更多可以修改类的灵活性。对于我们无法编辑相关类的情况(或者我们需要一种人类可读的格式),Jackson 和 Gson 等开源库为 JSON 序列化提供了有用的工具。
As always, all code is available over on GitHub.
一如既往,所有代码均可在 GitHub 上获取。