3 正则表达式类
Regex
Regex 类表示不可变(只读)正则表达式类。它还包含各种静态方法,允许在不显式创建其他类的实例的情况下使用其他正则表达式类。
下面的代码示例创建了 Regex 类的实例并在初始化对象时定义一个简单的正则表达式。请注意,使用了附加的反斜杠作为转义字符,它将 /s 匹配字符类中的反斜杠指定为原义字符。
Visual Basic 复制代码
' Declare object variable of type Regex.
Dim r As Regex
' Create a Regex object and define its regular expression.
r = New Regex("/s2000")
C# 复制代码
// Declare object variable of type Regex.
Regex r;
// Create a Regex object and define its regular expression.
r = new Regex("//s2000");
Match
Match 类表示正则表达式匹配操作的结果。下面的示例使用 Regex 类的 Match 方法返回 Match 类型的对象,以便找到输入字符串中的第一个匹配项。此示例使用 Match 类的 Match.Success 属性来指示是否已找到匹配。
Visual Basic 复制代码
' cCreate a new Regex object.
Dim r As New Regex("abc")
' Find a single match in the input string.
Dim m As Match = r.Match("123abc456")
If m.Success Then
' Print out the character position where a match was found.
' (Character position 3 in this case.)
Console.WriteLine("Found match at position " & m.Index.ToString())
End If
C# 复制代码
// Create a new Regex object.
Regex r = new Regex("abc");
// Find a single match in the string.
Match m = r.Match("123abc456");
if (m.Success)
{
// Print out the character position where a match was found.
// (Character position 3 in this case.)
Console.WriteLine("Found match at position " + m.Index);
}
MatchCollection
MatchCollection 类表示成功的非重叠匹配的序列。该集合为不可变(只读)的,并且没有公共构造函数。MatchCollection 的实例是由 Regex.Matches 方法返回的。
下面的示例使用 Regex 类的 Matches 方法,通过在输入字符串中找到的所有匹配填充 MatchCollection。该示例将此集合复制到一个字符串数组和一个整数数组中,其中字符串数组用以保存每个匹配项,整数数组用以指示每个匹配项的位置。
Visual Basic 复制代码
Dim mc As MatchCollection
Dim results(20) As String
Dim matchposition(20) As Integer
' Create a new Regex object and define the regular expression.
Dim r As New Regex("abc")
' Use the Matches method to find all matches in the input string.
mc = r.Matches("123abc4abcd")
' Loop through the match collection to retrieve all
' matches and positions.
Dim i As Integer
For i = 0 To mc.Count - 1
' Add the match string to the string array.
results(i) = mc(i).Value
' Record the character position where the match was found.
matchposition(i) = mc(i).Index
Next i
C# 复制代码
MatchCollection mc;
String[] results = new String[20];
int[] matchposition = new int[20];
// Create a new Regex object and define the regular expression.
Regex r = new Regex("abc");
// Use the Matches method to find all matches in the input string.
mc = r.Matches("123abc4abcd");
// Loop through the match collection to retrieve all
// matches and positions.
for (int i = 0; i < mc.Count; i++)
{
// Add the match string to the string array.
results[i] = mc[i].Value;
// Record the character position where the match was found.
matchposition[i] = mc[i].Index;
}
GroupCollection
GroupCollection 类表示捕获的组的集合并返回单个匹配中捕获的组的集合。该集合为不可变(只读)的,并且没有公共构造函数。GroupCollection 的实例在 Match.Groups 属性返回的集合中返回。
以下控制台应用程序示例查找并输出由正则表达式捕获的组的数目。有关如何提取组集合的每一成员中的各个捕获项的示例,请参见下面一节的 Capture Collection 示例。
Visual Basic 复制代码
Imports System
Imports System.Text.RegularExpressions
Public Class RegexTest
Public Shared Sub RunTest()
' Define groups "abc", "ab", and "b".
Dim r As New Regex("(a(b))c")
Dim m As Match = r.Match("abdabc")
Console.WriteLine("Number of groups found = " _
& m.Groups.Count.ToString())
End Sub
Public Shared Sub Main()
RunTest()
End Sub
End Class
C# 复制代码
using System;
using System.Text.RegularExpressions;
public class RegexTest
{
public static void RunTest()
{
// Define groups "abc", "ab", and "b".
Regex r = new Regex("(a(b))c");
Match m = r.Match("abdabc");
Console.WriteLine("Number of groups found = " + m.Groups.Count);
}
public static void Main()
{
RunTest();
}
}
该示例产生下面的输出。
Visual Basic 复制代码
Number of groups found = 3
C# 复制代码
Number of groups found = 3
CaptureCollection
CaptureCollection 类表示捕获的子字符串的序列,并且返回由单个捕获组执行的捕获的集合。由于限定符,捕获组可以在单个匹配中捕获多个字符串。Captures 属性(CaptureCollection 类的对象)是作为 Match 和 group 类的成员提供的,以便于对捕获的子字符串的集合的访问。
例如,如果使用正则表达式 ((a(b))c)+(其中 + 限定符指定一个或多个匹配)从字符串 “ abcabcabc ” 中捕获匹配,则子字符串的每一匹配的 Group 的 CaptureCollection 将包含三个成员。
以下控制台应用程序示例使用正则表达式 (Abc)+ 来查找字符串 “ XYZAbcAbcAbcXYZAbcAb ” 中的一个或多个匹配。该示例阐释了使用 Captures 属性来返回多组捕获的子字符串。
Visual Basic 复制代码
Imports System
Imports System.Text.RegularExpressions
Public Class RegexTest
Public Shared Sub RunTest()
Dim counter As Integer
Dim m As Match
Dim cc As CaptureCollection
Dim gc As GroupCollection
' Look for groupings of "Abc".
Dim r As New Regex("(Abc)+")
' Define the string to search.
m = r.Match("XYZAbcAbcAbcXYZAbcAb")
gc = m.Groups
' Print the number of groups.
Console.WriteLine("Captured groups = " & gc.Count.ToString())
' Loop through each group.
Dim i, ii As Integer
For i = 0 To gc.Count - 1
cc = gc(i).Captures
counter = cc.Count
' Print number of captures in this group.
Console.WriteLine("Captures count = " & counter.ToString())
' Loop through each capture in group.
For ii = 0 To counter - 1
' Print capture and position.
Console.WriteLine(cc(ii).ToString() _
& " Starts at character " & cc(ii).Index.ToString())
Next ii
Next i
End Sub
Public Shared Sub Main()
RunTest()
End Sub
End Class
C# 复制代码
using System;
using System.Text.RegularExpressions;
public class RegexTest
{
public static void RunTest()
{
int counter;
Match m;
CaptureCollection cc;
GroupCollection gc;
// Look for groupings of "Abc".
Regex r = new Regex("(Abc)+");
// Define the string to search.
m = r.Match("XYZAbcAbcAbcXYZAbcAb");
gc = m.Groups;
// Print the number of groups.
Console.WriteLine("Captured groups = " + gc.Count.ToString());
// Loop through each group.
for (int i=0; i < gc.Count; i++)
{
cc = gc[i].Captures;
counter = cc.Count;
// Print number of captures in this group.
Console.WriteLine("Captures count = " + counter.ToString());
// Loop through each capture in group.
for (int ii = 0; ii < counter; ii++)
{
// Print capture and position.
Console.WriteLine(cc[ii] + " Starts at character " +
cc[ii].Index);
}
}
}
public static void Main() {
RunTest();
}
}
此示例返回下面的输出结果。
Visual Basic 复制代码
Captured groups = 2
Captures count = 1
AbcAbcAbc Starts at character 3
Captures count = 3
Abc Starts at character 3
Abc Starts at character 6
Abc Starts at character 9
C# 复制代码
Captured groups = 2
Captures count = 1
AbcAbcAbc Starts at character 3
Captures count = 3
Abc Starts at character 3
Abc Starts at character 6
Abc Starts at character 9
组
group 类表示来自单个捕获组的结果。因为 Group 可以在单个匹配中捕获零个、一个或更多的字符串(使用限定符),所以它包含 Capture 对象的集合。因为 Group 继承自 Capture,所以可以直接访问最后捕获的子字符串(Group 实例本身等价于 Captures 属性返回的集合的最后一项)。
Group 的实例是由 Match.Groups(groupnum) 属性返回的,或者在使用 “ (?<groupname>) ” 分组构造的情况下,是由 Match.Groups("groupname") 属性返回的。
下面的代码示例使用嵌套的分组构造来将子字符串捕获到组中。
Visual Basic 复制代码
Dim matchposition(20) As Integer
Dim results(20) As String
' Define substrings abc, ab, b.
Dim r As New Regex("(a(b))c")
Dim m As Match = r.Match("abdabc")
Dim i As Integer = 0
While Not (m.Groups(i).Value = "")
' Copy groups to string array.
results(i) = m.Groups(i).Value
' Record character position.
matchposition(i) = m.Groups(i).Index
i = i + 1
End While
C# 复制代码
int[] matchposition = new int[20];
String[] results = new String[20];
// Define substrings abc, ab, b.
Regex r = new Regex("(a(b))c");
Match m = r.Match("abdabc");
for (int i = 0; m.Groups[i].Value != ""; i++)
{
// Copy groups to string array.
results[i]=m.Groups[i].Value;
// Record character position.
matchposition[i] = m.Groups[i].Index;
}
此示例返回下面的输出结果。
Visual Basic 复制代码
results(0) = "abc" matchposition(0) = 3
results(1) = "ab" matchposition(1) = 3
results(2) = "b" matchposition(2) = 4
C# 复制代码
results[0] = "abc" matchposition[0] = 3
results[1] = "ab" matchposition[1] = 3
results[2] = "b" matchposition[2] = 4
下面的代码示例使用命名的分组构造,从包含 “ DATANAME:VALUE ” 格式的数据的字符串中捕获子字符串,正则表达式通过冒号 “ : ” 拆分数据。
Visual Basic 复制代码
Dim r As New Regex("^(?<name>/w+):(?<value>/w+)")
Dim m As Match = r.Match("Section1:119900")
C# 复制代码
Regex r = new Regex("^(?<name>//w+):(?<value>//w+)");
Match m = r.Match("Section1:119900");
此正则表达式返回下面的输出结果。
Visual Basic 复制代码
m.Groups("name").Value = "Section1"
m.Groups("value").Value = "119900"
C# 复制代码
m.Groups["name"].Value = "Section1"
m.Groups["value"].Value = "119900"
Capture
Capture 类包含来自单个子表达式捕获的结果。
4 正则表达式行为的详细信息
匹配行为
.NET Framework 正则表达式引擎是回溯的正则表达式匹配器,它并入了传统的非确定性有限自动机 (NFA) 引擎(例如 Perl、Python、Emacs 和 Tcl 使用的引擎)。这使其有别于更快的、但功能更有限的纯正则表达式确定性有限自动机 (DFA) 引擎,例如在 awk、egrep 或 lex 中提供的那些引擎。这也使其有别于标准化的、但较慢的 POSIX NFA。
三种正则表达式引擎类型
本节概述了三种引擎类型的优缺点,并解释了 .NET Framework 引擎为什么实现传统的 NFA 匹配器。
DFA 引擎在线性时状态下执行,因为它们不要求回溯(并因此它们永远不测试相同的字符两次)。DFA 引擎还可以确保匹配最长的可能的字符串。但是,因为 DFA 引擎只包含有限的状态,所以它不能匹配具有反向引用的模式;并且因为它不构造显示扩展,所以它不可以捕获子表达式。
传统的 NFA 引擎运行所谓的 “ 贪婪的 ” 匹配回溯算法,以指定顺序测试正则表达式的所有可能的扩展并接受第一个匹配项。因为传统的 NFA 构造正则表达式的特定扩展以获得成功的匹配,所以它可以捕获子表达式匹配和匹配的反向引用。但是,因为传统的 NFA 回溯,所以它可以访问完全相同的状态多次(如果通过不同的路径到达该状态)。因此,在最坏情况下,它的执行速度可能非常慢。因为传统的 NFA 接受它找到的第一个匹配,所以它还可能会导致其他(可能更长)匹配未被发现。
POSIX NFA 引擎与传统的 NFA 引擎类似,不同的一点在于:在它们可以确保已找到了可能的最长的匹配之前,它们将继续回溯。因此,POSIX NFA 引擎的速度慢于传统的 NFA 引擎;并且在使用 POSIX NFA 时,您恐怕不会愿意在更改回溯搜索的顺序的情况下来支持较短的匹配搜索,而非较长的匹配搜索。
程序员更为喜欢传统的 NFA 引擎的原因在于,NFA 引擎与 DFA 或 POSIX NFA 引擎相比更易于表达。尽管在最坏情况下 NFA 引擎的运行速度稍慢,但您可以通过使用降低多义性和限制回溯的模式,控制这些引擎以在线性时或多项式时状态下查找匹配。
.NET Framework 引擎功能 :
在充分利用传统 NFA 引擎优点的基础上,.NET Framework 正则表达式引擎包括了一组完整的构造,让程序员能够操纵回溯引擎。这些构造可被用于更快地找到匹配,或支持特定扩展,而非其他扩展。
其他功能包括:
“ 惰性 ” 限定符:??、*?、+?、{n,m}?。这些惰性限定符指示回溯引擎首先搜索最少数目的重复。与之相反,普通的 “ 贪婪的 ” 限定符首先尝试匹配最大数目的重复。
积极的预测先行。这允许回溯引擎在匹配子表达式后返回到文本中相同的作用点。这对于通过验证起始于相同位置的多个模式来搜索整个文本是很有用的。
消极的预测先行。这增加了只在子表达式匹配失败的情况下才匹配表达式的能力。这对于删改一个搜索特别有用,因为与必须被包括在内的情况的表达式相比,应被排除的情况的表达式通常要简单得多。(例如,编写搜索不以 “ non ” 起始的单词的表达式就很困难)。
条件计算。这允许引擎可以根据以前的子表达式匹配的结果,使用多个替换模式进行搜索。这提供了超越反向引用所允许的、更为强大的功能,例如,当以前在子表达式中捕获了左括号时匹配右括号。
非回溯子表达式(也称作 “ 贪婪 ” 子表达式)。这允许回溯引擎确保子表达式只匹配为该子表达式找到的第一个匹配项,就好像该表达式独立于其包含的表达式运行。如果没有此构造,来自更大的表达式的回溯搜索可能会更改子表达式的行为。
从右到左匹配。这在从右到左而非从左到右搜索的情况下十分有用,或者在从模式的右侧部分开始搜索比从模式的左侧部分开始搜索更为有效的情况下十分有用。
积极的和消极的追溯。类似于预测先行。因为正则表达式引擎允许完全的从右到左匹配,所以正则表达式允许无限制的追溯。
反向引用
反向引用提供查找重复字符组的方便的方法。它们可被认为是再次匹配同一个字符串的快捷指令。
例如,若要查找重复且相邻的字符(如单词 “ tall ” 中的两个 L),可以使用正则表达式 (?<char>/w)/k<char>,该正则表达式使用元字符 /w 来查找任何单个单词的字符。分组构造 (?<char> ) 将元字符括在其中,以强制正则表达式引擎记住子表达式匹配(在此示例中将是任意单个字符),并以名称 “ char ” 保存它。反向引用构造 /k<char> 使引擎对当前字符和以名称 “ char ” 存储的先前匹配字符进行比较。只要单个字符与其前面的字符相同,整个正则表达式就可以找到一个匹配。
要找到重复的全字,您可以修改该分组子表达式,以搜索前面是空格的任何字符组,而不是只搜索任意单个字符。可以用匹配任何字符组的子表达式 /w+ 替换元字符 /w,并使用元字符 /s 匹配字符分组前的空格。这就生成了正则表达式 (?<char>/s/w+)/k<char>,该正则表达式查找任何重复的全字(例如 “ the the ” ),但也会匹配指定字符串的其他重复情况,例如词组 “ the theory ” 中的重复情况。
为验证上述第二种匹配是以单词为边界的,可以将元字符 /b 添加到重复匹配的后面。所生成的正则表达式 (?<char>/s/w+)/k<char>/b 只查找重复的、前面有空格的全字。
分析反向引用
表达式 /1 到 /9 总是指反向引用,而不是八进制代码。多位表达式 /11 和更高位表达式在具有与该号码对应的反向引用时被视作反向引用;否则,它们会被解释为八进制代码(除非起始位是 8 或 9,在这种情况下它们被视为原义的 “ 8 ” 和 “ 9 ” )。如果正则表达式包含对未定义的组成员的反向引用,则它被视作分析错误。如果有多义性问题,可以使用 /k<n> 表示法,该表示法是明确的,并且不会与八进制符号代码混淆;同样,诸如 /xdd 等的十六进制代码也是明确的,并且不会与反向引用混淆。
当 ECMAScript 选项标志被启用时,反向引用行为将稍有不同。有关更多信息,请参见 ECMAScript 与规范化匹配行为。
匹配反向引用
反向引用引用组的最近的定义(当从左到右匹配时,最靠近左侧的定义)。具体地讲,就是当组建立多个捕获时,反向引用引用最近的捕获。例如,(?<1>a)(?<1>/1b)* 使用捕获模式 (a)(ab)(abb) 来匹配 aababb。循环限定符不清除组定义。
如果一个组尚未捕获任何子字符串,则对该组的反向引用是未定义的并且永远不匹配。例如,表达式 /1() 永远不匹配任何字符串,但是表达式 ()/1 匹配空字符串。
回溯
当正则表达式具有可选的或替换的匹配模式时,该正则表达式在其输入字符串的计算的某些点上,可以分支到一个或多个方向以完成所有可能的匹配。如果匹配在引擎搜索的第一个方向不成功,则它必须备份产生分支的输入字符串中的这一位置并尝试其他替换的匹配。
例如,假定设计一个正则表达式来匹配灰色一词的两种拼写形式:gray 和 grey。替换字符 | 用于创建正则表达式 gr(a|e)y,它可以与两种拼法中的任何一种匹配。当该正则表达式应用到输入字符串 greengraygrowngrey 时,假设引擎首先尝试匹配 gray。它与输入字符串中的前两个字符 gr 匹配,遇到 green 中的 e 后匹配失败。它回溯到 r(替换字符前的上一个成功匹配)并尝试匹配 grey。在第二个 e 上匹配失败,引擎继续搜索并将最终匹配两个嵌入的单词 gray 和 grey。
非回溯预测先行和追溯
积极的预测先行和追溯不回溯。也就是说,对其内容的处理方式与对非回溯 (?> ) 组的内容的处理方式相同。
因为预测先行和追溯始终是零宽度的,所以仅当捕获组出现在积极的预测先行和追溯中时,回溯行为才是可见的。例如,表达式 (?=(a*))/1a 永远找不到匹配,因为在预测先行内定义的组 1 占用了所有的字符 “ a ” ,而 /1a 又请求一个 “ a ” 。因为预测先行表达式不是回溯的,所以匹配引擎不会重试具有较少的 “ a ” 的组 1。
有关分组、预测先行和追溯构造的更多信息,请参见分组构造。
限定符和空匹配
限定符 *、+、{n,m}(及其 “ 惰性 ” 对等符号)永远不在空匹配后重复(在匹配了最小数目 n 后)。此规则避免限定符在 m 是无限时进入空匹配上的无限循环(即使 m 不是无限时,该规则也适用)。
例如,(a?)* 匹配字符串 “ aaa ” 并以模式 (a)(a)(a)() 捕获子字符串。注意,由于第四个空捕获使限定符停止重复,因此没有第五个空捕获。
同样,(a/1|(?(1)/1)){0,2} 与空字符串而不是 “ a ” 匹配,因为它从不尝试扩展 ()(a).。{0,2} 限定符只允许在最后一次迭代中有空匹配。与之不同的是,(a/1|(?(1)/1)){2} 实际与 “ a ” 匹配,原因是:它确实会尝试扩展 ()(a);最小迭代次数为 2,可强制引擎在空匹配后重复。
编译和重复使用
默认情况下,正则表达式引擎将正则表达式编译成内部指令序列(这些指令序列是不同于 Microsoft 中间语言 (MSIL) 的高级代码)。当引擎执行正则表达式时,它解释该内部代码。
如果 Regex 对象是通过 RegexOptions.Compiled 选项构造的,它将正则表达式编译成显式 MSIL 代码,而非高级正则表达式内部指令。这使 .NET Framework 的实时 (JIT) 编译器可以将表达式转换为本机代码以获得更高的性能。
但是,生成的 MSIL 不能被卸载。卸载代码的唯一方法是卸载整个应用程序域(也就是说,卸载您的应用程序的所有代码)。实际上,一旦用 RegexOptions.Compiled 选项编译了正则表达式,.NET Framework 就永远不会释放由编译的表达式使用的资源,即使 Regex 对象本身已被释放并进行了垃圾回收。
您必须要注意对您用 RegexOptions.Compiled 选项编译的不同正则表达式的数目进行限制,以避免消耗过多的资源。如果应用程序必须使用较大数量或极大数量的正则表达式,则应该解释每一表达式,而非编译它们。但是,如果重复使用较少数目的正则表达式,则应使用 RegexOptions.Compiled 编译它们以获得更高的性能。另外一个选择是使用预编译的正则表达式。可以将所有表达式编译到一个可再次使用的 DLL。这样就无需在运行时进行编译,同时仍可受益于已编译正则表达式的速度。
为提高性能,正则表达式引擎将所有正则表达式缓存到内存中。这样在每次使用正则表达式时,就无需将正则表达式重新分析成高级字节代码。
线程安全
Regex 类本身是线程安全和不可变的(只读的)。也就是说,Regex 对象可以在任何线程上创建并在线程间共享;可以从任何线程调用匹配方法并且永远不会更改任何全局状态。
但是,应在单个线程上使用由 Regex 返回的结果对象(Match 和 MatchCollection)。尽管其中许多对象是逻辑上不可变的,但这些对象的实现可以延迟一些结果的计算以提高性能;因此,调用方必须序列化对这些对象的访问。
如果需要在多个线程上共享 Regex 结果对象,则通过调用这些对象的同步方法,可以将它们转换成线程安全的实例。除枚举数之外,所有正则表达式类都是线程安全的或者可以通过同步方法转换成线程安全对象。
枚举数是唯一的例外。应用程序必须序列化对集合枚举数的调用。规则为,如果可以在多个线程上同时枚举一个集合,则您应该同步由枚举数遍历的集合的根对象上的枚举数方法。