【问题标题】:What does (?!a){0}? mean in a Java regex?(?!a){0} 是什么?在 Java 正则表达式中是什么意思?
【发布时间】:2014-04-06 13:57:24
【问题描述】:

question if {0} quantifier actually makes sense 的启发,我开始使用一些包含{0} 量词的正则表达式,并编写了这个小型Java 程序,它只是根据各种测试正则表达式拆分一个测试短语:

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}(?&lt;!.{0})" 不匹配任何内容。对我来说很有意义:0-quantified token 的 Negative Lookahead / Lookbehind 不匹配。

让我感到惊讶

  1. "{0}" & "(?!a){0}":我实际上预计这里会出现异常,因为前面的标记不可量化:对于{0},前面根本没有任何东西,对于(?!a){0},实际上不仅仅是一个负前瞻。两者都在每个字符之前匹配,为什么?如果我在 javascript 验证器中尝试该正则表达式,我会得到“不可量化的错误”,see demo here!该正则表达式在 Java 和 Javascript 中的处理方式是否不同?
  2. "(?!a).{0}" & ".{0}(?&lt;!a)":这里也有一个小惊喜:除了a 之前/之后之外,短语的每个字符之前都匹配。我的理解是,在(?!a).{0} 中,(?!a) Negative Lookahead 部分断言不可能从字面上匹配a,但我正在展望.{0}。我认为它不适用于 0 量化标记,但看起来我也可以将 Lookahead 与这些标记一起使用。

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


更新:

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

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 不同?

【问题讨论】:

  • 仅供参考,你可以在这里测试java:regexplanet.com/advanced/java/index.html
  • 大声笑,我什至不知道这个网站 :-) @MartinCarney
  • 另请参阅此问题How does {m}{n} work? - 如果您不介意,我直接链接到我的答案,因为我认为它是相关的。基本上,Java 忽略了多余的 {n},这在大多数其他正则表达式风格中是非法的(就像您发布的示例中一样)。
  • 这与代码高尔夫无关。这是对 java regex 引擎如何工作的深刻理解。
  • Android 与 Java 的某些东西有所不同并不奇怪,尤其是像这样的极端情况,因为 Android Java 是 Java 的重新实现,就像 Linux 是 Unix 的重新实现但无法直接访问来源。

标签: java android regex split


【解决方案1】:

我对 oracles java 1.7 的源做了一些调查。

"{0}"

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

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

另一个结果是第二个"x{m}{n}" 将首先匹配x m 次,然后匹配空字符串n 次,有效地忽略{n},正如@Kobi 在上面的cmets 中提到的那样。

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

"(?!a){0}"

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

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

"(?!a).{0}" &amp; ".{0}(?&lt;!a)"

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

另一个类似。

Android 与众不同

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

【讨论】:

  • Android 使用 ICU 正则表达式,它与 Java 类似,但在支持的结构方面并不相同。
  • 引擎如何解释量词并不重要。底线是量词/重复运算符在不量化任何内容时不能存在于状态机中。而且,它们不能被解释为独立的状态。 For some reason, { is not in that list - 你是说没有解析有效的范围量词吗?这不是真的。 {5} 在某处被解析为重复运算符,否则它是文字。没有有效的前值,就没有有效的解释作为量词。出于这个原因,x{3}{8} 被解析为嵌套量词,没有任何意义。
  • This also prevents infinite recursion in the "(?!a)*" case. 断言上的所有量词解析为 1 或 0。与非贪婪或无限循环无关。这可以通过基准测试进行测试。 Regex1: (?!a){400000,100000000} Completed iterations: 50 / 50 ( x 1000 ) Matches found per iteration: 2 Elapsed Time: 0.26 s, 256.39 ms, 256387 µs Regex2: (?!a)+ Completed iterations: 50 / 50 ( x 1000 ) Matches found per iteration: 2 Elapsed Time: 0.25 s, 254.53 ms, 254529 µs
  • 也在这里看到 Regex1: (?!a){400000,100000000} Completed iterations: 50 / 50 ( x 1000 ) Matches found per iteration: 2 Elapsed Time: 0.25 s, 254.34 ms, 254339 µs Regex2: (?!a){1} Completed iterations: 50 / 50 ( x 1000 ) Matches found per iteration: 2 Elapsed Time: 0.25 s, 254.06 ms, 254059 µs 所以贪婪/非贪婪与它无关。
  • @sln Quantifiers cannot exist in a state machine when they don't quantify anything。确切地。这就是为什么在主循环中,当没有什么可以量化的时候,当你尝试这样做时,代码会抛出“悬空元字符”。例如使用正则表达式"+"。只有{ 字符通过,这似乎是一个错误。当 Java 解析它可以量化的字符时,它会检查 { 并正常工作。
猜你喜欢
  • 1970-01-01
  • 2012-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-28
  • 1970-01-01
  • 2012-09-07
相关资源
最近更新 更多