【问题标题】:Java PatternSyntaxException: Illegal repetition on string substitution?Java PatternSyntaxException:字符串替换的非法重复?
【发布时间】:2013-07-01 23:29:12
【问题描述】:

我正在尝试编写一个接受String 的方法,检查它是否有某些标记的实例(例如${fizz}${buzz}${foo} 等)并用新字符串替换每个标记从Map<String,String> 获取。

例如,如果我将以下字符串传递给此方法:

“现在 ${fizz} 怎么样了。${buzz} 的 ${foo} 形状很奇怪。”

如果方法咨询了以下Map<String,String>

Key             Value
==========================
"fizz"          "brown"
"buzz"          "arsonist"
"foo"           "feet"

那么结果字符串将是:

“现在怎么样了。纵火犯有奇怪的脚。”

这是我的方法:

String substituteAllTokens(Map<String,String> tokensMap, String toInspect) {
    String regex = "\\$\\{([^}]*)\\}";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(toInspect);
    while(matcher.find()) {
        String token = matcher.group();     // Ex: ${fizz}
        String tokenKey = matcher.group(1); // Ex: fizz
        String replacementValue = null;

        if(tokensMap.containsKey(tokenKey))
            replacementValue = tokensMap.get(tokenKey);
        else
            throw new RuntimeException("String contained an unsupported token.");

        toInspect = toInspect.replaceFirst(token, replacementValue);
    }

    return toInspect;
}

当我运行它时,我得到以下异常:

Exception in thread "main" java.util.regex.PatternSyntaxException: Illegal repetition near index 0
${fizz}
^
    at java.util.regex.Pattern.error(Pattern.java:1730)
    at java.util.regex.Pattern.closure(Pattern.java:2792)
    at java.util.regex.Pattern.sequence(Pattern.java:1906)
    at java.util.regex.Pattern.expr(Pattern.java:1769)
    at java.util.regex.Pattern.compile(Pattern.java:1477)
    at java.util.regex.Pattern.<init>(Pattern.java:1150)
    at java.util.regex.Pattern.compile(Pattern.java:840)
    at java.lang.String.replaceFirst(String.java:2158)
    ...rest of stack trace omitted for brevity (but available upon request!)

我为什么会收到这个?正确的解决方法是什么?提前致谢!

