CSV (Comma-Separated Values) 是最普遍的表格資料交換格式,**但 RFC 4180 規範包含引號包裹、逗號轉義、換行符嵌入、雙引號跳脫**等細節。手工 split(",") 僅能處理最簡單情況。本文從基礎手工解析出發,逐步引導至生產級庫選型。
import java.io.*;
import java.util.*;
public class HandleCsv {
public static void main(String[] args) throws IOException {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("test.csv"), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) {
// 簡單分割:不處理引號內逗號、換行等
String[] fields = line.split(",");
String name = fields[0].trim();
String age = fields[1].trim();
String birthday = fields[2].trim();
System.out.printf("%s\t%s\t%s%n", name, age, birthday);
}
}
}
}
alice, 22, 1992/03/05
bob, 33, 1981/11/21
cart, 40, 1974/07/13
| CSV 特性 | 範例 | split(",") 結果 | 正確結果 |
|---|---|---|---|
| 欄位含逗號 | "台北, 信義區", 25 | 3 欄位 | 2 欄位 |
| 欄位含換行 | "第一行\n第二行", 30 | 讀成兩行 | 1 列 2 欄 |
| 欄位含雙引號 | "He said \"Hi\"", 20 | 亂碼 | 1 欄位 |
| 前後空白 | alice , 22 | 含空白 | 需 trim |
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.10.0</version>
</dependency>
import org.apache.commons.csv.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class CommonsCsvDemo {
public static void main(String[] args) throws IOException {
try (Reader reader = new InputStreamReader(
new FileInputStream("data.csv"), StandardCharsets.UTF_8);
CSVParser parser = CSVFormat.DEFAULT
.withFirstRecordAsHeader() // 第一行作標題
.withTrim() // 自動 trim
.parse(reader)) {
for (CSVRecord record : parser) {
String name = record.get("name");
String age = record.get("age");
String birthday = record.get("birthday");
System.out.printf("%s | %s | %s%n", name, age, birthday);
}
}
}
}
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.7.1</version>
</dependency>
import com.opencsv.bean.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class OpenCsvDemo {
// 對應 CSV 欄位的 Java Bean
public static class Person {
@CsvBindByName private String name;
@CsvBindByName private int age;
@CsvBindByName private String birthday;
// getter/setter/toString 省略
}
public static void main(String[] args) throws IOException {
try (Reader reader = new InputStreamReader(
new FileInputStream("data.csv"), StandardCharsets.UTF_8)) {
CsvToBean csvToBean = new CsvToBeanBuilder(reader)
.withType(Person.class)
.withIgnoreLeadingWhiteSpace(true)
.build();
for (Person p : csvToBean) {
System.out.println(p);
}
}
}
}
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.15.2</version>
</dependency>
import com.fasterxml.jackson.dataformat.csv.*;
import com.fasterxml.jackson.databind.*;
import java.io.*;
import java.util.*;
public class JacksonCsvDemo {
public static class Person {
public String name;
public int age;
public String birthday;
}
public static void main(String[] args) throws IOException {
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(Person.class).withHeader();
try (MappingIterator it = mapper.readerFor(Person.class)
.with(schema)
.readValues(new File("data.csv"))) {
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
}
| 場景 | 推薦 | 理由 |
|---|---|---|
| 輕量、零依賴傾向、標準 RFC 4180 | Apache Commons CSV | API 簡潔、無第三方依賴、Spring 內建 |
| 需映射到 Bean、自訂轉換器、註解驅動 | OpenCSV | 功能最全、Bean 映射成熟、社群活躍 |
| 高效能、大檔案流式、已用 Jackson 生態 | Jackson CSV | 零拷貝流式、與 JSON 共用 ObjectMapper 配置 |
| Android / 極度受限環境 | 手工解析 + 狀態機 | 無庫體積,但需自行處理邊界案例 |
StandardCharsets.UTF_8。Charset.forName("Big5") / StandardCharsets.ISO_8859_1(需確認實際編碼)。// 所有主流庫均支援 Iterator/Stream 流式讀取,不載入整檔到記憶體
try (CSVParser parser = CSVFormat.DEFAULT.parse(reader)) {
for (CSVRecord record : parser) { // 一行一行處理
process(record);
}
}
// 或 Java 8 Stream
parser.stream().forEach(this::process);
split(",") 僅限教學/極簡 CSV**,生產環境**必須使用成熟庫**。避坑指南:CSV 看起來像文字,實則是**有結構的資料**。任何「我自己寫個 split 就好」的念頭,都是技術債的開始。引入一個 100KB 的庫,換來的是 RFC 4180 完整相容與無數個深夜的安睡。