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);
}
核心逻辑解析:
- 初始化
tokens
列表存储分割结果 - 遍历字符串时维护
isInQuotes
标志:- 遇到双引号时切换标志状态
- 仅当不在引号内且遇到逗号时才执行分割
- 循环结束后处理最后一个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);
实现步骤:
- 编译与前述相同的正则表达式
- 创建基于该模式的
Splitter
实例 - 调用
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();
关键点:
- 通过
CSVParserBuilder
指定逗号分隔符 - 使用
CSVReaderBuilder
创建读取器 readAll()
返回多行数据(每行对应一个String数组)- 注意: OpenCSV会自动移除结果中的引号
处理真实CSV文件时,可直接传入
FileReader
替代StringReader
。
6. 总结
本文对比了三种处理引号内逗号的方案:
方案 | 优点 | 缺点 |
---|---|---|
自定义解析器 | 性能高,控制灵活 | 代码复杂,需处理边缘情况 |
正则表达式 | 代码简洁 | 可读性差,性能较低 |
CSV库(如OpenCSV) | 健壮性好,功能丰富 | 需引入外部依赖 |
选择建议:
- 简单场景且性能敏感 → 自定义解析器
- 快速实现且数据量小 → 正则表达式
- 生产环境或复杂CSV → 专用CSV库
完整代码示例可在GitHub仓库获取。