串(六):模式匹配与文本处理
2012-05-04 11:43:54尽管String类和StringBuilder类提供了一套方法用来处理基于字符串的数据,但是RegEx和它的支持类却为字符串处理任务提供了更强大的功能。字符串的处理主要包括寻找字符串中的模式(模式匹配),以及通过称为正则表达式的特殊语言来执行操作。本问主要介绍正则表达式的方法以及如果利用它们解决普通文本处理任务。
一、正则表达式概述
简单的说,正则表达式是一种可以用于文字模式匹配和替换的强有力的工具。是由一系列普通字符和特殊字符组成的能明确描述文本字符串的文字匹配模式。正则表达式并非一门专用语言,但也可以看作是一种语言,它可以让用户通过使用一系列普通字符和特殊字符构建能明确描述文本字符串的匹配模式。除了简单描述这些模式之外,正则表达式解释引擎通常可用于遍历匹配,并使用模式作为分隔符来将字符串解析为子字符串,或以智能方式替换文本或重新设置文本格式。正则表达式为解决与文本处理有关的许多常见任务提供了有效而简捷的方式。
二、使用正则表达式
为了使用正则表达式需要把RegEx类引入程序。大家可以在System.Text.RegularExpression命名空间中找到这个类。一旦把这个类导入到程序,就需要决定想要用RegEx类来做什么事情了。如果想要进行匹配,就需要使用Match类。如果打算做替换,就需要使用RegEx类的Replace方法。我们先来看一个匹配的操作:
Regex reg = new Regex("the");
string str = "the quick brown fox jumped over the lazy dog";
Match matchSet = reg.Match(str);
int matchPos;
if (matchSet.Success)
{
matchPos = matchSet.Index;
Console.WriteLine("found match at position:" + matchPos);
}
上面程序中Match类为存储用来与正则表达式进行匹配的数据提供了方法。如果属性Success返回true,那么正则表达式在字符串中至少匹配了一条字串,否则Success返回false。程序还可以有另外一种方法来查看是否匹配成功。通过把正则表达式和目标字符串传递给IsMatch方法可以对正则表达式进行预测试。
如Regex.IsMatch(str, "the")。
Match类的一个问题就是它只能存储一个匹配。如果需要多个匹配可以使用另外一个类Matches。为了处理所有找到的匹配可以把匹配存储到MatchCollection对象中。
Regex reg = new Regex("the");
string str = "the quick brown fox jumped over the lazy dog";
MatchCollection matchSet = reg.Matches(str);
if (matchSet.Count > 0)
{
foreach (Match match in matchSet)
{
Console.WriteLine("found a match at: " + match.Index);
}
}
接下来我们来看替换的操作。Replace方法可以作为一种带有三个参数的类方法来进行调用:一个目标字符串,要替换的字串,以及用作替换的字串。如下代码
string str = "the quick brown fox jumped over the brown dog";
str = Regex.Replace(str, "brown", "black");
现在会把字符串读作”the quick black fox jumped over the black dog“。
三、数量词
在编写正则表达式的时候,经常会要向正则表达式添加数量型数据,诸如”精确匹配两次“。利用数量词就可以把这样的数据添加到正则表达式里面了。
1.(+):这个数量词说明正则表达式应该匹配一个或者多个紧接其前的字符。
例如:"ba+"可以匹配"bad","baaad"。
2.(*):这个数量词说明正则表达式应该匹配零个或多个紧接其前的字符。这个数量词相对数量词(+)有较少的限制,但是在实践中这个数量词非常难用,因为星号通常会导致匹配几乎所有内容。
3.(?):这是一种精确匹配零次或一次的数量词。
通过在一对大括号内部放置一个数可以指定一个有限数量的匹配。例如:"ba{2}d"将匹配"baad"。
通过在大括号内提供两个数字可以指定匹配的最大值和最小值{n,m}。例如:"ba{1,3}d"。
这里还可以编写"ba{1,}d"来匹配至少要有一个匹配,但是没有指定最大值。
到目前为止讨论的数量词展示的就是所谓的贪心行为。它们试图尽可能多的匹配,而且这种行为经常会导致不预期的匹配。来看下面的例子:
string str = "<b>string</b>";
string regExp = "<.*>";
if (Regex.IsMatch(str, regExp))
{
MatchCollection match = Regex.Matches(str, regExp);
for (int i = 0; i < match.Count; i++)
{
Console.WriteLine(match[i].Value);
}
}
原本期望这个程序就返回两个标签:和。但是由于贪心,正则表达式匹配了string。利用惰性量词:问号(?)就可以解决这个问题。当问号直接放在原有数量词后面时,数量词就变懒惰了。这里的惰性是指在正则表达式中将试图做尽量可能少的匹配。
四、使用字符类
字符类允许用户说明基于一串字符的模式。
1.(.):句点与字符串中任意字符匹配。例如:"t.e"。
2.([]):匹配[]内出现的字符。例如:"[A-Za-z]","[0-9]"。
3.(^):通过在字符类前面放置一个脱字符(^)的方法可以创建字符类的反或则字符类的否定。
例如:"[aeiou]"来表示元音类,那么"[^aeiou]"来表示辅音或非元音。
4.其他具体特殊意义的字符集。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^\f\n\r\t\v]。
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。等价于「[A-Za-z0-9_]」。
\W 匹配任何非单词字符。等价于「[^A-Za-z0-9_]」。
五、用断言修改正则表达式
C#语言包含一系列可以添加给正则表达式的运算符。这些运算符可以在不用正则表达式引擎遍历字符串的情况下改变表达式的行为,这些运算符被称为断言。
1.(^):匹配开始。例如:"^h"将匹配所有h开头的单词。
2.($):匹配结束。
3.(|):匹配或的关系。例如"a|b"将匹配a或b。
4.(\b):这个断言可以在正则表达式中指定所有匹配只能发生在单词的边缘,这就意味着匹配只能发生在用空格分隔的单词的开始或结束处。
5.零宽度正向预搜索断言和零宽度反向预搜索断言:判断当前位置的前后字符,是否符合指定的条件,但不匹配前后的字符。
六、使用分组构造
Regex类有一套分组构造可以用来把成功匹配进行分组,从而使字符解析成相关匹配更容易。例如,给定了生日和年龄的字符串,而用户只想确定生日的话。通过把生日分组到一起,就可以确定它们作为一组,而不再需要单独进行匹配。
通过括号内围绕正则表达式就可以组成一个分组构造。
例如:"08/14/57 46 02/25/29 45 06/05/85 18 03/12/88 16 09/09/90 13"。这个字符串就是有生日和年龄组成。如果只要匹配年龄而不要生日,就可以把正则表达式书写成:"([\s\d{2}\s](http://www.cnblogs.com/xiaosuo/admin/file:////s//d%7B2%7D//s))"。这样就产生一个匿名的分组,我们来看一段小程序来或者我们要的年龄:
using System;
using System.Text.RegularExpressions;
class chapter8
{
static void Main()
{
string words = "08/14/57 46 02/25/59 45 06/05/85 18" + "03/12/88 16 09/09/90 13";
string regExp1 = "(\\s\\d{2}\\s)";
MatchCollection matchSet = Regex.Matches(words,regExp1);
foreach (Match aMatch in matchSet)
Console.WriteLine(aMatch.Groups[0].Captures[0]);
}
}
组经常并不是匿名的,命名的组更容易使用。命名组由作为正则表达式前缀的问号和一对尖括号括着的名字组成,例如:"(?
using System;
using System.Text.RegularExpressions;
class chapter8
{
static void Main()
{
string words = "08/14/57 46 02/25/59 45 06/05/85 18 " + "03/12/88 16 09/09/90 13";
string regExp1 = "(?<ages>(\\d{2}/\\d{2}/\\d{2}))\\s";
MatchCollection matchSet = Regex.Matches(words,regExp1);
foreach (Match aMatch in matchSet)
Console.WriteLine("Date: {0}", aMatch.Groups["ages"]);
}
}
当正则表达式匹配子表达式的时候,产生了一个被称为是Capture的对象,而且会把此对象添加到名为CapturesCollection的集合里面。当在正则表达式中使用命名组的时候,这个组就有自己的捕获集合。
为了检索用了命名组的正则表达式所收集的捕获,就要调用来自Match对象Group属性的Captures属性。
七、正则表达式的选项