我记得一个教科书上的例子,一个正则表达式变坏了。请注意,不建议将以下任何一种方法用于生产用途!请改用适当的 CSV 解析器。
此示例中犯的错误很常见:使用点,其中较窄的字符类更适合。
在每行正好包含 12 个以逗号分隔的整数的 CSV 文件中,找到在第 6 个位置具有 13 的行(无论 13 位于其他位置)。
1, 2, 3, 4, 5, 6, 7, 8 ,9 ,10,11,12 // don't match
42,12,13,12,32,13,14,43,56,31,78,10 // match
42,12,13,12,32,14,13,43,56,31,78,10 // don't match
我们使用正好包含 11 个逗号的正则表达式:
".*,.*,.*,.*,.*,13,.*,.*,.*,.*,.*,.*"
这样,每个“.*”都限制为一个数字。此正则表达式解决了任务,但性能非常差。(在我的计算机上,每个字符串大约600微秒,匹配和不匹配字符串之间几乎没有区别。
一个简单的非正则表达式解决方案是每行并比较第6个元素。(快得多:每串 9 微秒。split()
正则表达式如此缓慢的原因是“*”量词在默认情况下是贪婪的,因此第一个“.*”尝试匹配整个字符串,然后开始逐个字符回溯。运行时在一行上的数字计数中呈指数级。
因此,我们将贪婪的量词替换为不情愿的量词:
".*?,.*?,.*?,.*?,.*?,13,.*?,.*?,.*?,.*?,.*?,.*?"
这对于匹配的字符串(系数为 100)的性能要好得多,但对于不匹配的字符串,性能几乎保持不变。
执行正则表达式将点替换为字符类“[^,]”:
"[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,13,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*"
(对于匹配的字符串,这需要每个字符串 3.7 微秒,对于我的计算机上不匹配的字符串,这需要 2.4 微秒。