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 型別 | 使用方法 | 範例 |
|---|---|---|
| String | getAsString("column") | "John Doe" |
| Integer | getAsInteger("column") | 25 |
| Long | getAsLong("column") | 1234567890L |
| BigDecimal | getAsBigDecimal("column") | 1999.99 |
| Date | getAsDate("column") | 2025-01-06 |
| LocalDate | getAsLocalDate("column") | 2025-01-06 |
| LocalDateTime | getAsLocalDateTime("column") | 2025-01-06T10:30:00 |
| Boolean | getAsBoolean("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 參考: CSV 或 Javadoc。