Java 正则表达式,避免额外的零长度匹配
假设我有以下正则表达式;(我正在使用java.util.regex package.java版本1.7.0_21) 反复调用find(),我应该能够提取CSV中的字段,如下所示:Java 正则表达式,避免额外的零长度匹配,java,regex,Java,Regex,假设我有以下正则表达式;(我正在使用java.util.regex package.java版本1.7.0_21) 反复调用find(),我应该能够提取CSV中的字段,如下所示: String myCSV = "a,b"; 让我们用最简单的循环来尝试。只需回显每个匹配的信息 Matcher m = p.matcher(myCSV); while (m.find()) { System.out.println("Match found from: " + m.
String myCSV = "a,b";
让我们用最简单的循环来尝试。只需回显每个匹配的信息
Matcher m = p.matcher(myCSV);
while (m.find()) {
System.out.println("Match found from: " + m.start()
+ " (included) to: " + m.end()+ " (excluded),"
+ " matching: '" + m.group() + "'. Does it hit end?" + m.hitEnd());
}
虽然我试图构造我的正则表达式,使其不允许零长度匹配,但令人惊讶的是,它确实做到了:
Match found from: 0 (included) to: 2 (excluded), matching: 'a,'. Does it hit end?false
Match found from: 2 (included) to: 3 (excluded), matching: 'b'. Does it hit end?true
Match found from: 3 (included) to: 3 (excluded), matching: ''. Does it hit end?true
看看第三个匹配,在我看来不应该出现。实际上,我的正则表达式要求每个匹配都以(,|$)结尾。因此,第二个匹配需要到达并“使用”字符串的结尾,才能有效:它不会将其留给进一步的匹配在第二场比赛刚结束的时候,这一点似乎就被证明是正确的
但是查找内部状态似乎没有考虑到这一点,该状态搜索进一步的匹配,在这一点上它显然找到了它,因为正则表达式允许零长度匹配,当后跟字符串结尾时,因为它是有效匹配,因为每个字段都允许为空字符串(如果不是这样,用+代替*显然可以解决问题)。
我要问两件事。
1) 此问题的修复方法
2) 它似乎两次匹配字符串结尾的原因我不确定原因,也许它从每个可能的起点(即从
b
和字符串结尾)查找匹配
但要解决这个问题,可以在正则表达式的开头添加另一部分,查找字符串的开头或逗号
比如:“(,| ^)[^,]*(,|$)”
但是你需要从比赛中去掉多余的逗号,也许是捕获一组而不是整个比赛
例如,
“(,|^)([^,]*)(,|$)”
然后使用m.group(2)
似乎简单的解决方案是将正则表达式分成两部分
,
,它就可以接受空字符串非逗号字符串的其他字符串
Pattern p = Pattern.compile("\\G[^,]*,|\\G[^,]+$");
String myCSV = "a,,b";
Matcher m = p.matcher(myCSV);
while (m.find()) {
System.out.println("Match found from: " + m.start()
+ " (included) to: " + m.end() + " (excluded),"
+ " matching: '" + m.group() + "'. Does it hit end?"
+ m.hitEnd());
}
输出:
Match found from: 0 (included) to: 2 (excluded), matching: 'a,'. Does it hit end?false
Match found from: 2 (included) to: 3 (excluded), matching: ','. Does it hit end?false
Match found from: 3 (included) to: 4 (excluded), matching: 'b'. Does it hit end?true
另一种更简单的方法是在每个逗号上使用
split
。如果您想同时拥有最后一个空字符串,您可以使用带负数限制的split,如
for(String token:"a,,b,".split(",",-1)){
System.out.println("'"+token+"'");
}
另外,如果您想在令牌中包含逗号,可以使用查找机制在每个逗号后拆分
for(String token:"a,,b,".split("(?<=,)",-1)){
System.out.println("'"+token+"'");
}
对于(字符串标记:“a,,b,”.split((?您的第一个问题有几个可能的答案。一个是使用lookback来确保始终在行首或逗号之后开始匹配,如:
(?<=^|,)([^,]*)(?:,|$)
(?
请参见演示可能是正则表达式解决方案-
# "(?:^|(?<=,))([^,]*)(?:,|$)"
(?:
^
| (?<= , )
)
( [^,]* ) # (1)
(?: , | $ )
#“(?:^ |)(?如果不需要将值作为匹配的一部分显示在,
之后,则可以在开始时匹配到(^ |,)
,而不是在结束时匹配到(,|$)
,这将消除您的问题:
\G(^ |,)[^,]*
而不是
如果您正在处理同一字符串中的多行,请将行分隔符添加到被求反的类中。未经测试:尝试“\\G((?=)[^,])*,?”
(再次提醒我,\\G
用于什么?),尝试+?
”用于不情愿的匹配。Java是“最有味道”的语言之一作者暗示:连续调用find()
将永远不会返回两个从同一位置开始的匹配项。通过反复试验,我得出了这个(?:^|(?我拥有您最初拥有的内容,但后来我认为它可以接受非标准CSV格式,而无需在上一个匹配的末尾开始下一个匹配(即允许两个分隔符之间出现“垃圾”).但现在我再仔细考虑一下,你是对的,它没有做任何事情,因为所有文本都是一个分隔符(即,
)或者它是一个可接受的CSV值。我将更新答案。谢谢。我认为您匹配前导逗号的方法是正确的,但是您还必须删除匹配尾随逗号的部分。@Alan我明白您的意思,我没有理解\G
-尽管我认为更好的方法是仍然匹配两个逗号,但删除e\G
。
Pattern p = Pattern.compile("[^,]+(?=\\s*|\\s*$)");
# "(?:^|(?<=,))([^,]*)(?:,|$)"
(?:
^
| (?<= , )
)
( [^,]* ) # (1)
(?: , | $ )