Java Matcher 中出现间歇性的堆栈溢出错误

13

我有一些文件解析器代码,在m.matches()(其中m是一个Matcher)上不定期地出现堆栈溢出错误。

我再次运行我的应用程序,并解析相同的文件时,没有堆栈溢出。

我的Pattern确实有点复杂。它基本上是一堆具有命名组的可选零长度正向先行断言,以便我可以匹配一堆变量名/值对,而不考虑它们的顺序。但是,如果某个字符串会导致堆栈溢出错误,我会期望它总是会导致错误...而不仅仅是有时候...有什么想法吗?

Pattern的简化版本:"prefix(?=\\s+user=(?<user>\\S+))?(?=\\s+repo=(?<repo>\\S+))?.*?"

完整的正则表达式为...

app=github(?=(?:[^"]|"[^"]*")*\s+user=(?<user>\S+))?(?=(?:[^"]|"[^"]*")*\s+repo=(?<repo>\S+))?(?=(?:[^"]|"[^"]*")*\s+remote_address=(?<ip>\S+))?(?=(?:[^"]|"[^"]*")*\s+now="(?<time>\S+)\+\d\d:\d\d")?(?=(?:[^"]|"[^"]*")*\s+url="(?<url>\S+)")?(?=(?:[^"]|"[^"]*")*\s+referer="(?<referer>\S+)")?(?=(?:[^"]|"[^"]*")*\s+status=(?<status>\S+))?(?=(?:[^"]|"[^"]*")*\s+elapsed=(?<elapsed>\S+))?(?=(?:[^"]|"[^"]*")*\s+request_method=(?<requestmethod>\S+))?(?=(?:[^"]|"[^"]*")*\s+created_at="(?<createdat>\S+)(?:-|\+)\d\d:\d\d")?(?=(?:[^"]|"[^"]*")*\s+pull_request_id=(?<pullrequestid>\d+))?(?=(?:[^"]|"[^"]*")*\s+at=(?<at>\S+))?(?=(?:[^"]|"[^"]*")*\s+fn=(?<fn>\S+))?(?=(?:[^"]|"[^"]*")*\s+method=(?<method>\S+))?(?=(?:[^"]|"[^"]*")*\s+current_user=(?<user2>\S+))?(?=(?:[^"]|"[^"]*")*\s+content_length=(?<contentlength>\S+))?(?=(?:[^"]|"[^"]*")*\s+request_category=(?<requestcategory>\S+))?(?=(?:[^"]|"[^"]*")*\s+controller=(?<controller>\S+))?(?=(?:[^"]|"[^"]*")*\s+action=(?<action>\S+))?.*?

堆栈溢出错误的栈顶...(它大约有9800行)

Exception: java.lang.StackOverflowError
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4480)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3706)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4516)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4570)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4697)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4629)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4480)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3706)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4516)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4570)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4697)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4629)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4480)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3706)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4516)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4570)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4697)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4629)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4480)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3706)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4516)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4570)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4697)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4629)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4480)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3706)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4516)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4570)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4697)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4629)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4480)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3706)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4516)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4570)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4697)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4629)
    at java.util.regex.Pattern$BranchConn.match(Pattern.java:4480)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3706)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4516)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4570)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4697)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4629)

我在某行代码上遇到了错误,但自那时以来已经运行了10次而没有出现任何错误。

