如何使用 Java HashMap 寫入和讀取文件
1. 概述
在開發軟體時,經常需要將記憶體中的物件寫入文件,反之,將文件內容讀入物件。對於原始值和String
值來說這很簡單,但在處理資料結構和物件時會變得更加複雜。
一種常見的 Java 資料結構是HashMap
。在本教程中,我們將介紹使用HashMap
資料讀寫檔案的三種方法: Java Properties
、Java 物件序列化和使用第三方函式庫的 JSON 序列化。
2.使用Java Properties
類
映射的常見應用是屬性文件,其中包含表示應用程式配置的String
的鍵值對。 Java Properties
類別非常適合使用基於String
的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
方法將Properties
物件寫入該檔案:
File file = File.createTempFile("student", ".data");
try (OutputStream output = Files.newOutputStream(file.toPath())) {
props.store(output, null);
}
此方法採用OutputStream
(或Writer
)和一個可選的String
來新增註解。這裡,我們可以傳入null
。如果我們查看我們創建的文件,我們可以看到我們的學生資料:
student.firstName: Henry
student.lastName: Winter
要將檔案內容讀回Properties
對象,我們可以使用load
方法:
Properties propsFromFile = new Properties();
try (InputStream input = Files.newInputStream(file.toPath())) {
propsFromFile.load(input);
}
由於Properties
實作了Map<Object, Object>
(但僅包含String
鍵和值),因此我們可以透過串流傳輸stringPropertyNames
並將結果收集回映射中來取得原始映射:
HashMap<String, String> studentDataFromProps = propsFromFile.stringPropertyNames()
.stream()
.collect(Collectors.toMap(key -> key, props::getProperty));
assertThat(studentDataFromProps).isEqualTo(studentData);
使用Properties
很簡單,但前提是我們要處理的是具有String
鍵和值的HashMap
。對於任何其他映射,我們需要使用其他策略。
3. 使用物件序列化
Java 提供了Serializable
,這是一個用於在物件與位元組流之間進行轉換的介面。讓我們定義一個包含學生資料的自訂類別Student
。我們將讓它實作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
}
接下來,我們將建立學生 ID 到Student
實例的對應:
Map<Integer, Student> studentData = new HashMap<>();
studentData.put(1234, new Student("Henry", "Winter"));
studentData.put(5678, new Student("Richard", "Papen"));
然後,我們可以使用ObjectOutputStream
將HashMap
寫入檔案:
File file = File.createTempFile("student", ".data");
try (FileOutputStream fileOutput = new FileOutputStream(file);
ObjectOutputStream objectStream = new ObjectOutputStream(fileOutput)) {
objectStream.writeObject(studentData);
}
該文件將包含二進位資料並且不可讀。為了驗證我們的檔案是否正確,我們可以使用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);
請注意,我們必須將readObject
的結果轉換回HashMap<Integer, Student>.
我們在這裡忽略未經檢查的強制轉換警告,因為該檔案將只包含我們的HashMap
,但操作代碼應考慮類型安全。
Serializable
提供了一種相對簡單的方法來序列化和反序列化文件中的HashMap
,但可能並不總是能夠以這種方式序列化類別 - 特別是當我們處理無法修改的類別時。幸運的是,還有另一個選擇。
4. 使用 JSON 函式庫
JSON 是一種廣泛使用的用於指定資料鍵值對的格式。有幾個開源程式庫可用於在 Java 中使用 JSON。 JSON 的優點之一是它是人類可讀的 - 上面的學生資料地圖的 JSON 表示如下:
{
1234: {
"firstName": "Henry",
"lastName": "Winter"
},
5678: {
"firstName": "Richard",
"lastName": "Papen"
}
}
在這裡,我們將使用兩個最著名的函式庫 Jackson 和 Gson,將HashMap
與 JSON 檔案相互轉換。
4.1.傑克森
Jackson 是 Java 中 JSON 序列化的常見選項。我們只會介紹序列化簡單資料結構所需的基礎知識 - 有關更多信息,請參閱我們的 Jackson 教程。
使用上面相同的學生資料映射,我們可以建立一個 Jackson ObjectMapper
並使用它將我們的HashMap
作為 JSON 寫入檔案:
ObjectMapper mapper = new ObjectMapper();
File file = File.createTempFile("student", ".data");
try (FileOutputStream fileOutput = new FileOutputStream(file)) {
mapper.writeValue(fileOutput, studentData);
}
類似地,我們可以使用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);
因為HashMap
是參數化類型,所以我們必須建立一個TypeReference
,以便 Jackson 知道如何將 JSON 檔案反序列化回HashMap<Integer, Student>
。
將Student
類別轉換為 JSON 不需要特殊的介面或類別修改 - 我們甚至可以放棄使用Serializable
介面。然而,這裡也需要注意的是,在反序列化過程中,Jackson 要求類別有一個預設的無參構造函數。儘管許多類別都提供了此要求,但如果類別無法更改,則此要求可能會成為問題。
4.2.格森
Gson 是 Java 中 JSON 序列化的另一個常見選擇。
我們將再次使用上面的映射並定義一個Gson
實例將其序列化為 JSON 檔案:
Gson gson = new Gson();
File file = File.createTempFile("student", ".data");
try (FileWriter writer = new FileWriter(file)) {
gson.toJson(studentData, writer);
}
將檔案讀回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);
與 Jackson 類似,Gson 需要類型資訊來反序列化參數化HashMap
– 這是使用 Gson 的TypeToken
以 Java Reflection API Type
的形式提供的。
Gson 具有與預設建構函式相同的要求,但提供了InstanceCreator
介面以在未提供該介面的情況下提供協助。
5. 總結
在本教程中,我們討論了使用HashMap
資料寫入和讀取檔案的三種方法。
對於String
的簡單映射,Java Properties
為我們提供了一個簡單的解決方案。物件序列化是另一個核心 Java 功能,它為我們可以修改的類別提供了更大的靈活性。對於我們無法編輯相關類別的情況(或者如果我們需要人類可讀的格式),Jackson 和 Gson 等開源程式庫提供了有用的 JSON 序列化工具。
與往常一樣,所有程式碼都可以在 GitHub 上取得。