【问题标题】:testing for "EndsWith" efficiently with a Regex使用正则表达式有效地测试“EndsWith”
【发布时间】:2010-01-17 15:58:56
【问题描述】:

我需要构建一个正则表达式(.NET 语法)来确定字符串是否以特定值结尾。具体来说,我需要测试一个文件是否具有特定的扩展名(或一组扩展名)。

我正在尝试修复的代码正在使用:

.*\.(png|jpg|gif)$

在我的场景中,这对于失败的匹配来说非常慢(可能是由于回溯。

在开始时简单地删除.*(这很好,因为API 只测试匹配并且不提取任何内容)使正则表达式更加高效。

还是觉得效率很低。我在这里遗漏了什么明显的东西吗?

不幸的是,我无法控制相关 API,因此我需要一个正则表达式来执行此操作,尽管我通常不会认为正则表达式是适合这项工作的工具。

我还使用RegexOptions.RightToLeft 进行了一些测试,发现使用^.*\.(png|jpg|gif)$ 可以从我的测试用例中挤出更多性能,但是我找不到在正则表达式本身的字符串,所以我认为我不能使用它。

【问题讨论】:

  • \.(png|jpg|gif)$ 的缓慢程度有多大,因为我认为这是使用正则表达式最简单的方法。其余的取决于实施。
  • 为什么不直接遍历有效扩展的数组并使用.EndsWith?简单的字符串比较比正则表达式引擎快得多。
  • 需要多快?在 Ruby(最慢的语言之一)中,我每秒可以比较 348,000 条路径。我会选择更简单的正则表达式,不是因为它更快,而是因为它更容易阅读和理解。
  • @Wayne:只有当你永远被一些扩展锁定时,它才会更容易阅读和理解。如果以后你想支持bmp怎么办? tif? tiff? apng?或者纠正jpeg实际上也是一个广泛使用的JPEG扩展?你的正则表达式很快就会变成一个很难diff 的单行怪物,我的数组仍然可以维护和理解。
  • @Kenny,说得好。在 Ruby 中,可以很容易地用字符串构建正则表达式。这肯定会影响我对什么是容易的和什么是困难的看法。从扩展数组开始,可以像这样简单地得到一个正则表达式:Regexp.new('.' + extensions.join('|') + '$')。如果可以用 C# 中的字符串制作正则表达式,则可以使用相同的技术(不同的语法)。

标签: c# regex


【解决方案1】:

我无权访问 C#,所以我不能尝试这个...但是您应该能够通过强制引擎首先找到字符串的结尾,然后匹配扩展名来避免过多的回溯:

$(?<=\.(gif|png|jpg))

不过,我不确定后视对性能的影响。

【讨论】:

  • 根据我的初步测试,这比我使用的方法快大约 2 倍,并且与我在测试应用程序中进行的 RightToLeft 匹配大致相当。我想这是有道理的,因为它本质上是手动将表达式编写为 RightToLeft
  • 好的,很高兴知道!我很确定一些正则表达式引擎在编译阶段看到$ 锚点时会进行优化,确保从字符串末尾开始检查,但显然.NET 正则表达式引擎不会。
  • 我注意到有人对我的回答投了反对票,但没在意解释原因……这让反对票毫无意义,不是吗?请始终解释为什么您对某事投反对票。
【解决方案2】:

真的,您也可以完全放弃 Regex,并使用 String.EndsWidth,如下所示:

var extensions = new String[] { ".png", ".jpg", ".gif" };
extensions.Any(ext => "something".EndsWith(ext));

我通常觉得在这种情况下使用简单的字符串函数最终会更快,而不是试图找到一种聪明的方法来使用高效的正则表达式,就运行时和/或开发时间而言,除非你是熟悉并知道在 Regex 方面什么是有效的。

【讨论】:

  • 另一方面,也许过于偏颇,请注意,我认为正则表达式实际上是邪恶的小妖精,试图超越你的大脑。
【解决方案3】:

让它看起来专门针对一个句点而不是扩展名之前的任何字符:

\.(png|jpg|gif)$

这将使其更安全(不会匹配 x.xgif),并且在找到句点之前根本不需要进行任何回溯(而不是在每个字符上回溯)。

【讨论】:

  • 我原来的正则表达式确实有文字点,但是当我在这里发布时它在标记中丢失了。
【解决方案4】:

如果您可以更改代码,为什么不能使用其他代码?你不控制 API,对,但无论如何你都在改变它。这个我真的不明白。

无论如何,为什么不简单:

var AcceptedExtensions = new List<string>() { "txt", "html", "htm" };
var extension = filename.Substring(filename.LastIndexOf(".") + 1).ToLower();
return AcceptedExtensions.Contains(extension);

IEnumerable AcceptedExtensions 将从某些配置中加载,与加载 jpg|gif|... 的方式相同。或者这将是一个常数,无论如何。您只是不需要在每次使用它时都重新创建它(不过我怀疑这会是一个瓶颈)。

【讨论】:

  • 是的,这是显而易见的方法,但我很确定他坚持使用一个 API,该 API 将字符串作为正则表达式处理。
  • 是的,我希望能够使用基于字符串的方法而不是正则表达式,但在这种情况下我没有这个选项。
  • 你为什么说你没有选择权?一种选择是上面的代码。为什么不能用?
  • @Bruno Reis:想象一下他必须使用类似processFiles(&lt;regex_goes_here&gt;) 的 API,并且 processFiles() 函数在二进制 dll 中提供给他。
  • @Bruno Reis:我们确切地知道限制是什么。他在帖子中说:"I don't control the API in question so I need a regex to do this even though I wouldn't normally consider regex to be the right tool for the job"。这意味着他需要调用一个接受字符串的函数(我不知道为什么不是正则表达式对象)并将该字符串解释为正则表达式。他不能改变那个函数的来源。他要求将一个好的正则表达式传递给该函数。
【解决方案5】:

您可能不需要正则表达式...但是请使用原始问题:

确保您使用 RegexOptions.Compiled 来预编译正则表达式,然后重用您的 RegEx 对象。这样可以避免每次使用 RegEx 时都设置它,这将大大加快速度。

【讨论】:

  • @NowYouHaveTwoProblems:您能否测试一下 SoapBox 的解决方案并告诉我们与您的方法相比如何?
  • 由于他不能发送RightToLeft选项,他也不能发送Compiled选项。
  • @Blixt 就在这里。我所能做的就是传入一个字符串,该字符串将被解释为远端的正则表达式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多