Apache Commons CSV 快速入门与集成示例

Apache Commons CSV 1.14.1 快速入门:Maven 集成、CSVParser 流式读取、CSVPrinter 写出、表头枚举映射、IOException 异常处理与百万行大文件流式解析实测,示例可独立运行。

Apache Commons CSVApache Commons CSV2026/10/13

Apache Commons CSV 快速入门与集成示例

目标:十分钟内完成 Apache Commons CSV 1.14.1 的工程集成,掌握读取(CSVParser/CSVRecord)、写出(CSVPrinter)、表头枚举映射、异常处理与大文件流式处理五项核心技能,每个示例均给出真实编译运行输出。已在 Ubuntu 22.04 / CentOS 8(JDK 17 + Maven)实测通过。

一、环境信息

cat /etc/os-release | head -2 && uname -m

回显:

NAME="Ubuntu"
PRETTY_NAME="Ubuntu 22.04.5 LTS"
x86_64
java -version | head -1 && mvn -version | head -1

回显:

openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.6.3
项目版本
Commons CSV1.14.1(中央仓库 latest/release)
JDKOpenJDK 17.0.20.1(库最低要求 Java 8)
Maven3.6.3

版本确认依据:中央仓库元数据 <latest>1.14.1</latest>、官方 RELEASE-NOTES 原文 "Commons CSV requires at least Java 8"。

二、Maven 集成五步走

💡 技术拓展:CSV 解析库选型对比(为什么选 Commons CSV)。

方案依赖方言支持表头映射适用场景
JDK 手写 split(",")无不识别引号内逗号/换行手工实现教学演示
OpenCSVopencsv + 若干依赖较全CsvToBean 注解映射JavaBean 绑定
Super CSVsuper-csv 单包较全CsvDozerBeanMapper 等校验/POJO,更新缓慢
Jackson CSV随 Jackson 体系常见方言ObjectMapper 绑定Jackson 项目复用
Commons CSV1.14.1 带 commons-io/codec 2 个 compile 依赖12 个内置预设枚举表头映射(Enum get)轻量、方言全

点名本文所用:Commons CSV 1.14.1,内置方言开箱即用,读写同一 CSVFormat 保证对称。

2.1 第一步:创建工程目录

mkdir -p /opt/csvapp/src/main/java/demo && cd /opt/csvapp

回显无输出。

2.2 第二步:编写 pom.xml

cat > pom.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>demo</groupId>
  <artifactId>csv-app</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-csv</artifactId>
      <version>1.14.1</version>
    </dependency>
  </dependencies>
</project>
EOF

回显无输出。pom 说明:依赖只声明 commons-csv;它还会带出 commons-io 2.20.0 与 commons-codec 1.19.0 两个 compile 传递依赖(下一步实测可见)。

2.3 第三步:验证依赖树

mvn dependency:tree

回显(输出节选,实测):

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ csv-app ---
[INFO] demo:csv-app:jar:1.0.0
[INFO] \- org.apache.commons:commons-csv:jar:1.14.1:compile
[INFO]    +- commons-io:commons-io:jar:2.20.0:compile
[INFO]    \- commons-codec:commons-codec:jar:1.19.0:compile
[INFO] BUILD SUCCESS

2.4 第四步:编译打包

mvn -q package

回显(首次运行打印依赖下载日志,静默模式退出码 0 即成功)。

2.5 第五步:运行

java -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvQuick

(示例类 CsvQuick 见第三章,回显见该章。)无法使用 Maven 的服务器可改用官方发行包手动方案:下载 commons-csv-1.14.1-bin.tar.gz(sha512 校验值为 22d25127957b7694 开头的 128 位哈希),连同 commons-io-2.20.0.jar 与 commons-codec-1.19.0.jar 三个 jar 一起放进 -cp,缺依赖会在运行期抛 NoClassDefFoundError: org/apache/commons/io/output/AppendableOutputStream。

三、读取:CSVParser 与 CSVRecord

核心模式:CSVParser.parse 静态工厂 + try-with-resources + for-each 迭代,内存占用与文件大小无关。

3.1 按列名读取

cd /opt/csvapp && cat > src/main/java/demo/CsvQuick.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;

public class CsvQuick {
    public static void main(String[] args) throws IOException {
        String data = "city,pop\nBeijing,2189\nShanghai,2487\n";
        CSVFormat fmt = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get();
        try (CSVParser parser = CSVParser.parse(data, fmt)) {
            for (CSVRecord r : parser) {
                System.out.println(r.get("city") + " pop=" + r.get("pop"));
            }
        }
    }
}
EOF
mvn -q package && java -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvQuick

回显:

Beijing pop=2189
Shanghai pop=2487

代码要点逐项说明:setHeader() 空参表示首行即表头;setSkipHeaderRecord(true) 读取时跳过它;r.get("city") 按列名取值;try-with-resources 保证关闭底层流。1.14.1 中 Builder 收口方法是 get()(build() 已标记废弃)。

