JSON 是現代應用的標準數據交換格式。Java 生態中,**Jackson** 和 **Gson** 是兩大主流 JSON 庫。配合 JDK 7+ `java.nio.file` (NIO.2) 的流式 API,可實現高效、安全、簡潔的文件讀寫。本文展示生產級代碼模板,涵蓋對象映射、流式處理大文件、異常處理、線程安全配置。
<!-- Jackson (推薦:功能豐富、效能最佳、Spring Boot 默認) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
<!-- Gson (輕量、API 簡單、Android 友好) -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.List;
public final class JsonFileUtils {
// 單例 ObjectMapper:線程安全、昂貴創建、可複用
private static final ObjectMapper MAPPER = createMapper();
private static ObjectMapper createMapper() {
ObjectMapper mapper = new ObjectMapper();
// 1. 支援 Java 8 時間類型 (LocalDateTime 等)
mapper.registerModule(new JavaTimeModule());
// 2. 序列化日期為 ISO-8601 字符串,而非時間戳
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 3. 忽略未知屬性 (反序列化時 JSON 含類中無的字段不報錯)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 4. 美化輸出 (文件存儲建議開啟,網絡傳輸關閉)
mapper.enable(SerializationFeature.INDENT_OUTPUT);
return mapper;
}
private JsonFileUtils() {} // 工具類禁止實例化
// ==================== 寫入 ====================
/**
* 將對象寫入 JSON 文件 (覆蓋模式)
* @param path 文件路徑
* @param object 任意對象 (POJO、List、Map 等)
* @param createParents 是否自動創建父目錄
*/
public static void write(Path path, Object object, boolean createParents) throws IOException {
if (createParents) {
Files.createDirectories(path.getParent());
}
// 原子寫入:先寫臨時文件,再移動替換 (防止寫入中斷損壞原文件)
Path temp = Files.createTempFile(path.getParent(), path.getFileName().toString(), ".tmp");
try {
MAPPER.writeValue(Files.newOutputStream(temp), object);
Files.move(temp, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (IOException e) {
Files.deleteIfExists(temp); // 失敗清理臨時文件
throw e;
}
}
/** 便捷重載:String 路徑 */
public static void write(String path, Object object) throws IOException {
write(Paths.get(path), object, true);
}
// ==================== 讀取 ====================
/**
* 從 JSON 文件讀取為指定類型
* @param path 文件路徑
* @param valueType 目標類型 (如 User.class, new TypeReference<List<User>>(){})
*/
public static T read(Path path, JavaType valueType) throws IOException {
return MAPPER.readValue(Files.newInputStream(path), valueType);
}
/** 便捷:讀取為 POJO */
public static T read(Path path, Class clazz) throws IOException {
return MAPPER.readValue(Files.newInputStream(path), clazz);
}
/** 便捷:讀取為泛型集合 (需 TypeReference 捕獲泛型) */
public static T read(Path path, TypeReference typeRef) throws IOException {
return MAPPER.readValue(Files.newInputStream(path), typeRef);
}
/** 字符串路徑重載 */
public static T read(String path, Class clazz) throws IOException {
return read(Paths.get(path), clazz);
}
public static T read(String path, TypeReference typeRef) throws IOException {
return read(Paths.get(path), typeRef);
}
// ==================== 流式處理大文件 (低記憶體) ====================
/**
* 逐個讀取 JSON 陣列中的對象 (適用超大文件,避免 OOM)
* JSON 結構必須為頂層陣列: [ {...}, {...}, ... ]
*/
public static void streamArray(Path path, Class elementClass, ElementConsumer consumer) throws IOException {
try (JsonParser parser = MAPPER.getFactory().createParser(Files.newInputStream(path))) {
// 移動到陣列開始 '['
while (parser.nextToken() != JsonToken.START_ARRAY) {
if (parser.currentToken() == null) throw new IOException("JSON 非陣列結構");
}
// 遍歷陣列元素
while (parser.nextToken() != JsonToken.END_ARRAY) {
T element = MAPPER.readValue(parser, elementClass);
consumer.accept(element);
}
}
}
@FunctionalInterface
public interface ElementConsumer {
void accept(T element) throws IOException;
}
// ==================== Gson 版本 (可選) ====================
// 同理可構建 GsonUtils,Gson 線程安全,API 為 toJson/fromJson
}
public class User {
public long id;
public String name;
public String email;
public LocalDateTime createdAt;
// getter/setter 或 public 字段
}
public class Demo {
public static void main(String[] args) throws IOException {
Path file = Paths.get("users.json");
// 1. 寫入列表
List users = List.of(
new User(1, "Alice", "alice@example.com", LocalDateTime.now()),
new User(2, "Bob", "bob@example.com", LocalDateTime.now())
);
JsonFileUtils.write(file, users);
System.out.println("寫入完成");
// 2. 讀取列表 (TypeReference 保留泛型)
List loaded = JsonFileUtils.read(file, new TypeReference>() {});
loaded.forEach(u -> System.out.println(u.name));
// 3. 流式處理大文件 (每次只在記憶體保留一個對象)
JsonFileUtils.streamArray(file, User.class, user -> {
System.out.println("流式處理: " + user.email);
// 可直接寫入資料庫、發送消息隊列等
});
}
}
| 實踐 | 代碼體現 | 理由 |
|---|---|---|
| ObjectMapper 單例複用 | `private static final ObjectMapper MAPPER` | 創建昂貴 (緩存序列化器)、線程安全 |
| 原子寫入 (臨時文件 + ATOMIC_MOVE) | `Files.move(temp, path, ..., ATOMIC_MOVE)` | 防止進程崩潰/斷電導致文件損壞、讀取到半截數據 |
| 流式處理大文件 | `JsonParser` + `streamArray` | O(1) 記憶體,可處理 GB 級 JSON 陣列 |
| TypeReference 捕獲泛型 | `new TypeReference
| 解決 Java 類型擦除,正確反序列化泛型集合 |
| JavaTimeModule | `registerModule(new JavaTimeModule())` | 原生支援 LocalDateTime/Instant,輸出 ISO-8601 字符串 |
| FAIL_ON_UNKNOWN_PROPERTIES = false | `configure(..., false)` | 向前兼容:新版 JSON 加字段不導致舊版本報錯 |
現代 Java JSON 文件操作應採用:**Jackson + NIO.2 + 原子寫入 + 流式大文件處理**。`JsonFileUtils` 封裝了線程安全的 `ObjectMapper`、泛型讀取、臨時文件原子替換、流式遍歷等生產級模式,可直接納入項目公共庫。拒絕手動 `FileReader`/`StringBuilder` 拼接字符串,擁抱類型安全、記憶體高效、故障恢復能力強的標準化方案。