在引号外的逗号上拆分
Posted
技术标签:
【中文标题】在引号外的逗号上拆分【英文标题】:Splitting on comma outside quotes 【发布时间】:2013-09-24 11:04:57 【问题描述】:我的程序从文件中读取一行。此行包含逗号分隔的文本,例如:
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"
中的逗号不是分隔符。如何处理?
【问题讨论】:
为什么有这个要求?您能否提供更多有关您要解决的问题的信息? 我不相信这个问题与前面提到的问题重复,因为这里双引号的字符串用逗号分隔;前面的问题没有这个要求。它希望(给出的示例)foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy"
被拆分为 foo
、bar
、c;qual="baz,blurb"
和 d;junk="quux,syzygy"
。这不是一个微不足道的区别,因为匹配@LAFKsaysReinstateMonica 的正则表达式"\"[^\"]*\"|[^,]+
在这里有效,但在那里无效。我已投票决定重新开放。
【参考方案1】:
你可以试试这个正则表达式:
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
);
【讨论】:
这个答案这么多年还是有价值的! 让我的程序与您的解释一起工作。谢谢!现在,有没有办法也可以为此添加换行符? \n 和 \r? 嗨,我的字符串是这样的:\“不要拆分,这个\”(它在“前面有那些反斜杠。如何修改正则表达式? 嗨 Rohit,我遵循了您的解决方案,其中我有两个分隔符,和/或。并使用以下正则表达式: (\s+and\s+|\s+or\s+)(?=(?:[^\"]*"[^\"]*\")*[^\"]*$ ). 它适用于大多数用例,但输入失败:'brand == "Kellogg\'\'s" 或 country == \'UnitedStates and "India\''。请你帮助我好吗?我对正则表达式很陌生。 这不适用于非平凡的情况,例如如果文本本身包含引号,则需要转义,例如\"
【参考方案2】:
既然可以匹配,为什么要拆分?
重新提出这个问题,因为出于某种原因,没有提到简单的解决方案。这是我们精美紧凑的正则表达式:
"[^"]*"|[^,]+
这将匹配所有需要的片段 (see demo)。
说明
和"[^"]*"
,我们匹配完整的"double-quoted strings"
或|
我们匹配[^,]+
任何不是逗号的字符。
一种可能的改进是改进交替的字符串端,以允许引用的字符串包含转义的引号。
【讨论】:
因为我喜欢这个而不是拆分,所以我将它与 Matcher 中的 Java 9 改进相结合,允许流式传输。我的答案包含演示它的 jshell 会话。 如果您还需要获取空字符串,此解决方案将不起作用,但我喜欢它。 @zx81 你知道如何使用转义引号\" 吗?【参考方案3】:你可以很容易地做到这一点,无需复杂的正则表达式:
-
拆分字符
"
。你得到一个字符串列表
处理列表中的每个字符串:将列表中偶数位置上的每个字符串(从零开始索引)拆分为“,”(您会在列表中获得一个列表),单独保留每个奇数位置的字符串(直接将其放入列表中的列表中)。
加入列表列表,因此您只得到一个列表。
如果你想处理 '"' 的引用,你必须稍微调整一下算法(加入一些部分,你有不正确的拆分,或者将拆分更改为简单的正则表达式),但基本结构保持不变。
所以基本上是这样的:
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);
承诺使用 lambda 会更简洁!
【讨论】:
【参考方案4】:基于 @zx81's 的回答,因为匹配的想法非常好,我添加了 Java 9 results
调用,它返回一个 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);
说明
-
正则表达式
[^"]
匹配:引号,除引号外的任何内容,引号。
正则表达式 [^"]*
匹配:一个引号,除了 0(或更多)次引号之外的任何内容,一个引号。
该正则表达式需要首先“获胜”,否则匹配除了逗号 1 次或更多次 - 即:[^,]+
- 将“获胜”。
results()
需要 Java 9 或更高版本。
它返回Stream<MatchResult>
,我使用group()
调用将其映射并收集到字符串数组。无参数的toArray()
调用将返回Object[]
。
【讨论】:
【参考方案5】:请看下面的代码sn-p。此代码仅考虑快乐流。根据您的要求更改
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);
【讨论】:
以上是关于在引号外的逗号上拆分的主要内容,如果未能解决你的问题,请参考以下文章