八.模式匹配和文本处理
正则表达式概述
概述:使用正则表达式
使用RegEx类以及Match类,在System.Text.RegularExpression空间下。
简例:
Regexreg = new Regex("the");
stringstr1 = "the quick brown fox jumped over thelazy dog";
MatchmatchSet;
matchSet = reg.Match(str1);
if(matchSet.Success)
{
intmatchPos= matchSet.Index;
Console.WriteLine("found match at position:" + matchPos);
}
///found match at position:0
RegEx类的构造
Regexreg = new Regex("the");
Regex类的Match方法
对一个RegEx类的实例,按照正则表达式进行匹配。返回第一个匹配结果Match类实例
Regex类的Matches方法
对一个RegEx类的实例,按照正则表达式进行匹配。返回所有匹配的MatchCollection类实例
MatchCollectionmatchSetCollection;
matchSetCollection =reg.Matches(str1);
if(matchSetCollection.Count > 0)
{
Console.Write("found match at position:");
foreach(Match iteminmatchSetCollection)
{
Console.Write(item.Index+",");
}
}
///found match at position:0,32,
Match类的Success方法
Success属性来确认是否匹配成功
Regex.IsMatch静态方法
如果产生匹配,那么返回True。
if(Regex.IsMatch(str1,"the"))
{
intmatchPos = matchSet.Index;
Console.WriteLine("found match at position:" + matchPos);
///found match at position:0
}
Regex.Replace静态方法
将在第一个参数(字符串)中,用一个新字符串进行替换一个老的字符串
Regex.Replace(str1,"brown","blace");
Console.WriteLine(str1);
///the quick brown fox jumped over the lazy dog
常用符号一览表格
表1.常用的数量限定符 | |
代码/语法 | 说明 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
表2.常用的元字符 | |
代码 | 说明 |
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
表3.常用的反义代码 | |
代码/语法 | 说明 |
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
例子:\S+匹配不包含空白符的字符串。
<a[^>]+>匹配用尖括号括起来的以a开头的字符串。
表4.常用分组语法 | ||
分类 | 代码/语法 | 说明 |
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
(?<name>exp) | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) | |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 | |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 | |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
表5.懒惰限定符 | |
代码/语法 | 说明 |
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
表7.尚未详细讨论的语法 | |
代码/语法 | 说明 |
\a | 报警字符(打印它的效果是电脑嘀一声) |
\b | 通常是单词分界位置,但如果在字符类里使用代表退格 |
\t | 制表符,Tab |
\r | 回车 |
\v | 竖向制表符 |
\f | 换页符 |
\n | 换行符 |
\e | Escape |
\0nn | ASCII代码中八进制代码为nn的字符 |
\xnn | ASCII代码中十六进制代码为nn的字符 |
\unnnn | Unicode代码中十六进制代码为nnnn的字符 |
\cN | ASCII控制字符。比如\cC代表Ctrl+C |
\A | 字符串开头(类似^,但不受处理多行选项的影响) |
\Z | 字符串结尾或行尾(不受处理多行选项的影响) |
\z | 字符串结尾(类似$,但不受处理多行选项的影响) |
\G | 当前搜索的开头 |
\p{name} | Unicode中命名为name的字符类,例如\p{IsGreek} |
(?>exp) | 贪婪子表达式 |
(?<x>-<y>exp) | 平衡组 |
(?im-nsx:exp) | 在子表达式exp中改变处理选项 |
(?im-nsx) | 为表达式后面的部分改变处理选项 |
(?(exp)yes|no) | 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no |
(?(exp)yes) | 同上,只是使用空表达式作为no |
(?(name)yes|no) | 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no |
(?(name)yes) | 同上,只是使用空表达式作为no |
正则表达式语法
数量限定词
紧接其前的字符的数量
加号(+)
匹配一个或者多个紧接其前的字符
string[]words = new string[]{ "bad", "boy","baaad","bear","bend" };
foreach(string iteminwords)
{
if(Regex.IsMatch(item,"ba+")) ///匹配“以b开头,并包含一个或者多个a”的字符串
{ Console.WriteLine(item);}
}
///bad
///baaad
星号(*)
匹配零个或多个紧接其前的字符。“.*”表示任意换行以外的字符任意多个
问号(?)
匹配零次或者一次紧接其前的字符
有限数量的匹配{n}
在一对大括号内部放置一个数可指定一个有限数量紧接其前的字符的匹配:{n}
在一对大括号内部提供两个数可指定紧接其前的字符的个数最大值和个数最小值:{n,m}
string[]words = new string[]{ "bad", "boy","baaad","bear","bend" };
foreach(string iteminwords)
{
if(Regex.IsMatch(item,"a{1,2}"))///匹配“以b开头,并包含一个或者多个a”的字符串
{ Console.WriteLine(item);}
}
///bad
///baaad
///bear
字符类
句点 (.)
匹配除了换行符以外的任意一个字符。
stringwords1 ="<b>string</b>";
stringregExp="</.?";
if(Regex.IsMatch(words1, regExp))
{
MatchCollectionaMatch =Regex.Matches(words1, regExp);
for(int i = 0; i < aMatch.Count; i++)
{
Console.WriteLine(aMatch[i].Value);
}
}
///</b
包含字符组 ([])
匹配包含字符组(区分大小写),用方括号括着,可以[abcdefg],也可以[a-g]。[A-Za-z0-9]
stringwords2 = "THE quick BROWN fox JUMPED over THElazy DOG";
stringregExp2 = "[a-z]";
if(Regex.IsMatch(words2, regExp2))
{
MatchCollectionaMatch =Regex.Matches(words2, regExp2);
for(int i = 0; i < aMatch.Count; i++)
{
Console.Write(aMatch[i].Value+",");
}
}
///q,u,i,c,k,f,o,x,o,v,e,r,l,a,z,y,
脱字符号 (^)
字符类的反或否定
stringword9 = "<string>Mike</string><int/> <double>Mike</double><string>Male</string> <s> <s1>";
stringregexp9 = @"<s[^>]+>";///尖括号括着的s开头两字符或以上的字符串
MatchCollectionmatchset9 =Regex.Matches(word9, regexp9);
foreach(Match iteminmatchset9)
{
Console.Write(item.Value+",");
}
///<string>,<string>,<s1>,
Console.WriteLine();
单词字符 (\w)
Word。字母或数字或下划线或汉字
非单词字符 (\W)
数字字符符 (\d)
Digital,等于[0-9]
非数字字符类 (\D)
等于[^0-9]
空格字符 (\s)
Space。任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等
非空格字符 (\S)
用断言修改正则表达式
字符串或行的开始处^
只能在字符串或行的开始处找到匹配。”^h”表示只与第一个字符为字母”h”的字符串向匹配,而忽视掉其他位置上的h
string[]words3 = new string[]{"heal","heel","noah","techno"};
stringregExp3 = "^h";///第一个字符为字母"h"
foreach(var iteminwords3)
{
if(Regex.IsMatch(item, regExp3))
{
MatchaMatch = Regex.Match(item, regExp3);
Console.WriteLine("Matched:{0} at position:{1},value:{2}",item,aMatch.Index,aMatch.Value);
}
}
///Matched:heal at position:0,value:h
///Matched:heel at position:0,value:h
字符串或行的末尾处$
只能在字符串或行的末尾找到匹配。”h$”表示只与最后一个字符为字母”h”的字符串向匹配,而忽视掉其他位置上的h
stringregExp4 = "h$";///最后一个字符为字母"h"
foreach(var iteminwords3)
{
if(Regex.IsMatch(item, regExp4))
{
MatchaMatch =Regex.Match(item, regExp4);
Console.WriteLine("Matched:{0} at position:{1},value:{2}",item, aMatch.Index, aMatch.Value);
}
}
///Matched:noah at position:3,value:h
单词的开始或者结束处\b
代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。
words2 = "hark,what doth thou say, Harold?";
stringregExp5 = "\\bh";///左边空格+h
if(Regex.IsMatch(words2, regExp5))
{
MatchCollectionaMatch =Regex.Matches(words2, regExp5);
foreach(Match iteminaMatch)
{
Console.WriteLine("Matched:{0} at position:{1},value:{2}",words2, item.Index, item.Value);
}
}
///Matched:hark, what doth thou say, Harold? atposition:0,value:h
分枝条件 |
匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。
stringword8 = "01012-345678 02255 02900-1234(010)12345678 (022-87654321";
stringregexp81 = @"\d{5}-\d{4}|\d{5}";///
MatchCollectionmatchset81 =Regex.Matches(word8, regexp81);
foreach(Match iteminmatchset81)
{
Console.Write(item.Value+",");
}
///12345,22334,02912,34567,12345,87654,
Console.WriteLine();
stringregexp82 = @"\d{5}|\d{5}-\d{4}";///
MatchCollectionmatchset82 =Regex.Matches(word8, regexp82);
foreach(Match iteminmatchset82)
{
Console.Write(item.Value+",");
}
///01012,34567,02255,02900,12345,87654,
Console.WriteLine();
使用分组构造
用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作
匿名组
正则表达式用括号括起来。
stringword4 = "08/14/57 46 02/25/59 45 06/05/8518"+"03/12/88 16 09/09/90 13";
stringregexp4 = "(\\s\\d{2}\\s)";
MatchCollectionmatchset4 =Regex.Matches(word4, regexp4);
foreach(Match iteminmatchset4)
{
Console.Write(item.Groups[0].Captures[0]+",");
}
/// 46 , 45 , 16 ,
Console.WriteLine();
命名组?< > ?’ ’
前缀的问号、一对尖括号括着名字和正则表达式组成。尖括号也可以用单引号代替。
stringregexp5 = "(?<date>\\d{2}/\\d{2}/\\d{2})(?<age>\\s\\d{2}\\s)";
MatchCollectionmatchset5 =Regex.Matches(word4, regexp5);
foreach(Match iteminmatchset5)
{
Console.Write(item.Groups["date"].Captures[0] +",");
}
///08/14/57,02/25/59,03/12/88,
Console.WriteLine();
foreach(Match iteminmatchset5)
{
Console.Write(item.Groups["age"].Captures[0] +",");
}
/// 46 , 45 , 16 ,
Console.WriteLine();
非捕获分组?:
stringword6 = "tree bear team lee bar t";
stringregexp6 = "((?:be|b)ar)";///ar前是be或者b
MatchCollectionmatchset6 =Regex.Matches(word6, regexp6);
foreach(Match iteminmatchset6)
{
Console.Write(item.Value+",");
}
///bear,bar,
Console.WriteLine();
零宽度正向预搜索断言和零宽度负向预搜索断言
expF(?=exp)expB和expF(?!exp)expB都是对后面的expB表达式,对匹配expB的字符串的开始部分进行exp限制。
expF(?<=exp)expB和expF(?<!exp)expB都是对前面的expF表达式,对匹配expF的字符串的结束部分进行exp限制。
正向预查 (?=)
零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。前面表达式目标的后一段字符的限制条件。expF(?=exp)expB:匹配exp,在前面expF匹配的字符后面加上exp的限制。
比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'msinging while you're dancing.时,它会匹配sing和danc。
stringregexp61 = @"(\b(?=t)\w+\b)";///t开头的单词
MatchCollectionmatchset61 =Regex.Matches(word6, regexp61);
foreach(Match iteminmatchset61)
{
Console.Write(item.Value+",");
}
///tree,team, t,
Console.WriteLine();
反向预查 (?<=)
零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。后面表达式目标的前一段字符的限制条件。expF(?<=exp)expB:匹配exp,对前面expF的位置,对匹配expF的字符的后面加上exp限制
比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找readinga book时,它匹配ading。
stringregexp62 = @"(?<= t)\w+\b";///前面是t开头的后面的单词
MatchCollectionmatchset62 =Regex.Matches(word6, regexp62);
foreach(Match iteminmatchset62)
{
Console.Write(item.Value+",");
}
///ree,eam,
Console.WriteLine();
string word11 = "1234567890";
string regexp111 = @"((?<=\d)\d{3})+\b";
MatchCollection matchset111 =Regex.Matches(word11, regexp111);
foreach (Matchitemin matchset111)
{
Console.Write(item.Value +";");
}
///234567890;
Console.WriteLine();
负正向预查 (?!)
零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp。
前面表达式目标的后一段字符的不能跟指定的字符串条件。expF(?!exp)expB:匹配不是exp,在前面expF、后面expB中间的位置。
例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
stringregexp63 = @"\b(?!t)\w+\b";///非t开头的单词
MatchCollectionmatchset63 =Regex.Matches(word6, regexp63);
foreach(Match iteminmatchset63)
{
Console.Write(item.Value+",");
}
///bear,lee,bar,
Console.WriteLine();
负反向预查 (?<!)
零宽度负回顾后发断言,断言此位置的前面不能匹配表达式exp。
后面表达式目标的前一段字符的不能跟指定的字符串条件。expF(?<!exp)expB:匹配不是exp,对前面expF的位置,对匹配expF的字符的前面加上exp限制。
(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
stringregexp64 = @"\b\w(?<!t)+\b";///@"\b(\w(?<!t))+\b",非t开头的单词
MatchCollectionmatchset64 =Regex.Matches(word6, regexp64);
foreach(Match iteminmatchset64)
{
Console.Write(item.Value+",");
}
///bear,lee,bar,
Console.WriteLine();
注释(?#)
语法(?#comment)来包含注释。
例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)。
贪婪与懒惰
贪婪匹配:当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。
以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?,就能表示尽可能少的重复。
这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
现在看看懒惰版的例子吧:
a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)
*?
重复任意次,但尽可能少重复 。
+?
重复1次或更多次,但尽可能少重复 。
??
重复0次或1次,但尽可能少重复 。
{n,m}?
重复n到m次,但尽可能少重复 。
{n,}?
重复n次以上,但尽可能少重复。
CapturesCollection类
CapturesCollection可以确保捕获匹配的不仅仅是最后一个匹配,而是所有的匹配。
当正则表达式匹配时,产生一个被称为Capture的对象,这个对象就是一个CapturesCollection集合。
Regex reg = new Regex("the");
string str1 = "thequick brown fox jumped over the lazy dog";
Match matchSet;
matchSet = reg.Match(str1);
if (matchSet.Success)
{
int matchPos= matchSet.Index;
Console.WriteLine("found match at position:" + matchPos +";" + matchSet.Captures[0]);
}
///found matchat position:0;the
当正则表达式中使用命名组的时候,这个组也会有自己的捕获集合。
word4 = "08/14/57 46 02/25/59 4506/05/85 18" +"03/12/88 1609/09/90 13";
regexp5 = "(?'date'\\d{2}/\\d{2}/\\d{2})(?'age'\\s\\d{2}\\s)";
matchset5 = Regex.Matches(word4,regexp5);
foreach (Matchitemin matchset5)
{
Console.WriteLine("Groups of date ,Captures:{0}",item.Groups["date"].Captures[0]);
Console.WriteLine("Groups of age ,Captures:{0}",item.Groups["age"].Captures[0]);
}
///Groups ofdate ,Captures:08/14/57
///Groups ofage ,Captures: 46
///Groups ofdate ,Captures:02/25/59
///Groups ofage ,Captures: 45
///Groups ofdate ,Captures:03/12/88
///Groups ofage ,Captures: 16
正则表达式的选项
几个选项如忽略大小写,处理多行等,这些选项能用来改变处理正则表达式的方式。
matchset5 = Regex.Matches(word4,regexp5,RegexOptions.Multiline);
RegexOption成员 | 内置字符 | 描述 |
None | N/A | 设置没有选项设置 |
IgnoreCase | I | 指定不区分字母大小写匹配 |
Multiline | M | 指定多行模式 |
ExplicitCaptrue | N | 指定只有对有明确命名或编号的组进行的捕获才是有效的 |
Compiled | N/A | 指定将正则表达式编译成汇编 |
Singleline | S | 指定单行模式 |
IgnorePatternWhiteSpace | X | 指定忽略模式中的非转义空格,使注释跟在符号(#)之后 |
RightToLeft | N/A | 指定搜索时从右到左,而不是从左到右 |
ECMAScript | N/A | 指定ECMAScript-compliant行为对不表达式有效 |