3.2 从文件读取与 toMap

CSVParser.parse 有五个重载,文件场景直接给 File 与字符集:

cd /opt/csvapp && cat > src/main/java/demo/CsvReadFile.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class CsvReadFile {
    public static void main(String[] args) throws IOException {
        File f = new File("people.csv");
        try (CSVParser parser = CSVParser.parse(f, StandardCharsets.UTF_8,
                CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get())) {
            System.out.println("header=" + parser.getHeaderNames());
            for (CSVRecord r : parser) {
                System.out.println("record#" + r.getRecordNumber() + " " + r.toMap());
            }
        }
    }
}
EOF
printf 'id,name\n101,Alice\n102,Bob\n' > people.csv
mvn -q package && java -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvReadFile

回显:

header=[id, name]
record#1 {id=101, name=Alice}
record#2 {id=102, name=Bob}

说明:CSVRecord.getRecordNumber() 从 1 开始计数据行;toMap() 把整行转成列名到值的 Map,适合字段较多的表。

四、写出:CSVPrinter

CSVPrinter 接收任意 Appendable(FileWriter/StringBuilder 等),按 CSVFormat 规则写出;printRecord(Object...) 对每个字段自动判断是否加引号。

cd /opt/csvapp && cat > src/main/java/demo/CsvWrite.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;

public class CsvWrite {
    public static void main(String[] args) throws IOException {
        try (CSVPrinter p = new CSVPrinter(new FileWriter("out.csv"),
                CSVFormat.DEFAULT.builder().setHeader("id", "name", "dept").get())) {
            p.printRecord(1, "Zhang San", "R&D");
            p.printRecord(2, "Li Si, Jr.", "Sales");
            p.printRecord(3, "Wang Wu", "Logistics");
        }
        System.out.println("written");
    }
}
EOF
mvn -q package && java -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvWrite && cat out.csv

回显:

written
id,name,dept
1,Zhang San,R&D
2,"Li Si, Jr.",Sales
3,Wang Wu,Logistics

💡 技术拓展:引号语义依据 RFC 4180——字段含分隔符/引号/换行时用双引号包裹,字段内双引号写成两个连续引号;各预设差异点如下。

方言行尾null 写出特殊字符处理
DEFAULT/RFC4180/EXCEL\r\n空串引号包裹 + "" 转义
MYSQL\n\N反斜杠转义
ORACLE\n\N引号包裹 + 反斜杠转义

说明:out.csv 中 "Li Si, Jr." 因含逗号被自动加引号,无需手工处理;写 MySQL 导入文件时换 CSVFormat.MYSQL 即可对齐 \N 与转义约定。

五、表头枚举映射

把列名固化成枚举,取值处拼写错误在编译期暴露,重构列名时 IDE 可全局改名:

cd /opt/csvapp && cat > src/main/java/demo/CsvEnum.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;

public class CsvEnum {
    enum User {
        NAME, EMAIL, AGE
    }
    public static void main(String[] args) throws IOException {
        String data = "Name,Email,Age\nAlice,alice@example.com,30\nBob,bob@example.com,25\n";
        CSVFormat fmt = CSVFormat.DEFAULT.builder()
                .setHeader(User.class)
                .setSkipHeaderRecord(true)
                .setIgnoreHeaderCase(true)
                .get();
        try (CSVParser parser = CSVParser.parse(data, fmt)) {
            for (CSVRecord r : parser) {
                System.out.println(r.get(User.NAME) + " <" + r.get(User.EMAIL) + "> age=" + r.get(User.AGE)
                        + " consistent=" + r.isConsistent());
            }
        }
    }
}
EOF
mvn -q package && java -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvEnum

回显:

Alice <alice@example.com> age=30 consistent=true
Bob <bob@example.com> age=25 consistent=true

说明:setHeader(User.class) 用枚举定义表头,文件首行 Name,Email,Age 被跳过;r.get(User.NAME) 按枚举取值;isConsistent() 验证每行列数与表头一致,数据质量检查一行搞定。

六、异常处理

Commons CSV 的受检异常体系围绕 IOException 展开,解析错误类型 CSVException 是其子类:

public class org.apache.commons.csv.CSVException extends java.io.IOException

(由 javap 实测确认。)两层要点:文件打开/读取失败抛 IOException;格式错误在 for-each 迭代时以 UncheckedIOException 包装出现。

6.1 方言不匹配的解析错误实测

cd /opt/csvapp && cat > src/main/java/demo/CsvError.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;