app=github env=production enterprise=true auth_fingerprint=\"token:6b29527b:9.99.999.99\" controller=\"Api::GitCommits\" path_info=\"/api/v3/repos/XYZ-ABCDE/abcdefg-abc/git/commits/77ae1376f969059f5f1e23cc5669bff8cca50563.diff\" query_string=nil version=v3 auth=oauth current_user=abcdefghijk oauth_access_id=24 oauth_application_id=0 oauth_scopes=\"gist,notifications,repo,user\" route=\"/repositories/:repository_id/git/commits/:id\" org=XYZ-ABCDE oauth_party=personal repo=XYZ-ABCDE/abcdefg-abc repo_visibility=private now=\"2015-09-24T13:44:52+00:00\" request_id=675fa67e-c1de-4bfa-a965-127b928d427a server_id=c31404fc-b7d0-41a1-8017-fc1a6dce8111 remote_address=9.99.999.99 request_method=get content_length=92 content_type=\"application/json; charset=utf-8\" user_agent=nil accept=application/json language=nil referer=nil x_requested_with=nil status=404 elapsed=0.041 url=\"https://git.abc.abcd.abc.com/api/v3/repos/XYZ-ABCDE/abcdefg-abc/git/commits/77ae1376f969059f5f1e23cc5669bff8cca50563.diff\" worker_request_count=77192 request_category=apiapp=github env=production enterprise=true auth_fingerprint=\"token:6b29527b:9.99.999.99\" controller=\"Api::GitCommits\" path_info=\"/api/v3/repos/XYZ-ABCDE/abcdefg-abc/git/commits/9bee255c7b13c589f4e9f1cb2d4ebb5b8519ba9c.diff\" query_string=nil version=v3 auth=oauth current_user=abcdefghijk oauth_access_id=24 oauth_application_id=0 oauth_scopes=\"gist,notifications,repo,user\" route=\"/repositories/:repository_id/git/commits/:id\" org=XYZ-ABCDE oauth_party=personal repo=XYZ-ABCDE/abcdefg-abc repo_visibility=private now=\"2015-09-24T13:44:52+00:00\" request_id=89fcb32e-9ab5-47f7-9464-e5f5cff175e8 server_id=1b74880a-5124-4483-adce-111b60dac111 remote_address=9.99.999.99 request_method=get content_length=92 content_type=\"application/json; charset=utf-8\" user_agent=nil accept=application/json language=nil referer=nil x_requested_with=nil status=404 elapsed=0.024 url=\"https://git.abc.abcd.abc.com/api/v3/repos/XYZ-ABCDE/abcdefg-abc/git/commits/9bee255c7b13c589f4e9f1cb2d4ebb5b8519ba9c.diff\" worker_request_count=76263 request_category=api

有趣的是...这一行似乎是错误的...日志似乎将换行符放在错误的位置,导致两个日志条目在单个行上,然后是一个空行。正是这一行太长造成了错误...但至少曾经如此...现在运行良好,没有堆栈溢出问题。


