需要新鲜的眼睛来看Java正则表达式,这太贪婪了

时间:2011-07-19 01:32:49

标签: java regex regex-greedy

我有一个表格的字符串:

canonical_class_name[key1="value1",key2="value2",key3="value3",...] 

目的是捕获组中的canonical_class_name,然后交替使用key = value组。目前它与测试字符串不匹配(在以下程序中testString)。

必须至少有一个键/值对,但可能有很多这样的对。

问题:目前正则表达式正在抓取规范类名,第一个键正确然后它吞噬了所有内容,直到最后一个双引号,如何让它抓住键值对懒惰?

这是以下程序汇总的正则表达式:

(\S+)\[\s*(\S+)\s*=\s*"(.*)"\s*(?:\s*,\s*(\S+)\s*=\s*"(.*)"\s*)*\]

根据您的偏好,您可能会发现程序版本更易于阅读。

如果我的程序传递了String:

org.myobject[key1=\"value1\", key2=\"value2\", key3=\"value3\"]

......这些是我得到的小组:

Group1 contains: org.myobject<br/>
Group2 contains: key1<br/>
Group3 contains: value1", key2="value2", key3="value3<br/>

还有一点需要注意,使用String.split()我可以简化表达式,但我正在使用它作为学习经验来更好地理解我的正则表达式,所以我不想使用这样的捷径。

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BasicORMParser {
     String regex =
            "canonicalName\\[ map (?: , map )*\\]"
            .replace("canonicalName", "(\\S+)")
            .replace("map", "key = \"value\"")
            .replace("key", "(\\S+)")
            .replace("value", "(.*)")
            .replace(" ", "\\s*"); 

    List<String> getGroups(String ormString){
        List<String> values = new ArrayList();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(ormString);
        if (matcher.matches() == false){
            String msg = String.format("String failed regex validiation. Required: %s , found: %s", regex, ormString);
            throw new RuntimeException(msg);
        }
        if(matcher.groupCount() < 2){
            String msg = String.format("Did not find Class and at least one key value.");
            throw new RuntimeException(msg);
        }
        for(int i = 1; i < matcher.groupCount(); i++){
            values.add(matcher.group(i));
        }
        return values;
    }
}

2 个答案:

答案 0 :(得分:4)

你几乎自己回答了这个问题:让他们懒惰。也就是说,使用懒惰(a.k.a。非贪婪不情愿)量词。只需将每个(\S+)更改为(\S+?),将每个(.*)更改为(.*?)。但如果是我,我会改变那些子表达,这样他们就永远无法匹配,无论贪婪如何。例如,您可以使用([^\s\[]+)作为类名,([^\s=]+)作为键,"([^"]*)"作为值。

但是,我不认为这会解决你的真正问题。一旦你得到它所以它正确匹配所有的键/值对,你会发现只有捕获第一对(组#2和#3)和最后一对(组# 4和#5)。这是因为,每次重复(?:\s*,\s*(\S+)\s*=\s*"(.*)"\s*)*时,这两个组都会覆盖其内容,并且在前一次迭代中捕获的内容都会丢失。没有绕过它,这至少是两步操作。例如,您可以将所有键/值对匹配为块,然后分解各个对。

还有一件事。这一行:

if(matcher.groupCount() < 2){

......可能没有按照你的想法去做。 groupCount()是Pattern对象的静态属性;它告诉正则表达式中有多少个捕获组。无论匹配成功还是失败,groupCount()将始终返回相同的值 - 在本例中为5。如果匹配成功,则某些捕获组可能为空(表示它们没有参与匹配),但总会有五个。


编辑:我怀疑这是你最初的尝试:

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

String s = "org.myobject[key1=\"value1\", key2=\"value2\", key3=\"value3\"]";
Matcher m = p.matcher(s);
while (m.find())
{
  if (m.group(1) != null)
  {
    System.out.printf("class : %s%n", m.group(1));
  }
  System.out.printf("key : %s, value : %s%n", m.group(2), m.group(3));
}

输出:

class : org.myobject
key : key1, value : value1
key : key2, value : value2
key : key3, value : value3

理解正则表达式的关键是这一部分:(?:([^\s\[]+)\[|\G)。在第一遍中,它匹配类名和开始方括号。之后,\G接管,将下一场比赛锚定到上一场比赛结束的位置。

答案 1 :(得分:2)

对于非贪婪匹配,请在模式后附加?。例如,.*?匹配尽可能少的字符数。