C++11 正则表达式替换确实相当慢,至少到目前为止是这样。 PCRE 在模式匹配速度方面表现得更好,但是,PCRECPP 为基于正则表达式的替换提供了非常有限的方法,引用手册页:
您可以将“str”中“pattern”的第一个匹配项替换为“rewrite”。
在“重写”中,反斜杠转义数字(\1 到 \9)可用于
插入与相应括号组匹配的文本
图案。 “rewrite”中的\0指的是整个匹配文本。
与 Perl 的 's' 命令相比,这确实很差。这就是为什么我围绕 PCRE 编写了自己的 C++ 包装器,它以接近 Perl 的 's' 的方式处理基于正则表达式的替换,并且还支持 16 位和 32 位字符串:PCRSCPP https://github.com/alllexx88/pcrscpp:
命令字符串语法
命令语法遵循 Perls/pattern/substitute/[options]
习俗。任何字符(反斜杠除外)\
)可以用作
分隔符,不仅仅是/
,但请确保分隔符已转义
一个反斜杠(\
)如果用在pattern
, substitute
or options
子字符串,例如:
请记住在 C++ 代码中使用双反斜杠,除非使用原始字符串
文字(参见字符串字面量 http://en.cppreference.com/w/cpp/language/string_literal):
pcrscpp::replace rx("s/\\\\/\\//g");
模式字符串语法
模式字符串直接传递给pcre*_compile
,因此必须
遵循 PCRE 语法,如中所述PCRE文档 http://pcre.org/pcre.txt.
替换字符串语法
替换字符串反向引用语法类似于 Perl 的:
-
$1
... $n
:第 n 个捕获子模式匹配。
-
$&
and $0
: 整场比赛
-
${label}
:标记子模式匹配。label
最多 32 个字母数字 +
下划线字符 ('A'-'Z'
,'a'-'z'
,'0'-'9'
,'_'
),
第一个字符必须是字母
-
$`
and $'
(反引号和勾号)指的是之前主题的区域
以及赛后。与 Perl 一样,未经修改的
即使先前匹配了全局替换,也会使用主题。
此外,还可以识别以下转义序列:
-
\n
: 新队
-
\r
: 回车
-
\t
: 水平制表符
-
\f
: 换页
-
\b
: 退格键
-
\a
: 警钟
-
\e
: escape
-
\0
:二进制零
任何其他转义序列\<char>
,被解释为<char>
,
这意味着你也必须转义反斜杠
选项字符串语法
在类似 Perl 的方式中,选项字符串是允许的修饰符序列
字母。 PCRSCPP 识别以下修饰符:
- Perl-compatible flags
-
g
:全局替换,不仅仅是第一个匹配
-
i
: 不区分大小写的匹配
(PCRE_CASELESS)
-
m
: 多行模式:^
and $
另外匹配位置
分别在换行符之后和之前
(PCRE_MULTILINE)
-
s
: 让范围.
元字符包括换行符
(将换行符视为普通字符)
(PCRE_DOTALL)
-
x
:允许扩展正则表达式语法,
在复杂模式中启用空白和注释
(PCRE_EXTENDED)
- PHP-compatible flags
-
A
:“锚定”模式:仅查找“锚定”匹配:那些
从零偏移开始。在单行模式下等同于
为所有模式替代分支添加前缀^
(PCRE_锚定)
-
D
: 对待美元$
仅作为主题结束断言,覆盖默认值:
结束,或紧邻末尾换行符之前。
在多行模式下被忽略
(PCRE_DOLLAR_ENDONLY)
-
U
: 反转*
and +
贪婪逻辑:默认不贪婪,?
切换回贪婪。(?U)
and (?-U)
模式内开关
不受影响
(PCRE_UNGREEDY)
-
u
:Unicode 模式。将模式和主题视为 UTF8/UTF16/UTF32 字符串。
与 PHP 不同,它也会影响换行符,\R
, \d
, \w
等匹配
((PCRE_UTF8/PCRE_UTF16/PCRE_UTF32) | PCRE_NEWLINE_ANY
| PCRE_BSR_UNICODE | PCRE_UCP)
- PCRSCPP own flags:
-
N
: 跳过空匹配
(PCRE_NOTEMPTY)
-
T
:将替换视为普通字符串,即不进行反向引用
和转义序列解释
-
n
:丢弃要替换的字符串的不匹配部分
注意:PCRSCPP 确实not自动添加换行符,
替换结果是简单的匹配串联,
在多行模式下要特别注意这一点
我编写了一个简单的速度测试代码,它存储文件“move.sh”的 10 倍副本并测试结果字符串的正则表达式性能:
#include <pcrscpp.h>
#include <string>
#include <iostream>
#include <fstream>
#include <regex>
#include <chrono>
int main (int argc, char *argv[]) {
const std::string file_name("move.sh");
pcrscpp::replace pcrscpp_rx(R"del(s/(?:^|\n)mv[ \t]+(?:-f)?[ \t]+"([^\n]+)"[ \t]+"([^\n]+)"(?:$|\n)/$1\n$2\n/Dgn)del");
std::regex std_rx (R"del((?:^|\n)mv[ \t]+(?:-f)?[ \t]+"([^\n]+)"[ \t]+"([^\n]+)"(?:$|\n))del");
std::ifstream file (file_name);
if (!file.is_open ()) {
std::cerr << "Unable to open file " << file_name << std::endl;
return 1;
}
std::string buffer;
{
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0);
if (size > 0) {
buffer.resize(size);
file.read(&buffer[0], size);
buffer.resize(size - 1); // strip '\0'
}
}
file.close();
std::string bigstring;
bigstring.reserve(10*buffer.size());
for (std::string::size_type i = 0; i < 10; i++)
bigstring.append(buffer);
int n = 10;
std::cout << "Running tests " << n << " times: be patient..." << std::endl;
std::chrono::high_resolution_clock::duration std_regex_duration, pcrscpp_duration;
std::chrono::high_resolution_clock::time_point t1, t2;
std::string result1, result2;
for (int i = 0; i < n; i++) {
// clear result
std::string().swap(result1);
t1 = std::chrono::high_resolution_clock::now();
result1 = std::regex_replace (bigstring, std_rx, "$1\\n$2", std::regex_constants::format_no_copy);
t2 = std::chrono::high_resolution_clock::now();
std_regex_duration = (std_regex_duration*i + (t2 - t1)) / (i + 1);
// clear result
std::string().swap(result2);
t1 = std::chrono::high_resolution_clock::now();
result2 = pcrscpp_rx.replace_copy (bigstring);
t2 = std::chrono::high_resolution_clock::now();
pcrscpp_duration = (pcrscpp_duration*i + (t2 - t1)) / (i + 1);
}
std::cout << "Time taken by std::regex_replace: "
<< std_regex_duration.count()
<< " ms" << std::endl
<< "Result size: " << result1.size() << std::endl;
std::cout << "Time taken by pcrscpp::replace: "
<< pcrscpp_duration.count()
<< " ms" << std::endl
<< "Result size: " << result2.size() << std::endl;
return 0;
}
(注意std
and pcrscpp
正则表达式在这里做同样的事情,表达式 for 中的尾随换行符pcrscpp
是由于std::regex_replace
尽管不剥离换行符std::regex_constants::format_no_copy
)
并在一个大型 (20.9 MB) shell 移动脚本上启动它:
Running tests 10 times: be patient...
Time taken by std::regex_replace: 12090771487 ms
Result size: 101087330
Time taken by pcrscpp::replace: 5910315642 ms
Result size: 101087330
正如您所看到的,PCRSCPP 的速度快了 2 倍以上。我预计这种差距会随着模式复杂性的增加而扩大,因为 PCRE 可以更好地处理复杂的模式。我最初为自己编写了一个包装器,但我认为它对其他人也有用。
问候,
亚历克斯