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

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.

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