首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java JSON 文件讀寫最佳實踐:Jackson、Gson 與 NIO.2 完整指南

     編輯:Java 相關知識

前言

JSON 是現代應用的標準數據交換格式。Java 生態中,**Jackson** 和 **Gson** 是兩大主流 JSON 庫。配合 JDK 7+ `java.nio.file` (NIO.2) 的流式 API,可實現高效、安全、簡潔的文件讀寫。本文展示生產級代碼模板,涵蓋對象映射、流式處理大文件、異常處理、線程安全配置。

依賴配置 (Maven)

<!-- 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>

核心工具類:JsonFileUtils (基於 Jackson)

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 加字段不導致舊版本報錯

Jackson vs Gson 選型建議

  • 選 Jackson:企業級應用、Spring 生態、需高效能/流式/多模塊 (CBOR, MessagePack, YAML)、複雜多態/泛型處理。
  • 選 Gson:Android 專案、簡單 POJO 轉換、不想引入較大依賴 (Jackson ~1.5MB vs Gson ~300KB)、偏好 `toJson/fromJson` 簡潔 API。

總結

現代 Java JSON 文件操作應採用:**Jackson + NIO.2 + 原子寫入 + 流式大文件處理**。`JsonFileUtils` 封裝了線程安全的 `ObjectMapper`、泛型讀取、臨時文件原子替換、流式遍歷等生產級模式,可直接納入項目公共庫。拒絕手動 `FileReader`/`StringBuilder` 拼接字符串,擁抱類型安全、記憶體高效、故障恢復能力強的標準化方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved