.NET正则表达式深入解析
1. .NET正则表达式简介
Microsoft的.NET Framework与Visual Basic、C#和C++等语言兼容,提供了一个共享的正则表达式库,统一了这些语言中的正则表达式语义。它是一个功能齐全、强大的引擎,能让你在速度和便利性之间实现最大的灵活性。虽然每种语言处理对象和方法的语法不同,但底层的对象和方法是相同的,所以一种语言中的复杂示例可以直接转换到.NET语言套件的其他语言中。
2. .NET正则表达式风格
.NET采用传统的NFA正则表达式引擎,因此之前关于NFA的重要知识都适用。下面的表格总结了.NET的正则表达式风格:
| 类别 | 具体内容 |
|---|---|
| 字符简写 |
\a
[\b]
\e
\f
\n
\r
\t
\v
\octal
\x##
\u####
\cchar
|
| 字符类和类构造 |
[˙˙˙]
[^˙˙˙]
点号(有时匹配任意字符) 类简写:
\w
\d
\s
\W
\D
\S
Unicode属性和块:
\p{Prop}
\P{Prop}
|
| 锚点和零宽测试 |
行/字符串开始:
^
\A
行/字符串结束:
$
\z
\Z
前一个匹配结束位置:
\G
单词边界:
\b
\B
环视:
(?=˙˙˙)
(?!˙˙˙)
(?<=˙˙˙)
(?<!˙˙˙)
|
| 注释和模式修改 |
模式修改:
(?mods - mods)
允许的修改符:
x
s
m
i
n
模式修改范围:
(?mods - mods:˙˙˙)
注释:
(?#˙˙˙)
|
| 分组、捕获、条件和控制 |
捕获括号:
(˙˙˙)
\1
\2
… 平衡分组:
(?<name - name>˙˙˙)
命名捕获、反向引用:
(?<name>˙˙˙)
\k<name>
仅分组括号:
(?:˙˙˙)
原子分组:
(?>˙˙˙)
交替:
<
贪婪量词:
*
+
?
{n}
{n,}
{x,y}
懒惰量词:
*?
+?
??
{n}?
{n,}?
{x,y}?
条件:
(?if then<else)
(“if”可以是环视、
(num)
或
(name)
)
|
此外,还有一些额外的注意事项:
-
\b
在字符类中是退格符的简写,在字符类外匹配单词边界。
-
\x##
允许恰好两个十六进制数字,例如
\xFCber
匹配 ‘über’。
-
\u####
允许恰好四个十六进制数字,例如
\u00FCber
匹配 ‘über’,
\u20AC
匹配 ‘€’。
- 从2.0版本开始,.NET Framework的字符类支持类减法,例如
[a - z - [aeiou]]
表示非元音小写ASCII字母。
-
\w
、
\d
和
\s
(及其大写对应项)通常匹配适当的Unicode字符的完整范围,但使用
RegexOptions.ECMAScript
选项时会切换到仅ASCII模式。
3. .NET匹配和正则表达式模式
以下表格列出了.NET的匹配和正则表达式模式:
| RegexOptions选项 |
(?mode)
| 描述 |
|---|---|---|
.Singleline
|
s
| 使点号匹配任何字符 |
.Multiline
|
m
|
扩展
^
和
$
可以匹配的位置
|
.IgnorePatternWhitespace
|
x
| 设置自由间距和注释模式 |
.IgnoreCase
|
i
| 开启不区分大小写的匹配 |
.ExplicitCapture
|
n
|
关闭
(˙˙˙)
的捕获功能,只有
(?<name>˙˙˙)
进行捕获
|
.ECMAScript
|
限制
\w
、
\s
和
\d
仅匹配ASCII字符等
| |
.RightToLeft
| 正则表达式正常应用,但方向相反(从字符串末尾开始向开头移动),但有缺陷 | |
.Compiled
| 提前花费额外时间优化正则表达式,使其应用时匹配更快 |
4. 正则表达式风格的额外说明
-
命名捕获
:.NET支持命名捕获,通过
(?<name>˙˙˙)或(?'name'˙˙˙)语法实现。可以使用\k<name>或\k'name'在正则表达式中反向引用命名捕获的文本。匹配后,可以通过Match对象的Groups(name)属性(C#中为Groups[name])访问命名捕获的文本。在替换字符串中,可以使用${name}序列访问命名捕获的结果。为了允许所有组都能通过数字访问,命名捕获组也会被赋予编号。不过,不建议混合使用普通捕获括号和命名捕获,否则捕获组的编号分配可能会带来重要的后果。 -
条件测试
:
(?if then;else)条件中的if部分可以是任何类型的环视,或者是括号中的捕获组编号或捕获组名称。普通文本(或普通正则表达式)在这个位置会自动被视为正向环视。为了避免歧义,建议使用显式的(?=˙˙˙)。 -
“编译”表达式
:
- 解析 :程序运行中首次遇到正则表达式时,必须检查并将其转换为适合正则表达式引擎实际应用的内部形式。
-
即时编译
:使用
RegexOptions.Compiled选项时,正则表达式引擎会将其编译为低级的MSIL代码,后续可由JIT编译器进一步优化为更快的本机机器代码。这样做会花费更多的时间和内存,但能使正则表达式运行更快。 -
预编译正则表达式
:可以将
Regex对象封装到磁盘上的DLL中,供其他程序通用。
使用
RegexOptions.Compiled
进行即时编译时,在初始启动时间、持续内存使用和正则表达式匹配速度之间存在重要的权衡:
| 指标 |
不使用
RegexOptions.Compiled
|
使用
RegexOptions.Compiled
|
|---|---|---|
| 启动时间 | 更快 | 更慢(慢60倍) |
| 内存使用 | 低 | 高(每个约5 - 15k) |
| 匹配速度 | 不快 | 快达10倍 |
5. 右到左匹配
.NET的
RegexOptions.RightToLeft
选项实现了右到左的匹配。但该选项的语义未明确记录,实际使用中结果可能出人意料。例如,对字符串 ‘123 and 456’ 应用
\d+
,正常匹配结果是 ‘123’,直觉上右到左匹配应是 ‘456’,但实际结果可能不同。所以使用该选项时需自行承担风险。
6. 反斜杠 - 数字歧义
当反斜杠后跟数字时,它可能是八进制转义或反向引用,具体取决于是否指定了
RegexOptions.ECMAScript
选项。如果不想处理这些微妙的差异,可以使用
\k<num>
进行反向引用,或者以零开头的八进制转义(如
\08
)确保其被正确识别。
7. ECMAScript模式
ECMAScript是JavaScript的标准化版本,有自己的正则表达式解析和应用语义。使用
RegexOptions.ECMAScript
选项创建的.NET正则表达式会尝试模仿这些语义。启用该模式时,只有
RegexOptions.IgnoreCase
、
RegexOptions.Multiline
和
RegexOptions.Compiled
可以与之组合,并且
\w
、
\d
和
\s
(及其大写对应项)会切换到仅ASCII匹配模式。
下面是一个简单的mermaid流程图,展示了选择是否使用
RegexOptions.Compiled
的决策过程:
graph TD;
A[正则表达式应用场景] --> B{是否对速度要求高且处理大量文本};
B -- 是 --> C[使用RegexOptions.Compiled];
B -- 否 --> D{是否是简单正则且处理少量文本};
D -- 是 --> E[不使用RegexOptions.Compiled];
D -- 否 --> F[权衡利弊后决定];
通过以上内容,我们对.NET的正则表达式有了更深入的了解,包括其风格、模式、特殊功能以及使用时的注意事项。在实际应用中,我们可以根据具体需求选择合适的模式和选项,以达到最佳的性能和效果。
8. 命名捕获的深入分析
命名捕获是.NET正则表达式的一个强大特性,它允许我们为捕获组指定名称,从而提高代码的可读性和可维护性。以下是关于命名捕获的更多细节:
-
语法选择
:前面提到了
(?<name>˙˙˙)
和
(?'name'˙˙˙)
两种语法,虽然它们功能相同,但
(?<name>˙˙˙)
可能会更常用。例如,在匹配日期的正则表达式中:
(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
,使用这种语法可以清晰地表明每个捕获组的含义。
-
反向引用
:在正则表达式内部,我们可以使用
\k<name>
或
\k'name'
来引用命名捕获的内容。比如,对于正则表达式
(?<word>\w+)\s+\k<word>
,它可以匹配像 “hello hello” 这样重复的单词。
-
结果访问
:匹配完成后,我们可以通过
Match
对象的
Groups
属性来访问命名捕获的结果。在C#中,代码示例如下:
using System;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string input = "2024-09-10";
string pattern = @"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
string year = match.Groups["year"].Value;
string month = match.Groups["month"].Value;
string day = match.Groups["day"].Value;
Console.WriteLine($"Year: {year}, Month: {month}, Day: {day}");
}
}
}
-
编号规则
:为了方便通过数字访问所有捕获组,命名捕获组也会被编号。编号顺序是在所有非命名捕获组之后。例如,正则表达式
(\w)(?<num>\d+)(\s+)中,非命名捕获组(\w)编号为1,命名捕获组(?<num>\d+)编号为3,非命名捕获组(\s+)编号为2。
9. 条件测试的实际应用
条件测试
(?if then;else)
为正则表达式提供了更灵活的匹配逻辑。以下是一些常见的应用场景:
-
基于捕获组的条件判断
:可以根据捕获组的结果来决定后续的匹配逻辑。例如,正则表达式
(?(<num>\d+) \d+; [a-z]+)
,如果前面的
(?<num>\d+)
成功匹配到数字,那么后续会尝试匹配更多的数字;否则,会尝试匹配小写字母。
-
避免歧义
:如前面所述,普通文本在条件测试的
if
部分会自动被视为正向环视。为了避免歧义,建议使用显式的
(?=˙˙˙)
。例如,正则表达式
(?(?=Num) then; else)
明确表示进行正向环视。
10. 编译选项的详细权衡
使用
RegexOptions.Compiled
进行编译时,需要在启动时间、内存使用和匹配速度之间进行权衡。以下是更详细的分析:
| 指标 | 不使用
RegexOptions.Compiled
| 使用
RegexOptions.Compiled
|
| — | — | — |
| 启动时间 | 对于简单正则表达式,启动时间几乎可以忽略不计。例如,在一个小型控制台程序中,使用未编译的正则表达式
\d+
进行匹配,启动时间非常短。 | 编译过程会花费额外的时间,尤其是对于复杂的正则表达式。在一个性能测试中,对一个复杂的正则表达式进行编译,启动时间比未编译时慢了约60倍。 |
| 内存使用 | 内存使用较低,不会占用过多的系统资源。对于频繁创建和销毁正则表达式对象的场景,这是一个优势。 | 每个编译后的正则表达式会占用大约5 - 15k的内存,并且这些内存会在程序运行期间一直被占用,无法释放。 |
| 匹配速度 | 匹配速度相对较慢,尤其是对于需要处理大量文本的场景。例如,在处理一个大型日志文件时,未编译的正则表达式可能会花费较多的时间。 | 匹配速度可以提高多达10倍,对于对性能要求较高的应用程序,如搜索引擎或数据处理系统,使用编译选项可以显著提高效率。 |
以下是一个mermaid流程图,展示了根据不同场景选择是否使用编译选项的决策过程:
graph TD;
A[正则表达式应用场景] --> B{是否需要频繁使用同一正则表达式};
B -- 是 --> C{处理的文本量是否大};
C -- 是 --> D[使用RegexOptions.Compiled];
C -- 否 --> E{对匹配速度要求是否高};
E -- 是 --> D;
E -- 否 --> F[不使用RegexOptions.Compiled];
B -- 否 --> F;
11. 右到左匹配的问题与解决方案
虽然
RegexOptions.RightToLeft
选项提供了右到左匹配的功能,但由于其语义不明确,在使用时可能会遇到各种问题。以下是一些可能的解决方案:
-
手动反转字符串
:如果右到左匹配的结果不符合预期,可以手动反转目标字符串和正则表达式,然后进行正常的左到右匹配。例如,对于字符串 “123 and 456” 和正则表达式
\d+
,可以将字符串反转成 “654 dna 321”,正则表达式反转成
+d\
,然后进行匹配,最后再将匹配结果反转回来。
-
测试和验证
:在使用右到左匹配之前,对各种可能的输入进行测试和验证,确保结果符合预期。可以编写单元测试来覆盖不同的场景,及时发现和解决问题。
12. 反斜杠 - 数字歧义的解决策略
为了避免反斜杠 - 数字歧义带来的问题,可以采用以下策略:
-
使用
\k<num>
进行反向引用
:无论是否使用
RegexOptions.ECMAScript
选项,
\k<num>
都能明确表示反向引用。例如,正则表达式
(?<word>\w+)\s+\k<word>
可以准确匹配重复的单词。
-
以零开头的八进制转义
:使用以零开头的八进制转义(如
\08
)可以确保其被正确识别为八进制转义。
13. ECMAScript模式的兼容性考虑
当使用
RegexOptions.ECMAScript
模式时,需要考虑与其他模式的兼容性。以下是一些注意事项:
-
组合限制
:只有
RegexOptions.IgnoreCase
、
RegexOptions.Multiline
和
RegexOptions.Compiled
可以与
RegexOptions.ECMAScript
组合使用。在编写代码时,需要确保不会使用其他不兼容的选项。
-
字符匹配范围
:
\w
、
\d
和
\s
(及其大写对应项)会切换到仅ASCII匹配模式。如果需要匹配Unicode字符,就不能使用该模式。
通过对以上内容的深入分析,我们可以更全面地掌握.NET正则表达式的各种特性和使用技巧。在实际开发中,根据具体的需求和场景,合理选择正则表达式的模式和选项,能够提高代码的性能和可维护性。
超级会员免费看

被折叠的 条评论
为什么被折叠?



