为了允许可选的小数部分,`(\。[0-9] *)?和`.. [0-9] *`?

时间:2019-08-27 09:18:07

标签: regex

起初,我只需要匹配整数(例如123),所以我只使用[0-9]+

然后,我需要允许一个可选的小数部分(例如123.456),因此我编辑以前的正则表达式并添加\.?[0-9]*

[0-9]+\.?[0-9]*

但是,《精通正则表达式》一书却说,我应该改用(\.[0-9])*,因为\.?[0-9]*“即使\.不匹配,也会错误地允许其他数字匹配”。正确的正则表达式应为:

[0-9]+(\.[0-9]*)?

我对这里的差异有些困惑,您能举个例子清楚地说明这两个正则表达式之间的差异吗?

除了可能的ReDoS外,我想知道是否有一个示例,这两个正则表达式给出了不同的匹配结果。

2 个答案:

答案 0 :(得分:1)

基本问题

第一个正则表达式的问题

[0-9]+\.?[0-9]*

因为小数点后缀是可选的,即使后面跟着更多的数字,正则表达式也可以在任意位置拆分整数和小数部分。

假设您尝试匹配1234,则正则表达式将被允许将整数部分视为1、12、123或1234中的任何一个,其余数字为小数部分。如果您没有捕获和使用结果,那么看起来似乎是一样的,但是效率很低,因为它以多种不同的方式进行匹配。

但是,贪婪匹配会在有匹配项时为您保存,它将被[0-9]+捕获。但是请继续阅读...

捕获群体的歧义

例如,考虑使用捕获组保存数字中的整数和小数部分:

([0-9]+)(\.?[0-9]*)

一旦您匹配了数字$1$2(如果您使用的是Perl;语法因语言而异)将分别匹配整数和十进制部分,但是正则表达式允许太多的自由,因此理论上它可以在任何地方破坏它,例如我的1234示例。当然,贪婪的匹配仍然可以节省您的时间,并且您仍然可以获得正确的答案,但效率较低。但是请继续阅读...

灾难性的回溯

您可能会遇到真正问题的地方是,如果以后添加更多与字符串不匹配的内容:使用此正则表达式,您可能会遇到灾难性的回溯。

考虑此正则表达式

[0-9]+\.?[0-9]* some text

,并尝试匹配1000个数字的字符串,然后匹配不匹配some text的其他文本。在文本上失败之前,这将具有成倍地成功匹配数字的多种方式。根据您的语言,您可能会收到一条错误消息,说“灾难性的回溯”,或者堆栈溢出,内存不足错误,或者程序只是挂起,似乎除了消耗大量CPU外什么也不做。

但是,此正则表达式立即失败,无需回溯:

[0-9]+(\.[0-9]*)? some text

如果您搜索“灾难性的回溯”,则会发现许多与之相关的问题示例,更多的示例与您的一种或多种方式相似。这是我刚刚发现的一种典型的情况:Regex Pattern Catastrophic backtracking

答案 1 :(得分:0)

这仍将与100.之类的数字匹配

[0-9]+(\.[0-9]*)?

因为*意味着匹配零个或多个时间,所以

正确的正则表达式应为

[0-9]+(?:\.[0-9]+)?
  • [0-9]+-匹配数字0到9一次或多次
  • (\.[0-9]+)-匹配.后跟一个或多个数字

注意:- 如果不使用对小数部分的反向引用,则应使用非捕获组而不是捕获组。 (?:)用于使群组无法捕获