public class CsvError {
    public static void main(String[] args) {
        String data = "name,note\n#comment\n\"Alice, A\",\"He said \"\"hi\"\"\"\n";
        try (CSVParser p = CSVParser.parse(data, CSVFormat.TDF)) {
            for (CSVRecord r : p) {
                System.out.println(r);
            }
        } catch (UncheckedIOException e) {
            System.out.println("caught: " + e.getClass().getName());
            System.out.println("message: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("caught (IOException): " + e.getClass().getName());
        }
    }
}
EOF
mvn -q package && java -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvError

回显:

CSVRecord [comment='null', recordNumber=1, values=[name,note]]
CSVRecord [comment='null', recordNumber=2, values=[#comment]]
caught: java.io.UncheckedIOException
message: org.apache.commons.csv.CSVException: Invalid character between encapsulated token and delimiter at line: 3, position: 30

处理建议逐项说明:迭代中抛出的是 UncheckedIOException(RuntimeException 子类),只写 catch (IOException) 接不住它——这是本例最重要的实测教训;UncheckedIOException 的 cause 消息含精确行号与列位置,可用于日志定位;打开文件失败等场景仍抛 IOException,两个分支都写才完整;生产代码对单行错误做"跳过并记录"策略时可用 iterator() 手工推进。

七、流式处理大文件实测

生成 100 万行 CSV 并流式解析,验证内存形态与耗时。本测试为功能验证参考(单机单次),非生产性能基准。

7.1 生成 100 万行

cd /opt/csvapp && cat > src/main/java/demo/CsvBigGen.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;

public class CsvBigGen {
    public static void main(String[] args) throws IOException {
        try (CSVPrinter p = new CSVPrinter(new BufferedWriter(new FileWriter("big.csv"), 1 << 16),
                CSVFormat.DEFAULT.builder().setHeader("id", "name", "city", "amount").get())) {
            for (int i = 1; i <= 1_000_000; i++) {
                p.printRecord(i, "user-" + i, i % 2 == 0 ? "Beijing" : "Shanghai", i * 0.5);
            }
        }
        System.out.println("generated");
    }
}
EOF
mvn -q package && java -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvBigGen && ls -lh big.csv && wc -l big.csv

回显:

generated
-rw-r--r-- 1 root root 36M big.csv
1000001 big.csv

7.2 流式解析(-Xmx512m)

cd /opt/csvapp && cat > src/main/java/demo/CsvBigParse.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class CsvBigParse {
    public static void main(String[] args) throws IOException {
        long t0 = System.currentTimeMillis();
        long sum = 0;
        int count = 0;
        try (CSVParser parser = CSVParser.parse(new File("big.csv"), StandardCharsets.UTF_8,
                CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get())) {
            for (CSVRecord r : parser) {
                sum += Long.parseLong(r.get("id"));
                count++;
            }
        }
        long t1 = System.currentTimeMillis();
        Runtime rt = Runtime.getRuntime();
        System.out.println("rows=" + count + " sum=" + sum + " elapsed=" + (t1 - t0) + " ms");
        System.out.println("heapUsed=" + (rt.totalMemory() - rt.freeMemory()) / 1024 / 1024 + " MB, maxHeap=" + rt.maxMemory() / 1024 / 1024 + " MB");
    }
}
EOF
mvn -q package && java -Xmx512m -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvBigParse

回显:

rows=1000000 sum=500000500000 elapsed=614 ms
heapUsed=189 MB, maxHeap=512 MB

7.3 对照:一次性 getRecords 会怎样

cd /opt/csvapp && cat > src/main/java/demo/CsvBigAll.java <<'EOF'
package demo;

import org.apache.commons.csv.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class CsvBigAll {
    public static void main(String[] args) throws IOException {
        try (CSVParser parser = CSVParser.parse(new File("big.csv"), StandardCharsets.UTF_8,
                CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get())) {
            var all = parser.getRecords();
            System.out.println("loaded=" + all.size());
        }
    }
}
EOF
mvn -q package && java -Xmx256m -cp 'target/classes:/root/.m2/repository/org/apache/commons/commons-csv/1.14.1/commons-csv-1.14.1.jar:/root/.m2/repository/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar:/root/.m2/repository/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar' demo.CsvBigAll 2>&1 | head -3

回显:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.base/java.util.Arrays.copyOfRange(Arrays.java:3822)
        at java.base/java.lang.StringLatin1.newString(StringLatin1.java:769)

结论与实践清单:for-each 流式遍历 256MB 堆即可完成百万行(约 0.6 秒);getRecords() 全量驻留直接 OOM;示例统一给 256 至 512MB 堆即可,堆再大也不会更快;逐行处理时用 isConsistent() 过滤脏行,用 try-with-resources 保证流关闭。

八、参考链接

  1. Commons CSV 用户指南:https://commons.apache.org/proper/commons-csv/user-guide.html
  2. Commons CSV javadoc(1.14.1):https://commons.apache.org/proper/commons-csv/apidocs/index.html
  3. Maven 中央仓库 commons-csv:https://repo1.maven.org/maven2/org/apache/commons/commons-csv/
  4. Commons CSV 版本变更历史:https://commons.apache.org/proper/commons-csv/changes.html
  5. RFC 4180(CSV 格式规范):https://datatracker.ietf.org/doc/html/rfc4180