初学正则时,对于Regex类不熟悉,遇到问题不知道该用哪种方法解决,本文结合一些正则应用的典型应用场景,介绍一下Regex类的基本应用。这里重点进行.NET类的介绍,对于正则的运用,不做深入探讨。
正则的应用最终都是进行模式的匹配,而根据目的的不同,基本上可以分为以下几种应用:验证、提取、替换、分割。结合.NET提供的控件、类以及类的方法,可以很方便的实现这些应用。
以下将结合一些典型的应用场景,对.NET中常见的类、方法及属性进行介绍。本文旨在.NET类基础用法的引导,对于其中涉及到的正则表达式不做深入探讨。本文适合于在.NET平台下使用正则的初学者。 验证的目的是为了判断输入的源字符串是否符合某一规律或规则,根据需求的不同,可能是校验整个源字符串,也可能只是校验其中一个子串。
验证在.NET中一般有两种应用,一种是在验证控件RegularExpressionValidator中,另一种是在程序中。 RegularExpressionValidator是.NET自带的一种客户端验证控件,通过简单的设置,即可完成对某控件输入值的校验。
基本应用语法如下:
对于RegularExpressionValidator控件不做过多介绍,只是说明一下使用时需要注意的几点: 1、 RegularExpressionValidator进行的是客户端验证;
2、 RegularExpressionValidator中正则使用的是JavaScript语法规则;
3、 RegularExpressionValidator控件不能验证输入是否为空。 由于RegularExpressionValidator做的是客户端验证,很容易被跳过,所以在使用RegularExpressionValidator验证的同时,还要做服务器端验证。
RegularExpressionValidator最终是要生成客户端的JavaScript代码进行验证的,所以RegularExpressionValidator使用的正则要求符合JavaScript语法规则,与.NET的几点区别: 1、 不支持逆序环视,也就是(?正则语法规则,只能做有限的格式校验,一些复杂的校验可以通过自己写JavaScript代码来实现,也是很简单的。 程序中的校验基本上就是使用IsMatch方法,验证的对象可能是源字符串的整体,也可能只是其中一个子串。
验证源字符串的整体是否符合某一规则,与使用RegularExpressionValidator时的需求基本上一致,不过由于是在.NET程序中,所以使用的是.NET的语法,比JavaScript中要强大得多。比如验证一个文本框输入的字符串是否符合某一规则,就是一个典型的验证整体的需求。
举例1:验证textBox1输入内容,要求整数部分为0或正整数,小数可有可无,有小数时必须为2位。
Regex reg = newRegex(@"^(?:[1-9][0-9]*|0)(?:\.[0-9]{2})?$");
if (reg.IsMatch(textBox1.Text))
{
richTextBox2.Text = "输入格式正确!";
}
else
{
richTextBox2.Text = "输入格式错误!";
}
由于是对源字符串的整体进行验证,所以"^"和"$"是必不可少的。否则验证的结果可能是错误的,比如正则表达式"(?:[1-9][0-9]*|0)(?:\.[0-9]{2})?",在输入"0.123"时是可以匹配成功的,匹配结果为"0.12",此时正则只起到了匹配的作用,没有起到验证的作用。
验证源字符串的局部是否符合某一规则,就是对于源字符串中子串的校验,通常是用来判断源字符串中是否包含,或是不包含符合某一规律的子串,作用类似于string类中的IndexOf。
举例2(参考问两个正则表达式):
数据
:
1985aaa1985bb
bcae1958fiefadf1955fef
atijc1944cvkd
df2564isdjfef2564d
abc1234def5678ghi5678jkl
需求1:验证字符串中任意位置出现的连续四个数字在整个字符串中是否有重复,有重复为True,无重复为False。
以上数据需求1的验证结果为True的应为:
1985aaa1985bb
df2564isdjfef2564d
abc1234def5678ghi5678jkl
因为需求中指明是任意位置的连续4个数字是否有重复,所以在找到重复前,要遍历源字符串中每一个位置时行验证,这样就不能限定开始标识符"^";而在匹配过程中,除非一直到结尾仍找不到重复,否则只要匹配到有重复的位置就可以了,这样也不需要结束标识符"$",所以这是典型的对字符串的子串行验证的需求。
代码实现:
string[] test = newstring[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" };
Regex reg = newRegex(@"(\d{4})(?:(?!\1).)*\1");
foreach (string s in test)
{
richTextBox2.Text += "源字符串: " + s.PadRight(25, ' ') + "验证结果: " + reg.IsMatch(s) + "\n";
}
/*--------输出--------
源字符串: 1985aaa1985bb 验证结果: True
源字符串: bcae1958fiefadf1955fef 验证结果: False
源字符串: atijc1944cvkd 验证结果: False
源字符串: df2564isdjfef2564d 验证结果: True
源字符串: abc1234def5678ghi5678jkl 验证结果: True
*/
由于涉及到了重复问题,所以这里用到了反向引用,对于反向引用的细节,可以参考正则基础之--反向引用。
需求2:验证字符串中第一个出现的连续4个数字是否有重复,有重复为True,无重复为False。
以上数据需求2的验证结果为True的应为:
1985aaa1985bb
df2564isdjfef2564d
因为需求中指明是第一个是否有重复,所以需要有开始标识符"^",来保证是第一个出现的连续4个数字;而在匹配过程中,除非一直到结尾仍找不到重复,否则只要匹配到有重复的位置就可以了,这样也不需要结束标识符"$",所以这仍是对字符串的子串行验证的需求,只不过相对于需求1来说,加了一个限定条件。
代码实现:
string[] test = newstring[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" };
Regex reg = newRegex(@"^(?:(?!\d{4}).)*(\d{4})(?:(?!\1).)*\1");
foreach (string s in test)
{
richTextBox2.Text += "源字符串: " + s.PadRight(25, ' ') + "验证结果: " + reg.IsMatch(s) + "\n";
}
/*--------输出--------
源字符串: 1985aaa1985bb 验证结果: True
源字符串: bcae1958fiefadf1955fef 验证结果: False
源字符串: atijc1944cvkd 验证结果: False
源字符串: df2564isdjfef2564d 验证结果: True
源字符串: abc1234def5678ghi5678jkl 验证结果: False
*/ 提取主要是从源字符串中,取得一个或多个符合某一规律或规则的子串。一般来说,在字符串处理中,提取应用比较广泛。提取时用得比较多的是Match()和Matches()方法,以及结果处理时Match类和MatchCollection类的一些方法,有时也会用到Capture类的一些方法。 当需要提取的内容只有一个,或是只需要获取第一次成功匹配的内容时,可以使用Match()方法。当使用Match()方法时,只要在某一位置匹配成功,就不再继续尝试匹配,并返回一个Match类型的对象。
举例:提取姓名
源字符串:姓名:张三,性别:男,年龄:25
代码实现:
string test = "姓名:张三,性别:男,年龄:25";
Regex reg = newRegex(@"(?标签的链接和文本。
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?is)[^""\s>]*)\ 1[^>]*>(?(?:(?!");
Match m = reg.Match(test);
if(m.Success)
{
richTextBox2.Text += m.Groups["url"].Value + "\n"; //链接
richTextBox2.Text += m.Groups["text"].Value + "\n"; //文本
}
/*--------输出--------
www.test1.com
测试一
*/
对于捕获组捕获结果的引用,还有一种方式
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?is)[^""\s>]*)\ 1[^>]*>(?(?:(?!"); Match m = reg.Match(test);
if(m.Success)
{
richTextBox2.Text += m.Result("${url}") + "\n"; //链接
richTextBox2.Text += m.Result("${text}") + "\n"; //文本
}
/*--------输出--------
www.test1.com
测试一
*/
这两种方法获取的结果都是一样的,使用哪一种,通常根据个人习惯而定。 当需要提取的内容有多个,并且需要提取所有符合规律的子串时,可以使用Matches()方法。当使用Matches()方法时,需要遍历源字符串的每一个位置进行尝试匹配,匹配结束返回一个MatchCollection类型的对象。
对于1.2.1节提到的提取链接和文本的例子,如果提取的是全部链接和文本,而不仅仅是第一个时,可以使用Matches()方法。
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?is)[^""\s>]*)\ 1[^>]*>(?(?:(?!");
MatchCollection mc = reg.Matches(test);
foreach(Match m in mc)
{
richTextBox2.Text += m.Groups["url"].Value + "\n"; //链接
richTextBox2.Text += m.Groups["text"].Value + "\n"; //文本
}
/*--------输出--------
www.test1.com
测试一
www.test2.com
测试二
*/
对于Matches(),某些场景下,也可以通过Count属性,用做统计符合某一规律的子串出现的次数,例如统计字符串中独立的"3"出现的次数。
string test = "137,1,33,4,3,6,21,3,35,93,2,98";
Regex reg = newRegex(@"\b3\b");
int count = reg.Matches(test).Count; //2
这时候关心的只是匹配成功的次数,对于匹配的内容是不关心的,所以实现这种需求时,正则应尽量简洁,能达到目的即可,这样可以加快匹配效率,减少资源占用。比如上面的提取链接的源字符串中,统计标签出现的次数,一般来说,如下代码即可达到目的了。
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?i)正则表达式整体匹配一次时,其中的捕获组可能会匹配多次。
举例:
源字符串:abcsadf jfdsajf abcsadf
需求:提取出每个region的属性和属性值,按region分组。
对于这个需求,可以先提取出所有region,再对每个region标签提取它的属性和属性值,但这样做比较麻烦,可以考虑在一个正则表达式中提取。由于属性的个数是不固定的,所以不能用固定个数的量词来匹配属性对,正则可以写为
(?is)[^\s=]+)=(?[^\s>]+)\s*)+>
此时如果还用Groups来取匹配结果时,由于Groups只保留最后一次匹配结果,所以只能取到最后一次匹配成功的子串。这时就要用到Captures属性。
string test = "abcsadf jfdsajf abcsadf ";
MatchCollection mc = Regex.Matches(test, @"(?is)[^\s=]+)=(?[^\s>]+)\s* )+>");
for (int i = 0; i 表达式中,很难进行分次匹配,这时Capture就比较有用了。 替换主要是从源字符串中,将符合某一规律或规则的子串替换为其它内容。一般来说,在字符串处理中,替换应用也比较广泛。替换主要是用Replace()方法,在一些替换规则复杂的应用场景中,也可能会用到委托方法。 替换的目的很明确,只要找出待替换子串的规律,替换为目标子串就可以了。
举例1: 源字符串: abc
需求:将相对地址替换为绝对地址,已经是绝对地址的不替换。
string test = " abc ";
Regex reg = newRegex(@"(?i)(?]+\1)");
string result = reg.Replace(test, "http://www.test.com");
/*--------输出--------
abc
*/
这里需要说明的是,在.NET中只提供了一个Replace()方法,没有提供类似于Java中的replaceAll()和replaceFirst()两种方法,所以如果在.NET中只替换第一次出现的符合规律的子串时,需要在正则表达式中处理。
举例2:
源字符串:abc123def123ghi
需求:将第一次出现的"123"替换为空,其余位置不替换。
string test = "abc123def123ghi";
Regex reg = newRegex(@"(?s)^((?:(?!123).)*)123");
string result = reg.Replace(test, "$1");
/*--------输出--------
abcdef123ghi
*/
这时用"^"限定只替换第一次出现的子串,由于绝大多数正则引擎都对"^"进行了优化,所以正则表达式在位置0处匹配成功或失败后,将不再对其它位置进行尝试匹配。 对于一些比较复杂的替换规则,可能会用到委托方法,由于这种应用属于比较典型的一种应用,所以将在后面的文章中单独进行介绍。 分割就是用符合某一规律的子串,将源字符串分割成一个数组,主要用到的是Split()方法。由于Regex的Split()方法中,并没有提供类似于string的Split()方法的StringSplitOptions.RemoveEmptyEntries参数,而如果符合规律的子串出现在开头或结尾时,会出现不需要的空串,这时需要在正则表达式中进行一下处理。
举例1:
源字符串:汉字123文字english
需求:按英文单词和非英文单词进行分割(英文单词包括由a-z,A-Z,0-9,_组成的子串)。
string str = "汉字123文字english";
string[] result = Regex.Split(str, @"(?正则就用技巧范畴的了。
举例2:
源字符串:Left(姓名,1),Left(姓名,1) , Left (姓名,1)
需求:以不在"()"内的","进行分割。
string test = "Left(姓名,1),Left(姓名,1) , Left (姓名,1)";
Regex reg = newRegex(@"(?正则的Split()方法时,有一点需求注意,那就是如果正则中出现了捕获组,那么捕获组匹配到的内容也会保存到分割结果中。
以下举例不做详细说明,看下结果基本上就明白了。
string test = "aa11cc22ee";
string[] temp = Regex.Split(test, @"[0-9]+(]*>)");
foreach (string s in temp)
{
richTextBox2.Text += s + "\n";
}
/*--------输出--------
aa
cc
ee
*/
如果不想把捕获组匹配到的内容也存入结果,可以使用非捕获组。
string test = "aa11cc22ee";
string[] temp = Regex.Split(test, @"[0-9]+(?:]*>)");
foreach (string s in temp)
{
richTextBox2.Text += s + "\n";
}
/*--------输出--------
aa
cc
ee
*/ 这里介绍一些可能涉及到的一些.NET中的正则扩展应用。 有时需要根据一些变量动态生成正则表达式,这时如果变量中含有正则中的元字符,会被解析成元字符,就可能会导致正则编译不通过,从而导致程序异常,需要对变量进行转义处理。Regex. Escape()方法通过替换为转义码来转义最小的字符集(\、*、+、?、|、{、[、(、)、^、$、.、# 和空白)。
比如根据用户输入的id取相应的div标签,id中没有元字符时,可以取得正确结果。
string test = "abcdef";
string[] ids = newstring[] { "test1", "test2" };
foreach (string id in ids)
{
Regex reg = newRegex(@"(?is)]*>(?:(?!");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
richTextBox2.Text += m.Value + "\n";
}
}
/*--------输出--------
abc
def
*/
但是如果输入的id中一旦出现未经转义的元字符,如"abc(",就会抛类似于下面的异常。
正在分析"(?is)]*>(?:(?!"- ) 不足。
此时可以用Escape()方法对输入的变量进行转义处理。
string test = "abcdef";
string[] ids = newstring[] { "test1", "test2", "abc(" };
foreach (string id in ids)
{
Regex reg = newRegex(@"(?is)]*>(?:(?!");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
richTextBox2.Text += m.Value + "\n";
}
}
/*--------输出--------
abc
def
*/
使用Escape()方法转义后,就可以得到正确的结果,而不会抛异常了。 .NET中一些Regex类的常见方法都提供了相应的静态方法,可以不显式的声明Regex对象,而直接调用相应的方法,书写起来更方便,代码更简洁、易读。
比如替换IP地址最后一节为"*",只需一行代码。
string result = Regex.Replace("10.27.123.12", @"\d+$", "*"); //10.27.123.*
静态方法每次调用都会创建一个临时的Regex对象,使用之后释放,所以每次调用静态方法时,都会重新编译,而这将会降低执行效率。因此在循环或是频繁调用的方法中,不适合使用静态方法,而需要进行显式声明Regex对象。
但是对于一些只调用一次,或是对执行效率没有要求的场景下,静态方法则是很不错的选择。 Regex..::.Split(String) 方法与 String..::.Split(array[]()[]) 方法类似,但前者是在由正则表达式而不是一组字符确定的分隔符处拆分字符串。将以尽可能多的次数拆分该字符串。如果找不到分隔符,返回值将包含一个值为原始 input 参数字符串的元素。 如果多个匹配项彼此相邻,则会在数组中插入空字符串。例如,如果用一个连字符来拆分字符串,将会导致返回的数组在两个相邻的连字符所在的位置包括一个空字符串,如下面的代码所示。 Visual Basic 复制代码 Dim regex As Regex = New Regex("-") ' Split on hyphens. Dim substrings() As String = regex.Split("plum--pear") For Each match As String In substrings Console.WriteLine("'{0}'", match) Next ' The method writes the following to the console: ' 'plum' ' '' ' 'pear' C# 复制代码 Regex regex = new Regex("-"); // Split on hyphens. string[] substrings = regex.Split("plum--pear"); foreach (string match in substrings) { Console.WriteLine("'{0}'", match); } // The method writes the following to the console: // 'plum' // '' // 'pear' 如果将捕获括号用于 Regex.Split 表达式中,则任何捕获的文本将包含在生成的字符串数组中。例如,如果要通过在捕获括号中放入一个连字符的方式来拆分字符串"plum-pear",则会向返回的数组添加一个包含该连字符的字符串元素。 Visual Basic 复制代码 Dim regex As Regex = New Regex("(-)") ' Split on hyphens. Dim substrings() As String = regex.Split("plum-pear") For Each match As String In substrings Console.WriteLine("'{0}'", match) Next ' The method writes the following to the console: ' 'plum' ' '-' ' 'pear' C# 复制代码 Regex regex = new Regex("(-)"); // Split on hyphens. string[] substrings = regex.Split("plum-pear"); foreach (string match in substrings) { Console.WriteLine("'{0}'", match); } // The method writes the following to the console: // 'plum' // '-' // 'pear' 但是,如果正则表达式模式包含多组捕获括号,则此方法的行为取决于 .NET Framework 的版本。在 .NET Framework 1.0 和 1.1 版中,如果在第一组捕获括号中未找到匹配项,则在返回的数组中不会包含从其他捕获括号中捕获的文本。在 .NET Framework 2.0 版中,所有捕获的文本还添加到返回的数组中。例如,下面的代码使用两组捕获括号从日期字符串中提取日期元素(包括日期分隔符)。第一组捕获括号捕获连字符,而第二组捕获括号捕获左斜线。如果已编译示例代码并在 .NET Framework 1.0 或 1.1 中运行,则它不包括斜线;如果已编译示例代码并在 .NET Framework 2.0 中运行,则它包括斜线。 Visual Basic 复制代码 Dim input As String = "07/14/2007" Dim pattern As String = "(-)|(/)" Dim regex As Regex = New Regex(pattern) For Each result As String In regex.Split(input) Console.WriteLine("'{0}'", result) Next ' In .NET 1.0 and 1.1, the method returns an array of ' 3 elements, as follows: ' '07' ' '14' ' '2007' ' ' In .NET 2.0, the method returns an array of ' 5 elements, as follows: ' '07' ' '/' ' '14' ' '/' ' '2007' C# 复制代码 string input = @"07/14/2007"; string pattern = @"(-)|(/)"; Regex regex = new Regex(pattern); foreach (string result in regex.Split(input)) { Console.WriteLine("'{0}'", result); } // Under .NET 1.0 and 1.1, the method returns an array of // 3 elements, as follows: // '07' // '14' // '2007' // // Under .NET 2.0, the method returns an array of // 5 elements, as follows: // '07' // '/' // '14' // '/' // '2007' 如果正则表达式可以匹配空字符串,则 Split(String) 会将字符串拆分为由单字符组成的字符串的数组,因为每个位置都有空字符串分隔符。例如: Visual Basic 复制代码 Dim input As String = "characters" Dim regex As New Regex("") Dim substrings() As String = regex.Split(input) Console.Write("{") For ctr As Integer = 0 to substrings.Length - 1 Console.Write(substrings(ctr)) If ctr < substrings.Length - 1 Then Console.Write(", ") Next Console.WriteLine("}") ' The example produces the following output: ' {, c, h, a, r, a, c, t, e, r, s, } C# 复制代码 string input = "characters"; Regex regex = new Regex(""); string[] substrings = regex.Split(input); Console.Write("{"); for(int ctr = 0; ctr < substrings.Length; ctr++) { Console.Write(substrings[ctr]); if (ctr < substrings.Length - 1) Console.Write(", "); } Console.WriteLine("}"); // The example produces the following output: // {, c, h, a, r, a, c, t, e, r, s, } 请注意,返回数组的开头和结尾还包括空字符串。
正则的应用最终都是进行模式的匹配,而根据目的的不同,基本上可以分为以下几种应用:验证、提取、替换、分割。结合.NET提供的控件、类以及类的方法,可以很方便的实现这些应用。
以下将结合一些典型的应用场景,对.NET中常见的类、方法及属性进行介绍。本文旨在.NET类基础用法的引导,对于其中涉及到的正则表达式不做深入探讨。本文适合于在.NET平台下使用正则的初学者。 验证的目的是为了判断输入的源字符串是否符合某一规律或规则,根据需求的不同,可能是校验整个源字符串,也可能只是校验其中一个子串。
验证在.NET中一般有两种应用,一种是在验证控件RegularExpressionValidator中,另一种是在程序中。 RegularExpressionValidator是.NET自带的一种客户端验证控件,通过简单的设置,即可完成对某控件输入值的校验。
基本应用语法如下:
对于RegularExpressionValidator控件不做过多介绍,只是说明一下使用时需要注意的几点: 1、 RegularExpressionValidator进行的是客户端验证;
2、 RegularExpressionValidator中正则使用的是JavaScript语法规则;
3、 RegularExpressionValidator控件不能验证输入是否为空。 由于RegularExpressionValidator做的是客户端验证,很容易被跳过,所以在使用RegularExpressionValidator验证的同时,还要做服务器端验证。
RegularExpressionValidator最终是要生成客户端的JavaScript代码进行验证的,所以RegularExpressionValidator使用的正则要求符合JavaScript语法规则,与.NET的几点区别: 1、 不支持逆序环视,也就是(?正则语法规则,只能做有限的格式校验,一些复杂的校验可以通过自己写JavaScript代码来实现,也是很简单的。 程序中的校验基本上就是使用IsMatch方法,验证的对象可能是源字符串的整体,也可能只是其中一个子串。
验证源字符串的整体是否符合某一规则,与使用RegularExpressionValidator时的需求基本上一致,不过由于是在.NET程序中,所以使用的是.NET的语法,比JavaScript中要强大得多。比如验证一个文本框输入的字符串是否符合某一规则,就是一个典型的验证整体的需求。
举例1:验证textBox1输入内容,要求整数部分为0或正整数,小数可有可无,有小数时必须为2位。
Regex reg = newRegex(@"^(?:[1-9][0-9]*|0)(?:\.[0-9]{2})?$");
if (reg.IsMatch(textBox1.Text))
{
richTextBox2.Text = "输入格式正确!";
}
else
{
richTextBox2.Text = "输入格式错误!";
}
由于是对源字符串的整体进行验证,所以"^"和"$"是必不可少的。否则验证的结果可能是错误的,比如正则表达式"(?:[1-9][0-9]*|0)(?:\.[0-9]{2})?",在输入"0.123"时是可以匹配成功的,匹配结果为"0.12",此时正则只起到了匹配的作用,没有起到验证的作用。
验证源字符串的局部是否符合某一规则,就是对于源字符串中子串的校验,通常是用来判断源字符串中是否包含,或是不包含符合某一规律的子串,作用类似于string类中的IndexOf。
举例2(参考问两个正则表达式):
数据
:
1985aaa1985bb
bcae1958fiefadf1955fef
atijc1944cvkd
df2564isdjfef2564d
abc1234def5678ghi5678jkl
需求1:验证字符串中任意位置出现的连续四个数字在整个字符串中是否有重复,有重复为True,无重复为False。
以上数据需求1的验证结果为True的应为:
1985aaa1985bb
df2564isdjfef2564d
abc1234def5678ghi5678jkl
因为需求中指明是任意位置的连续4个数字是否有重复,所以在找到重复前,要遍历源字符串中每一个位置时行验证,这样就不能限定开始标识符"^";而在匹配过程中,除非一直到结尾仍找不到重复,否则只要匹配到有重复的位置就可以了,这样也不需要结束标识符"$",所以这是典型的对字符串的子串行验证的需求。
代码实现:
string[] test = newstring[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" };
Regex reg = newRegex(@"(\d{4})(?:(?!\1).)*\1");
foreach (string s in test)
{
richTextBox2.Text += "源字符串: " + s.PadRight(25, ' ') + "验证结果: " + reg.IsMatch(s) + "\n";
}
/*--------输出--------
源字符串: 1985aaa1985bb 验证结果: True
源字符串: bcae1958fiefadf1955fef 验证结果: False
源字符串: atijc1944cvkd 验证结果: False
源字符串: df2564isdjfef2564d 验证结果: True
源字符串: abc1234def5678ghi5678jkl 验证结果: True
*/
由于涉及到了重复问题,所以这里用到了反向引用,对于反向引用的细节,可以参考正则基础之--反向引用。
需求2:验证字符串中第一个出现的连续4个数字是否有重复,有重复为True,无重复为False。
以上数据需求2的验证结果为True的应为:
1985aaa1985bb
df2564isdjfef2564d
因为需求中指明是第一个是否有重复,所以需要有开始标识符"^",来保证是第一个出现的连续4个数字;而在匹配过程中,除非一直到结尾仍找不到重复,否则只要匹配到有重复的位置就可以了,这样也不需要结束标识符"$",所以这仍是对字符串的子串行验证的需求,只不过相对于需求1来说,加了一个限定条件。
代码实现:
string[] test = newstring[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" };
Regex reg = newRegex(@"^(?:(?!\d{4}).)*(\d{4})(?:(?!\1).)*\1");
foreach (string s in test)
{
richTextBox2.Text += "源字符串: " + s.PadRight(25, ' ') + "验证结果: " + reg.IsMatch(s) + "\n";
}
/*--------输出--------
源字符串: 1985aaa1985bb 验证结果: True
源字符串: bcae1958fiefadf1955fef 验证结果: False
源字符串: atijc1944cvkd 验证结果: False
源字符串: df2564isdjfef2564d 验证结果: True
源字符串: abc1234def5678ghi5678jkl 验证结果: False
*/ 提取主要是从源字符串中,取得一个或多个符合某一规律或规则的子串。一般来说,在字符串处理中,提取应用比较广泛。提取时用得比较多的是Match()和Matches()方法,以及结果处理时Match类和MatchCollection类的一些方法,有时也会用到Capture类的一些方法。 当需要提取的内容只有一个,或是只需要获取第一次成功匹配的内容时,可以使用Match()方法。当使用Match()方法时,只要在某一位置匹配成功,就不再继续尝试匹配,并返回一个Match类型的对象。
举例:提取姓名
源字符串:姓名:张三,性别:男,年龄:25
代码实现:
string test = "姓名:张三,性别:男,年龄:25";
Regex reg = newRegex(@"(?标签的链接和文本。
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?is)[^""\s>]*)\ 1[^>]*>(?(?:(?!");
Match m = reg.Match(test);
if(m.Success)
{
richTextBox2.Text += m.Groups["url"].Value + "\n"; //链接
richTextBox2.Text += m.Groups["text"].Value + "\n"; //文本
}
/*--------输出--------
www.test1.com
测试一
*/
对于捕获组捕获结果的引用,还有一种方式
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?is)[^""\s>]*)\ 1[^>]*>(?(?:(?!"); Match m = reg.Match(test);
if(m.Success)
{
richTextBox2.Text += m.Result("${url}") + "\n"; //链接
richTextBox2.Text += m.Result("${text}") + "\n"; //文本
}
/*--------输出--------
www.test1.com
测试一
*/
这两种方法获取的结果都是一样的,使用哪一种,通常根据个人习惯而定。 当需要提取的内容有多个,并且需要提取所有符合规律的子串时,可以使用Matches()方法。当使用Matches()方法时,需要遍历源字符串的每一个位置进行尝试匹配,匹配结束返回一个MatchCollection类型的对象。
对于1.2.1节提到的提取链接和文本的例子,如果提取的是全部链接和文本,而不仅仅是第一个时,可以使用Matches()方法。
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?is)[^""\s>]*)\ 1[^>]*>(?(?:(?!");
MatchCollection mc = reg.Matches(test);
foreach(Match m in mc)
{
richTextBox2.Text += m.Groups["url"].Value + "\n"; //链接
richTextBox2.Text += m.Groups["text"].Value + "\n"; //文本
}
/*--------输出--------
www.test1.com
测试一
www.test2.com
测试二
*/
对于Matches(),某些场景下,也可以通过Count属性,用做统计符合某一规律的子串出现的次数,例如统计字符串中独立的"3"出现的次数。
string test = "137,1,33,4,3,6,21,3,35,93,2,98";
Regex reg = newRegex(@"\b3\b");
int count = reg.Matches(test).Count; //2
这时候关心的只是匹配成功的次数,对于匹配的内容是不关心的,所以实现这种需求时,正则应尽量简洁,能达到目的即可,这样可以加快匹配效率,减少资源占用。比如上面的提取链接的源字符串中,统计标签出现的次数,一般来说,如下代码即可达到目的了。
string test = "abc测试一def测试二ghi";
Regex reg = newRegex(@"(?i)正则表达式整体匹配一次时,其中的捕获组可能会匹配多次。
举例:
源字符串:abcsadf jfdsajf abcsadf
需求:提取出每个region的属性和属性值,按region分组。
对于这个需求,可以先提取出所有region,再对每个region标签提取它的属性和属性值,但这样做比较麻烦,可以考虑在一个正则表达式中提取。由于属性的个数是不固定的,所以不能用固定个数的量词来匹配属性对,正则可以写为
(?is)[^\s=]+)=(?[^\s>]+)\s*)+>
此时如果还用Groups来取匹配结果时,由于Groups只保留最后一次匹配结果,所以只能取到最后一次匹配成功的子串。这时就要用到Captures属性。
string test = "abcsadf jfdsajf abcsadf ";
MatchCollection mc = Regex.Matches(test, @"(?is)[^\s=]+)=(?[^\s>]+)\s* )+>");
for (int i = 0; i 表达式中,很难进行分次匹配,这时Capture就比较有用了。 替换主要是从源字符串中,将符合某一规律或规则的子串替换为其它内容。一般来说,在字符串处理中,替换应用也比较广泛。替换主要是用Replace()方法,在一些替换规则复杂的应用场景中,也可能会用到委托方法。 替换的目的很明确,只要找出待替换子串的规律,替换为目标子串就可以了。
举例1: 源字符串: abc
需求:将相对地址替换为绝对地址,已经是绝对地址的不替换。
string test = " abc ";
Regex reg = newRegex(@"(?i)(?]+\1)");
string result = reg.Replace(test, "http://www.test.com");
/*--------输出--------
abc
*/
这里需要说明的是,在.NET中只提供了一个Replace()方法,没有提供类似于Java中的replaceAll()和replaceFirst()两种方法,所以如果在.NET中只替换第一次出现的符合规律的子串时,需要在正则表达式中处理。
举例2:
源字符串:abc123def123ghi
需求:将第一次出现的"123"替换为空,其余位置不替换。
string test = "abc123def123ghi";
Regex reg = newRegex(@"(?s)^((?:(?!123).)*)123");
string result = reg.Replace(test, "$1");
/*--------输出--------
abcdef123ghi
*/
这时用"^"限定只替换第一次出现的子串,由于绝大多数正则引擎都对"^"进行了优化,所以正则表达式在位置0处匹配成功或失败后,将不再对其它位置进行尝试匹配。 对于一些比较复杂的替换规则,可能会用到委托方法,由于这种应用属于比较典型的一种应用,所以将在后面的文章中单独进行介绍。 分割就是用符合某一规律的子串,将源字符串分割成一个数组,主要用到的是Split()方法。由于Regex的Split()方法中,并没有提供类似于string的Split()方法的StringSplitOptions.RemoveEmptyEntries参数,而如果符合规律的子串出现在开头或结尾时,会出现不需要的空串,这时需要在正则表达式中进行一下处理。
举例1:
源字符串:汉字123文字english
需求:按英文单词和非英文单词进行分割(英文单词包括由a-z,A-Z,0-9,_组成的子串)。
string str = "汉字123文字english";
string[] result = Regex.Split(str, @"(?正则就用技巧范畴的了。
举例2:
源字符串:Left(姓名,1),Left(姓名,1) , Left (姓名,1)
需求:以不在"()"内的","进行分割。
string test = "Left(姓名,1),Left(姓名,1) , Left (姓名,1)";
Regex reg = newRegex(@"(?正则的Split()方法时,有一点需求注意,那就是如果正则中出现了捕获组,那么捕获组匹配到的内容也会保存到分割结果中。
以下举例不做详细说明,看下结果基本上就明白了。
string test = "aa11cc22ee";
string[] temp = Regex.Split(test, @"[0-9]+(]*>)");
foreach (string s in temp)
{
richTextBox2.Text += s + "\n";
}
/*--------输出--------
aa
cc
ee
*/
如果不想把捕获组匹配到的内容也存入结果,可以使用非捕获组。
string test = "aa11cc22ee";
string[] temp = Regex.Split(test, @"[0-9]+(?:]*>)");
foreach (string s in temp)
{
richTextBox2.Text += s + "\n";
}
/*--------输出--------
aa
cc
ee
*/ 这里介绍一些可能涉及到的一些.NET中的正则扩展应用。 有时需要根据一些变量动态生成正则表达式,这时如果变量中含有正则中的元字符,会被解析成元字符,就可能会导致正则编译不通过,从而导致程序异常,需要对变量进行转义处理。Regex. Escape()方法通过替换为转义码来转义最小的字符集(\、*、+、?、|、{、[、(、)、^、$、.、# 和空白)。
比如根据用户输入的id取相应的div标签,id中没有元字符时,可以取得正确结果。
string test = "abcdef";
string[] ids = newstring[] { "test1", "test2" };
foreach (string id in ids)
{
Regex reg = newRegex(@"(?is)]*>(?:(?!");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
richTextBox2.Text += m.Value + "\n";
}
}
/*--------输出--------
abc
def
*/
但是如果输入的id中一旦出现未经转义的元字符,如"abc(",就会抛类似于下面的异常。
正在分析"(?is)]*>(?:(?!"- ) 不足。
此时可以用Escape()方法对输入的变量进行转义处理。
string test = "abcdef";
string[] ids = newstring[] { "test1", "test2", "abc(" };
foreach (string id in ids)
{
Regex reg = newRegex(@"(?is)]*>(?:(?!");
MatchCollection mc = reg.Matches(test);
foreach (Match m in mc)
{
richTextBox2.Text += m.Value + "\n";
}
}
/*--------输出--------
abc
def
*/
使用Escape()方法转义后,就可以得到正确的结果,而不会抛异常了。 .NET中一些Regex类的常见方法都提供了相应的静态方法,可以不显式的声明Regex对象,而直接调用相应的方法,书写起来更方便,代码更简洁、易读。
比如替换IP地址最后一节为"*",只需一行代码。
string result = Regex.Replace("10.27.123.12", @"\d+$", "*"); //10.27.123.*
静态方法每次调用都会创建一个临时的Regex对象,使用之后释放,所以每次调用静态方法时,都会重新编译,而这将会降低执行效率。因此在循环或是频繁调用的方法中,不适合使用静态方法,而需要进行显式声明Regex对象。
但是对于一些只调用一次,或是对执行效率没有要求的场景下,静态方法则是很不错的选择。 Regex..::.Split(String) 方法与 String..::.Split(array[]()[]) 方法类似,但前者是在由正则表达式而不是一组字符确定的分隔符处拆分字符串。将以尽可能多的次数拆分该字符串。如果找不到分隔符,返回值将包含一个值为原始 input 参数字符串的元素。 如果多个匹配项彼此相邻,则会在数组中插入空字符串。例如,如果用一个连字符来拆分字符串,将会导致返回的数组在两个相邻的连字符所在的位置包括一个空字符串,如下面的代码所示。 Visual Basic 复制代码 Dim regex As Regex = New Regex("-") ' Split on hyphens. Dim substrings() As String = regex.Split("plum--pear") For Each match As String In substrings Console.WriteLine("'{0}'", match) Next ' The method writes the following to the console: ' 'plum' ' '' ' 'pear' C# 复制代码 Regex regex = new Regex("-"); // Split on hyphens. string[] substrings = regex.Split("plum--pear"); foreach (string match in substrings) { Console.WriteLine("'{0}'", match); } // The method writes the following to the console: // 'plum' // '' // 'pear' 如果将捕获括号用于 Regex.Split 表达式中,则任何捕获的文本将包含在生成的字符串数组中。例如,如果要通过在捕获括号中放入一个连字符的方式来拆分字符串"plum-pear",则会向返回的数组添加一个包含该连字符的字符串元素。 Visual Basic 复制代码 Dim regex As Regex = New Regex("(-)") ' Split on hyphens. Dim substrings() As String = regex.Split("plum-pear") For Each match As String In substrings Console.WriteLine("'{0}'", match) Next ' The method writes the following to the console: ' 'plum' ' '-' ' 'pear' C# 复制代码 Regex regex = new Regex("(-)"); // Split on hyphens. string[] substrings = regex.Split("plum-pear"); foreach (string match in substrings) { Console.WriteLine("'{0}'", match); } // The method writes the following to the console: // 'plum' // '-' // 'pear' 但是,如果正则表达式模式包含多组捕获括号,则此方法的行为取决于 .NET Framework 的版本。在 .NET Framework 1.0 和 1.1 版中,如果在第一组捕获括号中未找到匹配项,则在返回的数组中不会包含从其他捕获括号中捕获的文本。在 .NET Framework 2.0 版中,所有捕获的文本还添加到返回的数组中。例如,下面的代码使用两组捕获括号从日期字符串中提取日期元素(包括日期分隔符)。第一组捕获括号捕获连字符,而第二组捕获括号捕获左斜线。如果已编译示例代码并在 .NET Framework 1.0 或 1.1 中运行,则它不包括斜线;如果已编译示例代码并在 .NET Framework 2.0 中运行,则它包括斜线。 Visual Basic 复制代码 Dim input As String = "07/14/2007" Dim pattern As String = "(-)|(/)" Dim regex As Regex = New Regex(pattern) For Each result As String In regex.Split(input) Console.WriteLine("'{0}'", result) Next ' In .NET 1.0 and 1.1, the method returns an array of ' 3 elements, as follows: ' '07' ' '14' ' '2007' ' ' In .NET 2.0, the method returns an array of ' 5 elements, as follows: ' '07' ' '/' ' '14' ' '/' ' '2007' C# 复制代码 string input = @"07/14/2007"; string pattern = @"(-)|(/)"; Regex regex = new Regex(pattern); foreach (string result in regex.Split(input)) { Console.WriteLine("'{0}'", result); } // Under .NET 1.0 and 1.1, the method returns an array of // 3 elements, as follows: // '07' // '14' // '2007' // // Under .NET 2.0, the method returns an array of // 5 elements, as follows: // '07' // '/' // '14' // '/' // '2007' 如果正则表达式可以匹配空字符串,则 Split(String) 会将字符串拆分为由单字符组成的字符串的数组,因为每个位置都有空字符串分隔符。例如: Visual Basic 复制代码 Dim input As String = "characters" Dim regex As New Regex("") Dim substrings() As String = regex.Split(input) Console.Write("{") For ctr As Integer = 0 to substrings.Length - 1 Console.Write(substrings(ctr)) If ctr < substrings.Length - 1 Then Console.Write(", ") Next Console.WriteLine("}") ' The example produces the following output: ' {, c, h, a, r, a, c, t, e, r, s, } C# 复制代码 string input = "characters"; Regex regex = new Regex(""); string[] substrings = regex.Split(input); Console.Write("{"); for(int ctr = 0; ctr < substrings.Length; ctr++) { Console.Write(substrings[ctr]); if (ctr < substrings.Length - 1) Console.Write(", "); } Console.WriteLine("}"); // The example produces the following output: // {, c, h, a, r, a, c, t, e, r, s, } 请注意,返回数组的开头和结尾还包括空字符串。