【问题标题】:Regular expression to match strings in quotes with double-quotes inside正则表达式匹配引号中的字符串,其中包含双引号
【发布时间】:2014-04-14 11:35:00
【问题描述】:

我面临一个挑战,以匹配以下格式的输入:

  • 输入由键=值对组成。键以斜线开头。该值可以是数字或引号中的字符串。
  • 该值可以选择包含转义引号,即引号后跟引号 ("")。这种转义的报价应被视为价值的一部分。无需检查转义引号是否平衡(例如,以另一个转义引号结尾)。

正则表达式应匹配序列中给定的 key=value 部分,并且不应因长输入而中断(例如,值是 10000 个字符)。

首先我想到了这个解决方案:

/(\w+)=(\d+|"(?:""|[^"])+"(?!"))

它的表现还不错,但是它在 Java6 中失败并出现 StackOverflowError 的长输入(例如现金 regexplanet)。我尝试对其进行改进以更快地运行:

/(\w+)=(\d+|"(?:""|[^"]+)+"(?!"))

但是如果输入不匹配,它会在回溯中进入无限循环尝试匹配它。

然后我来到了这个正则表达式:

/(\w+)=(\d+|".+?(?<!")(?:"")*"(?!"))

执行速度较慢,但​​似乎可以解决任务。

谁能推荐一个更好/更快的正则表达式?

示例输入:

/mol_type="protein" /transl_table=11 /note="[CDS] (""multi
line)"  nn  /organism="""Some"" Sequence" nn  /organism="Some ""Sequence"""
/translation="MHPSSSRIPHIAVVGVSAIFPGSLDAHGFWRDILSGTDLITDVPSTHWLVE
DYYDPDPSAPDKTYAKRGAFLKDVPFDPLEWGVPPSIVPATDTTQLLALIVAKRVLEDAAQGQFE
SMSRERMSVILGVTSAQELLASMVSRIQRPVWAKALRDLGYPEDEVKRACDKIAGNYVPWQESSF
PGLLGNVVAGRIANRLDLGGTNCVTDAACASSLSAMSMAINELALGQSDLVIAGGCDTMNDAFMY
MCFSKTPALSKSGDCRPFSDKADGTLLGEGIAMVALKRLDDAERDGDRVYAVIRGIGSSSDGRSK
SVYAPVPEGQAKALRRTYAAAGYGPETVELMEAHGTGTKAGDAAEFEGLRAMFDESGREDRQWCA
LGSVKSQIGHTKAAAGAAGLFKAIMALHHKVLPPTIKVDKPNPKLDIEKTAFYLNTQARPWIRPG
DHPRRASVSSFGFGGSNFHVALEEYTGPAPKAWRVRALPAELFLLSADTPAALADRARALAKEAE
VPEILRFLARESVLSFDASRPARLGLCATDEADLRKKLEQVAAHLEARPEQALSAPLVHCASGEA
PGRVAFLFPGQGSQYVGMGADALMTFDPARAAWDAAAGVAIADAPLHEVVFPRPVFSDEDRAAQE
ARLRETRWAQPAIGATSLAHLALLAALGVRAEAFAGHSFGEITALHAAGALSAADLLRVARRRGE
LRTLGQVVDHLRASLPAAGPAASASPAAAASVPKASTAAVPAVASVAAPGAAEVERVVMAVVAET
TGYPAEMLGLQMELESDLGIDSIKRVEILSAVRDRTPGLSEVDASALAQLRTLGQVVDHLRASLP
AASAGPAVAAPAAKAPAVAAPTGVSGATPGAAEVERVVMAVVAETTGYPAEMLGLQMELESDLGI
DSIKRVEILSAVRDRTPGLAEVDASALAQLRTLGQVVDHLRASLGPAAVTAGAAPAEPAEEPAST
PLGRWTLVEEPAPAAGLAMPGLFDAGTLVITGHDAIGPALVAALAARGIAAEYAPAVPRGARGAV
FLGGLRELATADAALAVHREAFLAAQAIAAKPALFVTVQDTGGDFGLAGSDRAWVGGLPGLVKTA
ALEWPEASCRAIDLERAGRSDGELAEAIASELLSGGVELEIGLRADGRRTTPRSVRQDAQPGPLP
LGPSDVVVASGGARGVTAATLIALARASHARFALLGRTALEDEPAACRGADGEAALKAALVKAAT
SAGQRVTPAEIGRSVAKILANREVRATLDAIRAAGGEALYVPVDVNDARAVAAALDGVRGALGPV
TAIVHGAGVLADKLVAEKTVEQFERVFSTKVDGLRALLGATAGDPLKAIVLFSSIAARGGNKGQC
DYAMANEVLNKVAAAEAARRPGCRVKSLGWGPWQGGMVNAALEAHFAQLGVPLIPLAAGAKMLLD
ELCDASGDRGARGQGGAPPGAVELVLGAEPKALAAQGHGGRVALAVRADRATHPYLGDHAINGVP
VVPVVIALEWFARAARACRPDLVVTELRDVRVLRGIKLAAYESGGEVFRVDCREVSNGHGAVLAA
ELRGPQGALHYAATIQMQQPEGRVAPKGPAAPELGPWPAGGELYDGRTLFHGRDFQVIRRLDGVS
RDGIAGTVVGLREAGWVAQPWKTDPAALDGGLQLATLWTQHVLGGAALPMSVGALHTFAEGPSDG
PLRAVVRGQIVARDRTKADIAFVDDRGSLVAELRDVQYVLRPDTARGQA"
/note="primer of  Streptococcus pneumoniae

预期输出(来自regexhero.net):

【问题讨论】:

  • 看看here...
  • String.split 和朋友呢(很可能不会拆分,因为可以转义分隔符)?我很确定大多数 JSON 解析器不是基于正则表达式的。
  • @fge:感谢您的链接。确实是类似的问题,但我不需要检查转义引号是否平衡,因此模板 E(S|E)* 对我来说不是最佳选择。
  • @zapl:拆分可能是一个好朋友——欢迎您发布您的想法作为答案。请注意,输入字符串可能在“key=value”部分之间包含一些“垃圾”文本(例如示例中的nn),这是使用正则表达式解决问题的主要原因。

标签: java regex java-6


【解决方案1】:

为了在合理的时间内失败,您确实需要避免灾难性的回溯。这可以使用原子分组(?&gt;...)

/(\w+)=(\d+|"(?>(?>""|[^"]+)+)"(?!"))

# (?>(?>""|[^"]+)+)
(?>               # throw away the states created by (...)+
    (?>           # throw away the states created by [^"]+
        ""|[^"]+
    )+
)

您在对永远不会匹配的字符串使用 (?:""|[^"]+)+ 时遇到的问题与以下事实有关:每次匹配新的 [^"] 字符时,正则表达式引擎都可以选择使用内部或外部 + 量词。

这导致了很多回溯的可能性,并且在返回失败之前引擎必须全部尝试。

我们知道,如果在引擎到达终点时我们还没有找到匹配项,我们永远不会:我们需要做的就是丢弃回溯位置以避免问题,这就是原子分组的用途.

查看DEMO:24 步失败,同时保持成功案例的速度(不是真正的基准测试工具,但很容易发现灾难性的回溯)

【讨论】:

    【解决方案2】:

    你最初的正则表达式已经很好了,但它比必要的复杂,导致catastrophic backtracking

    你应该使用

    /(\w+)=(\d+|"(?:""|[^"])*"(?!"))
    

    live on regex101.com

    说明:

    /                # Slash
    (\w+)            # Indentifier --> Group 1
    =                # Equals sign
    (                # Group 2:
     \d+             # Either a number
    |                # or
     "(?:""|[^"])*"  # a quoted string
     (?!")           # unless another quote follows
    )                # End of group 2
    

    【讨论】:

    • 感谢您的建议。我看到它是我的一个小变种,但是(不幸)它使 Java 引擎崩溃。它适用于 PCRE 和 .NET,但可惜不适用于 Java。
    • @dma_k:我不太明白它会如何使 Java 崩溃,因为那里没有任何东西可以灾难性地失败(至少据我所知)。你是如何编译正则表达式的?在 Java 中,转义规则相当复杂。在这种情况下,Pattern regex = Pattern.compile("/(\\w+)=(\\d+|\"(?:\"\"|[^\"])*\"(?!\"))"); 之类的东西应该可以工作。或者你能发布一个这个正则表达式失败/崩溃的示例字符串吗?
    • 我也很惊讶Java引擎无法应付它,但情况就是这样。这是screeshot of code 在与StackOverflowError 被抛出时所讨论的输入大致相同的输入上运行。 regexplanet 也会崩溃。
    【解决方案3】:

    这个怎么样:

    /(\w+)=("(?:[^"]|"")*"|\d+)
    

    (请注意,/ 是此处正则表达式的一部分。根据您的宿主语言对其进行转义。)

    如果您的正则表达式引擎支持它(Java 支持),请创建 * possessive:

    /(\w+)=("(?:[^"]|"")*+"|\d+)
    

    经过一些调试,后一个表达式可以改进为:

    /(\w+)=("(?:""|[^"]*+)*+"|\d++)
    

    注意双重*+)*+,它允许在一个步骤中匹配连续的文本,同时不易受到灾难性回溯的影响。

    【讨论】:

    • 感谢所有格量词的提示——这是解决问题的关键。您的第二次更新正是solution proposed by Robin
    • @dma_k 是的,只是他用原子团做到了。我想保持我的所有格量词方法。 :)
    • 据我所知,“atomic grouping”与“所有格量词”概念相同(有关详细信息,请参见独立子表达式 (?> 模式);所有格量词只是语法糖那个构造)。如果可以的话,我会提名两个答案。
    • 它们在概念上是相等的。但你可以使用某物。比如\w++,它比(?&gt;\w+)短。此外,对我来说,所有格量词更容易阅读,因为它们在视觉上会影响“有趣”的部分——量词。但这是一个偏好问题(当然还有正则表达式引擎功能)。
    猜你喜欢
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-26
    相关资源
    最近更新 更多