1. 概述

处理包含逗号分隔值(CSV)的文本时,常需要忽略引号内的逗号。本文将探讨几种在分割CSV字符串时跳过引号内逗号的实现方案,包括自定义解析器、正则表达式和专用CSV库。

2. 问题陈述

假设需要分割以下CSV字符串:

String input = "baeldung,tutorial,splitting,text,\"ignoring this comma,\"";

期望的分割结果应为:

baeldung
tutorial
splitting
text
"ignoring this comma,"

关键点在于:不能简单地将所有逗号视为分隔符,必须忽略引号内的逗号。

3. 实现一个简单的解析器

下面是一个简单粗暴的解析算法:

List<String> tokens = new ArrayList<String>();
int startPosition = 0;
boolean isInQuotes = false;
for (int currentPosition = 0; currentPosition < input.length(); currentPosition++) {
    if (input.charAt(currentPosition) == '\"') {
        isInQuotes = !isInQuotes;
    }
    else if (input.charAt(currentPosition) == ',' && !isInQuotes) {
        tokens.add(input.substring(startPosition, currentPosition));
        startPosition = currentPosition + 1;
    }
}

String lastToken = input.substring(startPosition);
if (lastToken.equals(",")) {
    tokens.add("");
} else {
    tokens.add(lastToken);
}

核心逻辑解析:

  1. 初始化tokens列表存储分割结果
  2. 遍历字符串时维护isInQuotes标志:
    • 遇到双引号时切换标志状态
    • 仅当不在引号内且遇到逗号时才执行分割
  3. 循环结束后处理最后一个token:
    • 如果是单独逗号则添加空字符串
    • 否则添加剩余内容

测试代码:

String input = "baeldung,tutorial,splitting,text,\"ignoring this comma,\"";
var matcher = contains("baeldung", "tutorial", "splitting", "text", "\"ignoring this comma,\"");
assertThat(splitWithParser(input), matcher);

实际项目中应补充更多边界测试用例,比如连续引号、转义字符等场景。

4. 使用正则表达式

虽然自定义解析器效率高,但代码较复杂。正则表达式提供了更简洁的替代方案,但需注意性能问题——处理大数据量时可能成为瓶颈。

4.1. String的split()方法

利用String.split()配合正则表达式:

String[] tokens = input.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);

正则表达式解读:

  • 使用正向预查确保逗号分割条件
  • 仅当逗号后满足以下情况之一才分割:
    • 后续无双引号
    • 双引号成对出现(偶数个)
  • 限制参数-1表示保留所有空token

4.2. Guava的Splitter类

使用Guava库的Splitter类:

Pattern pattern = Pattern.compile(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
Splitter splitter = Splitter.on(pattern);
List<String> tokens = splitter.splitToList(input);

实现步骤:

  1. 编译与前述相同的正则表达式
  2. 创建基于该模式的Splitter实例
  3. 调用splitToList()直接获取结果列表

正则方案虽简洁,但可读性差且性能较低,需谨慎选择使用场景。

5. 使用CSV库

当输入格式复杂或需要健壮性时,推荐使用专业CSV库(如OpenCSV):

优势:

  • 无需手写复杂逻辑
  • 更好处理转义字符等边缘情况
  • 代码更易维护

Maven依赖:

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.8</version>
</dependency>

使用示例:

CSVParser parser = new CSVParserBuilder()
  .withSeparator(',')
  .build();

CSVReader reader = new CSVReaderBuilder(new StringReader(input))
  .withCSVParser(parser)
  .build();

List<String[]> lines = new ArrayList<>();
lines = reader.readAll();
reader.close();

关键点:

  1. 通过CSVParserBuilder指定逗号分隔符
  2. 使用CSVReaderBuilder创建读取器
  3. readAll()返回多行数据(每行对应一个String数组)
  4. 注意: OpenCSV会自动移除结果中的引号

处理真实CSV文件时,可直接传入FileReader替代StringReader

6. 总结

本文对比了三种处理引号内逗号的方案:

方案 优点 缺点
自定义解析器 性能高,控制灵活 代码复杂,需处理边缘情况
正则表达式 代码简洁 可读性差,性能较低
CSV库(如OpenCSV) 健壮性好,功能丰富 需引入外部依赖

选择建议:

  • 简单场景且性能敏感 → 自定义解析器
  • 快速实现且数据量小 → 正则表达式
  • 生产环境或复杂CSV → 专用CSV库

完整代码示例可在GitHub仓库获取。