逗号外引号分裂

我的程序从一个文件中读取一行。这行包含逗号分隔的文本,如:

123,test,444,"don't split, this",more test,1

我希望分手的结果是这样的:

123
test
444
"don't split, this"
more test
1

如果我使用 String.split(","),我会得到这个:

123
test
444
"don't split
this"
more test
1

换句话说: 子字符串 "don't split, this"中的逗号不是分隔符。如何处理这个问题?

63589 次浏览

你可以试试这个正则表达式:

str.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

这将分割 ,上的字符串,该字符串后跟偶数个双引号。换句话说,它在双引号之外的逗号上分开。如果在字符串中有平衡引号,那么这个方法就可以工作。

说明:

,           // Split on comma
(?=         // Followed by
(?:      // Start a non-capture group
[^"]*  // 0 or more non-quote characters
"      // 1 quote
[^"]*  // 0 or more non-quote characters
"      // 1 quote
)*       // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
[^"]*    // Finally 0 or more non-quotes
$        // Till the end  (This is necessary, else every comma will satisfy the condition)
)

您甚至可以在代码中使用 (?x)修饰符和正则表达式类似地输入。修饰符忽略正则表达式中的任何空格,因此更容易读取分成多行的正则表达式,如下所示:

String[] arr = str.split("(?x)   " +
",          " +   // Split on comma
"(?=        " +   // Followed by
"  (?:      " +   // Start a non-capture group
"    [^\"]* " +   // 0 or more non-quote characters
"    \"     " +   // 1 quote
"    [^\"]* " +   // 0 or more non-quote characters
"    \"     " +   // 1 quote
"  )*       " +   // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
"  [^\"]*   " +   // Finally 0 or more non-quotes
"  $        " +   // Till the end  (This is necessary, else every comma will satisfy the condition)
")          "     // End look-ahead
);

不需要复杂的正则表达式就可以很容易地做到这一点:

  1. 分割字符 ",得到一个字符串列表
  2. 处理列表中的每个字符串: 将列表中偶数位置上的每个字符串拆分(从零开始索引)到“ ,”(你在列表中得到一个列表) ,留下每个奇数位置的字符串(直接放在列表中的列表中)。
  3. 加入列表的列表,所以您只能得到一个列表。

如果您想处理’”’的引用,则必须稍微调整算法(连接某些部分,您错误地拆分了它,或者将拆分更改为简单的 regexp) ,但是基本结构仍然保留。

所以基本上是这样的:

public class SplitTest {
public static void main(String[] args) {
final String splitMe="123,test,444,\"don't split, this\",more test,1";
final String[] splitByQuote=splitMe.split("\"");
final String[][] splitByComma=new String[splitByQuote.length][];
for(int i=0;i<splitByQuote.length;i++) {
String part=splitByQuote[i];
if (i % 2 == 0){
splitByComma[i]=part.split(",");
}else{
splitByComma[i]=new String[1];
splitByComma[i][0]=part;
}
}
for (String parts[] : splitByComma) {
for (String part : parts) {
System.out.println(part);
}
}
}
}

这将是更干净的 Lambdas,承诺!

请参阅下面的代码片段。此代码只考虑快乐流。根据您的要求更改

public static String[] splitWithEscape(final String str, char split,
char escapeCharacter) {
final List<String> list = new LinkedList<String>();


char[] cArr = str.toCharArray();


boolean isEscape = false;
StringBuilder sb = new StringBuilder();


for (char c : cArr) {
if (isEscape && c != escapeCharacter) {
sb.append(c);
} else if (c != split && c != escapeCharacter) {
sb.append(c);
} else if (c == escapeCharacter) {
if (!isEscape) {
isEscape = true;
if (sb.length() > 0) {
list.add(sb.toString());
sb = new StringBuilder();
}
} else {
isEscape = false;
}


} else if (c == split) {
list.add(sb.toString());
sb = new StringBuilder();
}
}


if (sb.length() > 0) {
list.add(sb.toString());
}


String[] strArr = new String[list.size()];


return list.toArray(strArr);
}

如果你能匹配,为什么要分开?

由于某些原因,我们没有提到这个简单的解决方案:

"[^"]*"|[^,]+

这将匹配所有需要的片段(看小样)。

解释

  • "[^"]*",我们匹配完整的 "double-quoted strings"
  • |
  • 我们匹配 [^,]+任何不是逗号的字符。

一个可能的改进是改进交替的字符串侧,以允许引号字符串包含转义引号。

@ zx81’s应答的基础上,因为匹配的想法非常好,我添加了 Java9results调用,它返回一个 Stream。因为 OP 想要使用 split,所以我收集到了 String[],就像 split一样。

注意,如果逗号分隔符(a, b, "c,d")后面有空格,那么需要改变模式。

Jshell 演示

$ jshell
-> String so = "123,test,444,\"don't split, this\",more test,1";
|  Added variable so of type String with initial value "123,test,444,"don't split, this",more test,1"


-> Pattern.compile("\"[^\"]*\"|[^,]+").matcher(so).results();
|  Expression value is: java.util.stream.ReferencePipeline$Head@2038ae61
|    assigned to temporary variable $68 of type java.util.stream.Stream<MatchResult>


-> $68.map(MatchResult::group).toArray(String[]::new);
|  Expression value is: [Ljava.lang.String;@6b09bb57
|    assigned to temporary variable $69 of type String[]


-> Arrays.stream($69).forEach(System.out::println);
123
test
444
"don't split, this"
more test
1

密码

String so = "123,test,444,\"don't split, this\",more test,1";
Pattern.compile("\"[^\"]*\"|[^,]+")
.matcher(so)
.results()
.map(MatchResult::group)
.toArray(String[]::new);

解释

  1. 正则表达式 [^"]匹配: 一个引号,除了引号之外的任何东西,一个引号。
  2. 正则表达式 [^"]*匹配: 一个报价,除了报价0(或更多)倍以外的任何东西,一个报价。
  3. 该正则表达式需要先去“赢”,否则匹配 除了逗号以外的任何东西,一次或更多次-即: [^,]+-将“赢”。
  4. results()需要 Java9或更高版本。
  5. 它返回 Stream<MatchResult>,我使用 group()调用将它映射并收集到 String 数组。无参数 toArray()调用将返回 Object[]