正则表达式是否有效?尝试使用(?s)prefix(?=.*\\s+user=(?<user>\\S+))(?=.*\\s+repo=(?<repo>\\S+))。您将?量词应用于前瞻,这是没有意义的。结尾处的.*?不匹配任何内容,只是空字符串。请参见此演示 - Wiktor Stribiżew
@nhahtdh 完整的正则表达式已发布。不确定您是否能够轻松地重现...我可以很好地解析10万行,然后它随机死机并显示此错误。启动它时,它可以很好地解析相同的行。 - Ben
@stribizhev 是的 - 正则表达式几乎总是按照预期的方式工作。 .* 在结尾处是必需的... 它基本上匹配行中的 EVERYTHING... 因为前向查找都没有声明任何内容... 而且 Java 要求整个行都匹配。 最后的 ? ... 你是对的... 确实不需要... :-) 但是如果没有 .* 就无法工作... 请尝试下面的代码... - Ben
Pattern p = Pattern.compile("(?=.\s+user=(?<user>\S+))?"); Matcher m = p.matcher("user=username"); if (!m.matches()) { System.out.println("没有匹配上,结尾没有 ."); } p = Pattern.compile("(?=.*\\s+user=(?\\S+))?.*"); m = p.matcher("user=username"); if (m.matches()) { System.out.println("匹配上了,结尾有 .*"); } - Ben
我完全确定问题是由 (?:[^"]|"[^"]*")* 引起的。在一个长字符串中,它会重复多次,并且每个外部选择的重复都必须存储在堆栈上,导致堆栈溢出。你试图解析的输入是什么?URL中的查询字符串?配置文件? - nhahtdh
显示剩余4条评论
2个回答

11

解决这个问题有两种方法:

  • 正确地解析输入字符串,并从 Map 中获取关键值。

    我强烈推荐使用此方法,因为代码将更加简洁,我们也不再需要关注输入大小的限制。

  • 修改现有正则表达式以大大减少实现缺陷造成的 StackOverflowError 影响。

解析输入字符串

您可以使用以下正则表达式解析输入字符串:

\G\s*+(\w++)=([^\s"]++|"[^"]*+")(?:\s++|$)
  • 所有量词都采用占有模式(*+替代 *, ++替代+),因为我写的模式不需要回溯。

  • 您可以使用基本正则表达式(\w++)=([^\s"]++|"[^"]*+")在中间匹配键值对。

  • \G用于确保匹配从上一次匹配结束的地方开始。它用于防止引擎在匹配失败时“碰撞”。

  • \s*+(?:\s++|$)用于消耗多余的空格。我指定(?:\s++|$)而不是\s*+以防止将key="value"key=value识别为有效输入。

下面是完整的示例代码:

private static final Pattern KEY_VALUE = Pattern.compile("\\G\\s*+(\\w++)=([^\\s\"]++|\"[^\"]*+\")(?:\\s++|$)");

public static Map<String, String> parseKeyValue(String kvString) {
    Matcher matcher = KEY_VALUE.matcher(kvString);

    Map<String, String> output = new HashMap<String, String>();
    int lastIndex = -1;

    while (matcher.find()) {
        output.put(matcher.group(1), matcher.group(2));
        lastIndex = matcher.end();
    }

    // Make sure that we match everything from the input string
    if (lastIndex != kvString.length()) {
        return null;
    }

    return output;
}

根据你的需求,你可能需要取消值的引用。

你也可以重写函数以传递一个要提取的键的列表,并在循环中挑选它们,以避免存储你不关心的键。

修改正则表达式

问题是由于外部重复使用了(?:[^"]|"[^"]*")*,导致在输入字符串足够长时出现StackOverflowError

具体而言,在每次重复中,它匹配引号标记或单个非引号字符。因此,堆栈随着非引号字符的数量线性增长并爆炸。

您可以将所有实例(?:[^"]|"[^"]*")*替换为[^"]*(?:"[^"]*"[^"]*)*。堆栈现在将随着引号标记的数量线性增长,因此除非输入字符串中有数千个引号标记,否则不会发生StackOverflowError。

Pattern KEY_CAPTURE = Pattern.compile("app=github(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+user=(?<user>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+repo=(?<repo>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+remote_address=(?<ip>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+now=\"(?<time>\\S+)\\+\\d\\d:\\d\\d\")?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+url=\"(?<url>\\S+)\")?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+referer=\"(?<referer>\\S+)\")?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+status=(?<status>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+elapsed=(?<elapsed>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+request_method=(?<requestmethod>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+created_at=\"(?<createdat>\\S+)(?:-|\\+)\\d\\d:\\d\\d\")?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+pull_request_id=(?<pullrequestid>\\d+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+at=(?<at>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+fn=(?<fn>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+method=(?<method>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+current_user=(?<user2>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+content_length=(?<contentlength>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+request_category=(?<requestcategory>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+controller=(?<controller>\\S+))?(?=[^\"]*(?:\"[^\"]*\"[^\"]*)*\\s+action=(?<action>\\S+))?");

等价的正则表达式展开式为 (A|B)*A*(BA*)*。哪个作为 A,哪个作为 B 取决于它们重复的次数——重复更多的应该是 A,另一个应该是 B。

深入探究实现细节

Pattern 中的 StackOverflowError 是一个已知的问题,当您的模式包含一个非确定性捕获/非捕获组的重复时,即子模式(?:[^"]|"[^"]*")*,就可能发生这种情况。

1 这是 Pattern 源代码中使用的术语,可能意味着模式具有固定长度。然而,实现认为选择分支 | 是非确定性的,不考虑实际模式。

非确定性捕获/非捕获组的贪婪或懒惰重复被编译成 Loop/LazyLoop 类,通过递归实现重复。因此,这样的模式极易触发 StackOverflowError,特别是当该组包含仅一次匹配单个字符的分支时。

另一方面,确定性2 重复、占有型重复以及 独立组即 (?>...)(也称为原子组或非回溯组)的重复被编译成 Curly/GroupCurly 类,在大多数情况下使用循环处理重复,因此不会出现 StackOverflowError

2 重复的模式是字符类,或没有任何选择分支的固定长度捕获/非捕获组

您可以在下面看到您原始正则表达式的一个片段是如何被编译的。请注意有问题的部分,它从 Loop 开始,并将其与堆栈跟踪进行比较。

app=github(?=(?:[^"]|"[^"]*")*\s+user=(?<user>\S+))?(?=(?:[^"]|"[^"]*")*\s+repo=(?<repo>\S+))?
BnM. Boyer-Moore (BMP only version) (length=10)
  app=github
Ques. Greedy optional quantifier
  Pos. Positive look-ahead
    GroupHead. local=0
    Prolog. Loop wrapper
    Loop [1889ca51]. Greedy quantifier {0,2147483647}
      GroupHead. local=1
      Branch. Alternation (in printed order):
        CharProperty.complement. S̄:
          BitClass. Match any of these 1 character(s):
            "
        ---
        Single. Match code point: U+0022 QUOTATION MARK
        Curly. Greedy quantifier {0,2147483647}
          CharProperty.complement. S̄:
            BitClass. Match any of these 1 character(s):
              "
          Node. Accept match
        Single. Match code point: U+0022 QUOTATION MARK
        ---
      BranchConn [7e41986c]. Connect branches to sequel.
      GroupTail [47e1b36]. local=1, group=0. --[next]--> Loop [1889ca51]
    Curly. Greedy quantifier {1,2147483647}
      Ctype. POSIX (US-ASCII): SPACE
      Node. Accept match
    Slice. Match the following sequence (BMP only version) (length=5)
      user=
    GroupHead. local=3
    Curly. Greedy quantifier {1,2147483647}
      CharProperty.complement. S̄:
        Ctype. POSIX (US-ASCII): SPACE
      Node. Accept match
    GroupTail [732c7887]. local=3, group=2. --[next]--> GroupTail [6c9d2223]
    GroupTail [6c9d2223]. local=0, group=0. --[next]--> Node [4ea5d7f2]
    Node. Accept match
  Node. Accept match
Ques. Greedy optional quantifier
  Pos. Positive look-ahead
    GroupHead. local=4
    Prolog. Loop wrapper
    Loop [402c5f8a]. Greedy quantifier {0,2147483647}
      GroupHead. local=5
      Branch. Alternation (in printed order):
        CharProperty.complement. S̄:
          BitClass. Match any of these 1 character(s):
            "
        ---
        Single. Match code point: U+0022 QUOTATION MARK
        Curly. Greedy quantifier {0,2147483647}
          CharProperty.complement. S̄:
            BitClass. Match any of these 1 character(s):
              "
          Node. Accept match
        Single. Match code point: U+0022 QUOTATION MARK
        ---
      BranchConn [21347df0]. Connect branches to sequel.
      GroupTail [7d382897]. local=5, group=0. --[next]--> Loop [402c5f8a]
    Curly. Greedy quantifier {1,2147483647}
      Ctype. POSIX (US-ASCII): SPACE
      Node. Accept match
    Slice. Match the following sequence (BMP only version) (length=5)
      repo=
    GroupHead. local=7
    Curly. Greedy quantifier {1,2147483647}
      CharProperty.complement. S̄:
        Ctype. POSIX (US-ASCII): SPACE
      Node. Accept match
    GroupTail [71f111ba]. local=7, group=4. --[next]--> GroupTail [9c304c7]
    GroupTail [9c304c7]. local=4, group=0. --[next]--> Node [4ea5d7f2]
    Node. Accept match
  Node. Accept match
LastNode.
Node. Accept match

2
哇...谢谢!非常完整和有用的答案!我喜欢你的首选建议...更加简洁和灵活!我花了一些时间才弄清楚这是如何工作的... [^"](?:"[^"]"[^"]) ...我很好奇...这是否可以防止尽可能多的递归?(?:[^"]++|"[^"]") - Ben
@Ben:在你的情况下它不会起作用,因为你需要引擎回溯以获取你想匹配的键值对。 - nhahtdh
@nhahtdh,非常有见地!希望我能多点赞 :) - epoch
@nhahtdh 谢谢!啊...所以即使它被包含在非占有组中,占有性也是永久的吗?就像我认为占有性会使得每个非捕获组的迭代在单个迭代中尽可能多地获取非引号,但是每个非捕获组的迭代作为一个整体到达结尾时仍然会回溯,包括其中包含的占有匹配。天哪...当正则表达式变得复杂时,我突然感到如此无知,感觉自己应该更好地掌握这个... - Ben
@Ben - 在这种情况下,实际上你不想要回溯。使用原子组是最好的选择。我更新了一个新的正则表达式,请试一试。 - user557597
显示剩余2条评论

3

最终答案:

将此(?:[^"]|"[^"]*")*功能移入与其他功能相似的备选组中。

示例:https://ideone.com/YuVcMg

它不会被破坏!


附注-我注意到您说您删除了一个换行符,并且最后一条记录没有分隔符连接下一条记录,就像这样request_category=apiapp=github

那没关系,但是当它遇到\S+时,这些正则表达式大多会忽略它。

因此,最好用(?:(?!app=github)\S)+替换\S+,以下正则表达式未添加该替换。

以下是添加该替换后的正则表达式:

"(?s)app=github(?>\\s+user=(?<user>(?:(?!app=github)\\S)+)|\\s+repo=(?<repo>(?:(?!app=github)\\S)+)|\\s+remote_address=(?<ip>(?:(?!app=github)\\S)+)|\\s+now=\\\\?\"(?<time>(?:(?!app=github)\\S)+)\\+\\d\\d:\\d\\d\\\\?\"|\\s+url=\\\\?\"(?<url>(?:(?!app=github)\\S)+)\\\\?\"|\\s+referer=\\\\?\"(?<referer>(?:(?!app=github)\\S)+)\\\\?\"|\\s+status=(?<status>(?:(?!app=github)\\S)+)|\\s+elapsed=(?<elapsed>(?:(?!app=github)\\S)+)|\\s+request_method=(?<requestmethod>(?:(?!app=github)\\S)+)|\\s+created_at=\\\\?\"(?<createdat>(?:(?!app=github)\\S)+)[-+]\\d\\d:\\d\\d\\\\?\"|\\s+pull_request_id=(?<pullrequestid>\\d+)|\\s+at=(?<at>(?:(?!app=github)\\S)+)|\\s+fn=(?<fn>(?:(?!app=github)\\S)+)|\\s+method=(?<method>(?:(?!app=github)\\S)+)|\\s+current_user=(?<user2>(?:(?!app=github)\\S)+)|\\s+content_length=(?<contentlength>(?:(?!app=github)\\S)+)|\\s+request_category=(?<requestcategory>(?:(?!app=github)\\S)+)|\\s+controller=(?<controller>(?:(?!app=github)\\S)+)|\\s+action=(?<action>(?:(?!app=github)\\S)+)|\"[^\"]*\"|(?!app=github).)+"

以下是使用该示例的链接:https://ideone.com/hdwufO


正则表达式

原始内容:

(?s)app=github(?>\s+user=(?<user>\S+)|\s+repo=(?<repo>\S+)|\s+remote_address=(?<ip>\S+)|\s+now=\\?"(?<time>\S+)\+\d\d:\d\d\\?"|\s+url=\\?"(?<url>\S+)\\?"|\s+referer=\\?"(?<referer>\S+)\\?"|\s+status=(?<status>\S+)|\s+elapsed=(?<elapsed>\S+)|\s+request_method=(?<requestmethod>\S+)|\s+created_at=\\?"(?<createdat>\S+)[-+]\d\d:\d\d\\?"|\s+pull_request_id=(?<pullrequestid>\d+)|\s+at=(?<at>\S+)|\s+fn=(?<fn>\S+)|\s+method=(?<method>\S+)|\s+current_user=(?<user2>\S+)|\s+content_length=(?<contentlength>\S+)|\s+request_category=(?<requestcategory>\S+)|\s+controller=(?<controller>\S+)|\s+action=(?<action>\S+)|"[^"]*"|(?!app=github).)+

字符串操作:

"(?s)app=github(?>\\s+user=(?<user>\\S+)|\\s+repo=(?<repo>\\S+)|\\s+remote_address=(?<ip>\\S+)|\\s+now=\\\\?\"(?<time>\\S+)\\+\\d\\d:\\d\\d\\\\?\"|\\s+url=\\\\?\"(?<url>\\S+)\\\\?\"|\\s+referer=\\\\?\"(?<referer>\\S+)\\\\?\"|\\s+status=(?<status>\\S+)|\\s+elapsed=(?<elapsed>\\S+)|\\s+request_method=(?<requestmethod>\\S+)|\\s+created_at=\\\\?\"(?<createdat>\\S+)[-+]\\d\\d:\\d\\d\\\\?\"|\\s+pull_request_id=(?<pullrequestid>\\d+)|\\s+at=(?<at>\\S+)|\\s+fn=(?<fn>\\S+)|\\s+method=(?<method>\\S+)|\\s+current_user=(?<user2>\\S+)|\\s+content_length=(?<contentlength>\\S+)|\\s+request_category=(?<requestcategory>\\S+)|\\s+controller=(?<controller>\\S+)|\\s+action=(?<action>\\S+)|\"[^\"]*\"|(?!app=github).)+"

格式化:

 (?s)
 app = github
 (?>
      \s+ 
      user =
      (?<user> \S+ )                # (1)
   |  
      \s+  repo =
      (?<repo> \S+ )                # (2)
   |  
      \s+ remote_address =
      (?<ip> \S+ )                  # (3)
   |  
      \s+ now= \\? "
      (?<time> \S+ )                # (4)
      \+ \d\d : \d\d \\? "
   |  
      \s+ url = \\? "
      (?<url> \S+ )                 # (5)
      \\? "
   |  
      \s+ referer = \\? "
      (?<referer> \S+ )             # (6)
      \\? "
   |  
      \s+ status =
      (?<status> \S+ )              # (7)
   |  
      \s+ elapsed =
      (?<elapsed> \S+ )             # (8)
   |  
      \s+ request_method =
      (?<requestmethod> \S+ )       # (9)
   |  
      \s+ created_at = \\? "
      (?<createdat> \S+ )           # (10)
      [-+] 
      \d\d : \d\d \\? "
   |  
      \s+ pull_request_id =
      (?<pullrequestid> \d+ )       # (11)
   |  
      \s+ at=
      (?<at> \S+ )                  # (12)
   |  
      \s+ fn=
      (?<fn> \S+ )                  # (13)
   |  
      \s+ method =
      (?<method> \S+ )              # (14)
   |  
      \s+ current_user =
      (?<user2> \S+ )               # (15)
   |  
      \s+ content_length =
      (?<contentlength> \S+ )       # (16)
   |  
      \s+ request_categor y=
      (?<requestcategory> \S+ )     # (17)
   |  
      \s+ controller =
      (?<controller> \S+ )          # (18)
   |  
      \s+ action =
      (?<action> \S+ )              # (19)
   |  
      " [^"]* "                     # None of the above, give quotes a chance
   |  
      (?! app = github )            # Failsafe, consume a character, advance by 1
      . 
 )+

@nhahtdh - 我已经发布了两种正则表达式的性能统计数据。我没有看到任何堆栈溢出错误,但是我没有Java,所以分别使用Boost和Perl进行了测试。也许你可以使用我在更新中解释的测试来演示溢出或超时。 - user557597
关于SOE,这里有一个ideone上的测试:https://ideone.com/sSA1cG。您的模式将在引号字符串外使用每个字符一个堆栈帧,因此当字符串更长时,它将获得SOE。 - nhahtdh
@nhahtdh - 非常有趣。唯一似乎能阻止它的是原子组。试试这个 https://ideone.com/YuVcMg 我只是将功能移到了一个原子群组中。但是,坦率地说,如果它是一个普通组,就不会有回溯。所以,Java很厉害!! - user557597
@Ben -_原子组_就像普通组一样,不同的是其中包含的表达式匹配的任何内容都不会被返回给外部回溯。此外,在给定位置从左到右进行匹配。并且选项在左到右(或者上到下)处理,因此将"[^"]*"放在其他选项后面,可以使其匹配完整的引号部分。最后,组中的最后一个是单个字符.。这两个一起消耗了所有文本,直到您想要匹配的键/值对,该键/值对首先按照备选顺序进行匹配。 - user557597
@Ben - 第二个问题。我添加了正则表达式和链接,处理相邻记录,所以它不会超过下一条记录。现在,记录之间是否有空格都无关紧要。注意-我使用的技术来自多年的经验。但是,我已经学到了Java可能存在其他引擎上不存在的问题。总有一个解决方法,但每次都这样做会很麻烦。 - user557597
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接