正则表达式,避免额外的零长度匹配

时间:2013-12-01 23:09:33

标签: java regex

说我有以下正则表达式; (我正在使用java.util.regex包.java版本1.7.0_21)

Pattern p = Pattern.compile("\\G[^,]*(,|$)");

重复调用find(),我应该能够提取CSV中的字段,如下所示:

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

看看第3次匹配,在我的想法中不应该出现。的确,我的正则表达式要求每个匹配以(,| $)结尾。因此,第二个匹配,需要达到并“消耗”字符串的结尾才有效:它不会让它进一步匹配!
在第二场比赛之后,这似乎被hitEnd确认了! -
但是这似乎不被find内部状态所考虑,它会搜索进一步的匹配,并且此时它显然会找到它,因为正则表达式允许零长度匹配,然后结束字符串因为它是一个有效的匹配,因为每个字段都允许是一个空字符串(如果不是这种情况,使用+代替*显然会解决问题)。
我问两件事 1)解决这个问题的方法 2)它似乎与字符串的末尾匹配两次的原因

6 个答案:

答案 0 :(得分:1)

您的第一个问题有几个可能的答案。一种是使用lookbehind来确保你总是在行开头或逗号后开始匹配,如:

(?<=^|,)([^,]*)(?:,|$)

http://rubular.com/r/L5d8lZ44kh

所示

出于演示目的,我为非分隔符匹配引入了捕获组,并使用非捕获组进行分隔符匹配。在您的情况下,这些变化都不是必需的;你只需要确保包括后观变化。

另外,正如@sin在评论中指出的那样,没有必要匹配上一场比赛的结束,所以我淘汰了\ G.如果您将可接受的CSV“值”字符限制为比逗号以外的所有字符更窄的集合,则情况并非如此。

第二个问题的答案有点棘手。首先,重要的是要理解锚点位置(例如,行的开头,最后一个匹配的结束,行尾等)永远不会被正则表达式捕获;他们只是匹配。比赛位置保持不变。因此,多个连续的表达式可以匹配相同的锚点,就像您所经历的那样。

除了需要有一种方法可以避免匹配无数个零长度表达式之外,这个工作正常。至少有几种方法可以实现这一点,如http://www.regular-expressions.info/zerolength.html中所述。虽然这对你的问题并不是非常关键,但我尝试通过谷歌搜索来确定Java实现使用哪种方法,但不能。

答案 1 :(得分:0)

我不确定原因,也许它会从每个可能的起点(即b以及字符串的末尾)寻找匹配?

要解决此问题,您可以在正则表达式的开头添加另一个部分,以查找字符串的开头或逗号。

类似于:"(,|^)[^,]*(,|$)"


但是你需要从比赛中删除额外的逗号,或许是在捕捉一个小组而不是整场比赛?

E.g。 "(,|^)([^,]*)(,|$)",然后使用m.group(2)

获取

答案 2 :(得分:0)

似乎简单的解决方案是将正则表达式分为两部分

  1. 只要在,之后就可以接受空字符串。
  2. 其他不接受空non-comma字符串的其他字符串
  3. 这似乎可以解决问题

    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。如果你想要最后一个空字符串,你可以使用分割与负限制,如

    for(String token:"a,,b,".split(",",-1)){
        System.out.println("'"+token+"'");
    }
    

    此外,如果你想在逗号中加入逗号,你可以使用look-behind机制在每个逗号后分割

    for(String token:"a,,b,".split("(?<=,)",-1)){
        System.out.println("'"+token+"'");
    }
    

答案 3 :(得分:0)

Pattern p = Pattern.compile("[^,]+(?=\\s*|\\s*$)");

参见演示 here

答案 4 :(得分:0)

可能是正则表达式解决方案 -

 #  "(?:^|(?<=,))([^,]*)(?:,|$)"

 (?:
      ^ 
   |  (?<= , )
 )
 ( [^,]* )                          # (1)
 (?: , | $ )

答案 5 :(得分:0)

如果您不想在值后显示,作为匹配项的一部分,则您可以在开头时匹配(^|,),而不是最后匹配(,|$),这将消除你的问题:

\G(^|,)[^,]*

RegexHero shows 2 matches代替3 matches

如果您在同一个字符串中使用多行,则将行分隔符添加到否定的类中。