【问题标题】:regex, avoid extra zero-length matching正则表达式,避免额外的零长度匹配
【发布时间】:2013-12-02 23:37:12
【问题描述】:

假设我有以下正则表达式; (我正在使用 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

看第三个匹配,在我的想法中不应该出现。事实上,我的正则表达式要求每个匹配都以 (,|$) 结尾。因此,第二次匹配需要到达并“消耗”字符串的末尾,才有效:它不会将其留给进一步的匹配!
并且这似乎在第二场比赛之后被 hitEnd 为真证实了!。

但是 这似乎没有被 find 内部状态考虑,它搜索进一步的匹配,并且此时它显然找到了它,因为正则表达式允许在字符串结尾时进行零长度匹配因为它是有效匹配,因为每个字段都允许为空字符串(如果不是这种情况,使用 + 而不是 * 显然可以解决问题)。
我在问两件事。
1) 解决此问题
2) 似乎两次匹配到字符串结尾的原因

【问题讨论】:

  • 未经测试:尝试"\\G((?=.)[^,])*,?"(再次提醒我,\\G 是什么?)。
  • Per this part of Java tutorial,尝试“+?”匹配Reluctant

标签: java regex


【解决方案1】:

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

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

http://rubular.com/r/L5d8lZ44kh所示

出于演示目的,我为非分隔符匹配引入了一个捕获组,并为分隔符匹配使用了一个非捕获组。在您的情况下,这些更改都不是必需的;您只需要确保包含后向更改。

另外,正如@sin 在评论中指出的那样,没有必要匹配上一场比赛的结尾,所以我消除了 \G。如果您曾经将可接受的 CSV“值”字符限制为比逗号以外的所有字符更窄的集合,这将是不正确的。

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

除了需要有一种方法来避免匹配无限数量的零长度表达式之外,这可以正常工作。正如http://www.regular-expressions.info/zerolength.html 中所讨论的,至少有两种方法可以实现这一点。虽然这对您的问题并不重要,但我尝试通过谷歌搜索找出 Java 实现使用哪种方法,但没有。

【讨论】:

  • Java 是作者提到的“最风味”之一:对find() 的连续调用将永远不会返回两个从同一位置开始的匹配项。
  • 经过反复试验,我想出了这个((?:^|(?&lt;=,))[^,]*$|[^,]*,),然后将它们组合成你所拥有的。我认为这里不需要\G 锚,是吗?另外,Java 是否允许使用交替的lookbehinds?
  • 我有你最初拥有的东西,但后来我认为它会接受非标准 CSV 格式,因为没有任何要求下一场比赛在前一场比赛结束时开始(即它允许两个分隔符之间的“垃圾”)。但现在我想多了,你说得对,它什么也没做,因为所有文本要么是一个分隔符(即,),要么是可接受的 CSV 值。我会更新答案。谢谢。
【解决方案2】:

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

  1. 可以接受空字符串,只要后面有,
  2. 其他不接受空的non-comma 字符串(如果它放在末尾)

这似乎可以解决问题

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+"'");
}

此外,如果您想在标记中包含逗号,您可以使用后视机制在每个逗号之后拆分

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

【讨论】:

    【解决方案3】:
    Pattern p = Pattern.compile("[^,]+(?=\\s*|\\s*$)");
    

    查看演示here

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

        但要修复它,您可以在正则表达式的开头添加另一部分,以查找字符串或逗号的开头。

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


        但是您需要从比赛中删除多余的逗号,也许是捕获一个组而不是整个比赛?

        例如"(,|^)([^,]*)(,|$)" 然后用m.group(2) 得到它

        【讨论】:

        • 我认为你在正确的轨道上匹配前导逗号,但你还必须删除匹配 尾随逗号的部分。
        • @Alan 我明白你的意思,我错过了\G 的含义 - 虽然我认为更好的方法是仍然匹配两个逗号但删除\G。跨度>
        【解决方案6】:

        如果您不需要将值后面的, 作为匹配的一部分出现,您可以只匹配开头的(^|,) 而不是结尾的(,|$),这样可以消除您的问题:

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

        RegexHero shows 2 matches 而不是3 matches

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-04-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多