什么(?!a){0}?在 Java 正则表达式中的意思是?

2022-09-01 13:26:40

受到{0}量词是否真的有意义的问题的启发,我开始玩一些包含量词的正则表达式,并编写了这个小java程序,该程序仅根据各种测试正则表达式拆分一个测试短语:{0}

private static final String TEST_STR =
    "Just a test-phrase!! 1.2.3.. @ {(t·e·s·t)}";

private static void test(final String pattern) {
    System.out.format("%-17s", "\"" + pattern + "\":");
    System.out.println(Arrays.toString(TEST_STR.split(pattern)));
}

public static void main(String[] args) { 
    test("");
    test("{0}");
    test(".{0}");
    test("([^.]{0})?+");
    test("(?!a){0}");
    test("(?!a).{0}");
    test("(?!.{0}).{0}");
    test(".{0}(?<!a)");
    test(".{0}(?<!.{0})");
} 

==> 输出:

"":              [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"{0}":           [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
".{0}":          [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"([^.]{0})?+":   [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"(?!a){0}":      [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"(?!a).{0}":     [, J, u, s, t,  a,  , t, e, s, t, -, p, h, ra, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"(?!.{0}).{0}":  [Just a test-phrase!! 1.2.3.. @ {(t·e·s·t)}]
".{0}(?<!a)":    [, J, u, s, t,  , a , t, e, s, t, -, p, h, r, as, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
".{0}(?<!.{0})": [Just a test-phrase!! 1.2.3.. @ {(t·e·s·t)}]

以下内容并没有让我感到惊讶

  1. "",,并在每个字符之前拆分,这是有道理的,因为0量词。".{0}""([^.]{0})?+"
  2. "(?!.{0}).{0}"并且不匹配任何东西。对我来说是有道理的:负前观/0量化令牌的查找后缀不匹配。".{0}(?<!.{0})"

让我感到惊讶的是:

  1. "{0}" & "(?!a){0}":我实际上期望在这里有一个例外,因为前面的令牌是不可量化的:因为前面根本没有任何东西,而且不仅仅是一个负面的展望。两者都在每个字符之前匹配,为什么?如果我在javascript验证器中尝试该正则表达式,我得到“不可量化的错误”,请参阅此处的演示!在 Java 和 Javascript 中,正则表达式的处理方式是否不同?{0}(?!a){0}
  2. "(?!a).{0}" & ".{0}(?<!a)":这里还有一点惊喜:在短语的每个字符之前匹配,除了之前/之后。我的理解是,在负前方部分断言不可能与字面匹配,但我正在展望未来 。我认为它不适用于0量化令牌,但看起来我也可以使用Lookahead。a(?!a).{0}(?!a)a.{0}

==> 所以对我来说剩下的谜团是为什么在我的测试短语中的每个字符之前实际上匹配。这难道不应该是一个无效的模式,并抛出一个PatternSyntaxException或类似的东西吗?(?!a){0}


更新:

如果我在Android Activity中运行相同的Java代码,结果会有所不同!在那里,正则表达式确实抛出了一个PatternSyntaxException,请参阅:(?!a){0}

03-20 22:43:31.941: D/AndroidRuntime(2799): Shutting down VM
03-20 22:43:31.950: E/AndroidRuntime(2799): FATAL EXCEPTION: main
03-20 22:43:31.950: E/AndroidRuntime(2799): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.appham.courseraapp1/com.appham.courseraapp1.MainActivity}: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 6:
03-20 22:43:31.950: E/AndroidRuntime(2799): (?!a){0}
03-20 22:43:31.950: E/AndroidRuntime(2799):       ^
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.access$600(ActivityThread.java:141)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1234)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.os.Handler.dispatchMessage(Handler.java:99)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.os.Looper.loop(Looper.java:137)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.main(ActivityThread.java:5041)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.reflect.Method.invokeNative(Native Method)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.reflect.Method.invoke(Method.java:511)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at dalvik.system.NativeStart.main(Native Method)
03-20 22:43:31.950: E/AndroidRuntime(2799): Caused by: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 6:
03-20 22:43:31.950: E/AndroidRuntime(2799): (?!a){0}
03-20 22:43:31.950: E/AndroidRuntime(2799):       ^
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.compileImpl(Native Method)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.compile(Pattern.java:407)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.<init>(Pattern.java:390)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.compile(Pattern.java:381)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.String.split(String.java:1832)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.String.split(String.java:1813)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at com.appham.courseraapp1.MainActivity.onCreate(MainActivity.java:22)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.Activity.performCreate(Activity.java:5104)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
03-20 22:43:31.950: E/AndroidRuntime(2799):     ... 11 more

为什么Android中的正则表达式与普通Java的行为不同?


答案 1

我对预言机java 1.7的源代码进行了一些研究。

"{0}"

我发现一些代码,当它在主循环中找到?,*或+时,它会抛出“悬空的元字符”。也就是说,不要紧接在某个文字、组或显式检查量词的其他任何位置之后。由于某种原因,不在该列表中。结果是,它通过对特殊字符的所有检查,并开始解析文本字符串。它遇到的第一个字符是 ,它告诉解析器是时候停止解析文本字符串并检查量词了。"."{{

结果是,将匹配空字符串 n 次。"{n}"

另一个结果是,第二个将首先匹配时间,然后匹配空字符串时间,有效地忽略了,如上面注释中提到的@Kobi。"x{m}{n}"xmn{n}

对我来说似乎是一个错误,但如果他们想保留它以保持向后兼容性,那我也不会感到惊讶。

"(?!a){0}"

"(?!a)"只是一个可量化的节点。您可以检查下一个字符是否为“a”10次。但是,它每次都会返回相同的结果,因此它不是很有用。在我们的例子中,它将检查下一个字符是否是'a'0次,这将始终成功。

请注意,当匹配项的长度为 0 时(如此处所示),作为优化,量词永远不会贪婪。这也防止了在这种情况下的无限递归。"(?!a)*"

"(?!a).{0}" & ".{0}(?<!a)"

如上所述,执行检查 0 次,这始终成功。它有效地忽略了它之前的任何东西。该均值与 具有预期结果的 相同。{0}"(?!a).{0}""(?!a)"

另一个类似。

安卓与众不同

正如@GenericJam中提到的,android是一种不同的实现,在这些边缘情况下可能具有不同的特征。我也尝试查看该源代码,但Android实际上在那里使用本机代码:)


答案 2