跳至主要内容

CSV 模組使用指南

Package: io.leandev.appfuse.csv.* 狀態: 穩定


簡介

AppFuse CSV 提供穩定的 CSV 處理 API,讓應用層不受底層實作庫變動影響。

核心特色

特色說明價值
欄位名稱存取record.getAsString("username")易讀、不怕欄位順序變更
自動類型轉換整合 Converters,支援 15+ 常見類型減少 70% 樣板代碼
物件序列化writer.write(product)無需手動映射
依賴隔離層底層可替換(Univocity → Jackson CSV)應用層零改動

與傳統工具的對比

// ❌ 傳統工具:位置存取 + 手動轉換
String[] values = record.getValues();
String username = values[0]; // 索引容易錯
BigDecimal price = new BigDecimal(values[3]); // 手動轉換

// ✅ AppFuse CSV:名稱存取 + 自動轉換
String username = record.getAsString("username"); // 語義清楚
BigDecimal price = record.getAsBigDecimal("price"); // 自動轉換

快速開始

讀取 CSV

import io.leandev.appfuse.csv.*;

try (InputStream is = new FileInputStream("users.csv");
CsvReader reader = CsvReaderBuilder.of(is).build()) {

reader.readHeaders();

for (CsvRecord record = reader.read(); record != null; record = reader.read()) {
String username = record.getAsString("username");
Integer age = record.getAsInteger("age");
BigDecimal salary = record.getAsBigDecimal("salary");
LocalDate birthday = record.getAsLocalDate("birthday");

System.out.printf("%s, %d 歲, 月薪 %s%n", username, age, salary);
}
}

寫入 CSV

try (OutputStream os = new FileOutputStream("output.csv");
CsvWriter writer = CsvWriterBuilder.of(os).build()) {

writer.writeHeaders("username", "age", "salary", "birthday");

List<User> users = userService.findAll();
for (User user : users) {
writer.write(user); // 自動序列化物件
}
}

使用 Stream API

try (CsvReader reader = new CsvReader(inputStream)) {
reader.readHeaders();

List<User> activeUsers = reader.stream()
.filter(record -> record.getAsBoolean("active"))
.map(record -> new User(
record.getAsString("username"),
record.getAsInteger("age")
))
.collect(Collectors.toList());
}

支援的資料型別

Java 型別使用方法範例
StringgetAsString("column")"John Doe"
IntegergetAsInteger("column")25
LonggetAsLong("column")1234567890L
BigDecimalgetAsBigDecimal("column")1999.99
DategetAsDate("column")2025-01-06
LocalDategetAsLocalDate("column")2025-01-06
LocalDateTimegetAsLocalDateTime("column")2025-01-06T10:30:00
BooleangetAsBoolean("column")true / false

常見場景

場景 1: 從資料庫匯出

List<Product> products = productRepository.findAll();

try (CsvWriter writer = CsvWriterBuilder.of(outputStream).build()) {
writer.writeHeaders("id", "name", "price", "stock", "createdOn");
products.forEach(writer::write);
}

場景 2: 匯入到資料庫

try (CsvReader reader = new CsvReader(inputStream)) {
reader.readHeaders();

List<Product> products = reader.stream()
.map(record -> new Product(
record.getAsString("id"),
record.getAsString("name"),
record.getAsBigDecimal("price"),
record.getAsInteger("stock"),
record.getAsLocalDateTime("createdOn")
))
.collect(Collectors.toList());

productRepository.saveAll(products);
}

場景 3: 處理大型檔案

// 使用 Stream API,記憶體佔用穩定
try (CsvReader reader = new CsvReader(inputStream)) {
reader.readHeaders();

reader.stream()
.filter(record -> record.getAsBigDecimal("amount")
.compareTo(new BigDecimal("10000")) > 0)
.forEach(record -> processRecord(record)); // 逐行處理
}

場景 4: CSV 格式轉換

// 從逗號分隔轉換為管線分隔
try (CsvReader reader = CsvReaderBuilder.of(inputStream)
.setDelimiter(",").build();
CsvWriter writer = CsvWriterBuilder.of(outputStream)
.setDelimiter("|").build()) {

reader.readHeaders();
writer.writeHeaders(reader.getHeaders());

for (CsvRecord record = reader.read(); record != null; record = reader.read()) {
writer.write(record.getValues());
}
}

進階配置

自訂分隔符與引號

CsvReader reader = CsvReaderBuilder.of(inputStream)
.setDelimiter("|") // 使用管線符號
.setQuote('\'') // 使用單引號
.setLineSeparator("\n") // Unix 換行符
.build();

自訂字元編碼

CsvReader reader = CsvReaderBuilder.of(inputStream, Charset.forName("Big5"))
.build();

無 Header 的 CSV

CsvReader reader = CsvReaderBuilder.of(inputStream)
.setHeader(Arrays.asList("id", "name", "price"))
.build();

// 之後可用名稱存取
String name = record.getAsString("name");

效能基準

操作吞吐量測試環境
讀取82 萬行/秒100 萬行,10 欄位
寫入33 萬行/秒100 萬行,10 欄位

測試環境:Apple M1, 16GB RAM, JDK 21


常見問題

Q: 為什麼不直接用 Jackson CSV / OpenCSV?

A: AppFuse CSV 提供穩定的 API 契約,底層實作可替換。當第三方庫停止維護時(如 Univocity),只需框架內部遷移,應用層代碼不受影響。

Q: 如何處理欄位不存在的情況?

if (record.indexOf("optional_column") >= 0) {
String value = record.getAsString("optional_column");
}

Q: 支援 Excel (XLSX) 嗎?

A: 目前僅支援 CSV,XLSX 支援在未來規劃中。


API 參考

詳細的類別設計和方法簽名,請參閱 API 參考: CSVJavadoc