【问题讨论】:

    标签: java regex string exception


    【解决方案1】:

    ${fizz}

    { 是正则表达式引擎的指示符,表明您即将启动重复指示符,例如 {2,4} 表示“前一个令牌的 2 到 4 倍”。但是{f是非法的,因为后面必须跟数字,所以会抛出异常。

    您需要转义所有正则表达式元字符(在本例中为 ${})(尝试使用 http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html#quote(java.lang.String))或使用其他方法将字符串替换为字符串,而不是使用正则表达式替换一个字符串。

    【讨论】:

    • { 被转义了:"\\$\\{([^}]*)\\}"
    • @Brian 不,不是。堆栈跟踪:at java.lang.String.replaceFirst(String.java:2158) 指的是这一行:toInspect = toInspect.replaceFirst(token, replacementValue);token 的值为${fizz},没有转义。
    • 啊,是的!当然。读得太快了,我猜。 +1 也许您可以详细说明传递的确切内容以及失败的原因。
    • 完全正确 - 我仍在努力将您的答案付诸实施,但感谢@Patashu 和 +1!
    • @TicketMonster replaceFirst 在第一个参数中需要一个正则表达式,就像Pattern.compile 方法一样。但是,您将它传递给${fizz},而没有任何转义。在传递token 之前使用Pattern.quote(token)
    【解决方案2】:

    改编自Matcher.replaceAll

    boolean result = matcher.find();
    if (result) {
        StringBuffer sb = new StringBuffer();
        do {
            String tokenKey = matcher.group(1); // Ex: fizz
            String replacement = Matcher.quoteReplacement(tokensMap.get(tokenKey));
            matcher.appendReplacement(sb, replacement);
            result = matcher.find();
        } while (result);
        matcher.appendTail(sb);
        return sb.toString();
    }
    

    【讨论】:

    • 使用此方法在此处添加我的测试结果:原始正则表达式 ([^}]*) 运行 10000 次需要 15.14 毫秒,我的正则表达式 ([^}]+) 需要 14.88 毫秒,@Brian 建议的正则表达式 ( [^}]++) 15.89 毫秒。正如我之前所说,这种方法在速度方面绝对是赢家,在这里我的正则表达式更适合:)
    • @Miguel 尝试使用很长的toInspect 作为输入,例如"${fizz}${buzz}${foo}${fizz}${buzz}${foo}${fizz}${buzz}${foo}...${fizz}${buzz}${foo}${fizz}${buzz}${foo}${fizz}${buzz}${foo}${fizz}${buzz}${foo}"(我打赌我的StringBuffer 会比你的String 快,当长度增加时,它会赢不仅仅是快 3 倍。)
    • 使用"How now ${fizz} cow. The ${buzz} had oddly-shaped ${foo}.How now ${fizz} cow. The ${buzz} had oddly-shaped ${foo}.${fizz}${foo}${buzz}${buzz}${foo}${fizz}${foo}${fizz}${buzz}${fizz}${foo}${buzz}${buzz}${foo}${fizz}${foo}${fizz}${buzz}${fizz}${foo}${buzz}${buzz}${foo}${fizz}${foo}${fizz}${buzz}" 作为输入,这个方法的性能甚至比我的还要好,快了 5 倍。正则表达式的结果不同。原始正则表达式 ([^}]*) 运行 10000 次需要 92.94 毫秒,我的正则表达式 ([^}]+) 需要 93.6 毫秒,@Brian ([^}]++) 建议的正则表达式需要 91.83 毫秒。布赖恩做得更好。为什么会这样?
    • @Miguel 我只知道 3x->5x: 1. replaceFirst 再次使用正则表达式,这是不必要的。 2. 在一个循环中,StringBuffer 连接String 比直接在String 中更快,因为String 每次都会生成一个新的String。使用哪个正则表达式并不重要。
    【解决方案3】:

    正如 Patashu 所指出的,问题出在replaceFirst(token, replacementValue) 中,它需要在第一个参数中使用正则表达式,而不是文字。把它改成replaceFirst(Pattern.quote(token), replacementValue)就行了。

    我还对第一个正则表达式进行了一些更改,因为使用 + 而不是 * 会更快,但这不是必需的。

    static String substituteAllTokens(Map<String,String> tokensMap, String toInspect) {
        String regex = "\\$\\{([^}]+)\\}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(toInspect);
        String result = toInspect;
        while(matcher.find()) {
            String token = matcher.group();     // Ex: ${fizz}
            String tokenKey = matcher.group(1); // Ex: fizz
            String replacementValue = null;
    
            if(tokensMap.containsKey(tokenKey))
                replacementValue = tokensMap.get(tokenKey);
            else
                throw new RuntimeException("String contained an unsupported token.");
    
            result = result.replaceFirst(Pattern.quote(token), replacementValue);
        }
    
        return result;
    }
    

    【讨论】:

    • 如果您想真正对速度挑剔,请使用[^}]++ 使其具有占有性而不是贪婪,这样它就永远不会退缩。
    • 速度结果令人惊讶:使用我的方法,原始正则表达式 ([^}]*) 需要 37.04 毫秒才能运行 10000 次,我的正则表达式 ([^}]+) 需要 37.35 毫秒并建议正则表达式 (@987654330 @) 36.98 毫秒。几乎没有区别。
    • 对于更长的输入"How now ${fizz} cow. The ${buzz} had oddly-shaped ${foo}.How now ${fizz} cow. The ${buzz} had oddly-shaped ${foo}.${fizz}${foo}${buzz}${buzz}${foo}${fizz}${foo}${fizz}${buzz}${fizz}${foo}${buzz}${buzz}${foo}${fizz}${foo}${fizz}${buzz}${fizz}${foo}${buzz}${buzz}${foo}${fizz}${foo}${fizz}${buzz}",正则表达式的结果不同。原始正则表达式 ([^}]*) 运行 10000 次耗时 515.47 毫秒,我的正则表达式 ([^}]+) 耗时 514.41 毫秒,@Brian ([^}]++) 建议的正则表达式耗时 507.41 毫秒。仍然接近,但布赖恩的表现更好。
    • 您是否使用了实际的基准测试工具来进行检查,例如Caliper?此外,您还应确保使用部分匹配的字符串进行测试,例如 The ${fizz cow 没有终止 }。另外,公平地说,我说真的很挑剔:3
    • 不,我自己编写了基准测试。在开始时进行了一个热身循环以激活热点并在测量之外多次调用System.gc()。但是,是的,我知道它不是防弹的。不过,我不知道 Caliper。很高兴知道!
    【解决方案4】:

    使用 String-replaceAll。 用于测试的示例输入字符串 “SESSIONKEY1”:

    “${SOMESTRING.properties.SESSIONKEY1}”

    ,

        String pattern = "\\\"\\$\\{SOMESTRING\\.[^\\}]+\\}\\\""; 
        System.out.println(pattern);
        String result = inputString.replaceAll(pattern, "null");
        return result.toString();
    

    【讨论】:

      【解决方案5】:

      你可以让你的 RegEx 有点难看,但是 这会工作

      String regex = "\\$[\\{]([^}]*)[\\}]";
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多