vlambda博客
学习文章列表

接上篇--.NET高级技术之正则表达式


正则表达式



先来看几个需求

看到这样的需求,是不是很无奈?当掌握正则表达式之后,一切变得很简单

1.什么是正则表达式,正则表达式能做什么?

正则表达式是用来进行文本处理的技术,是语言无关的,在几乎所有语言中都有实现。javascript中还会用到。
一个正则表达式就是由普通字符以及特殊字符(称为元字符)组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
正则表达式可以:

字符串的匹配、字符串的提取、字符串的替换

正则表达式第一步:元字符

.        匹配除\n之外的任何单个字符    例如正则表达式“b.g”能匹配如下字符串:“big”、“bug”、“b g”,但是不匹配“buug”,“b..g”可以匹配“buug”。 
[ ]     匹配括号中的任何一个字符       例如正则表达式“b[aui]g”匹配bug、big和bag,但是不匹配beg、baug。

可以在括号中使用连字符“-”来指定字符的区间来简化表示,例如正则表达式[0-9]可以匹配任何数字字符,这样正则表达式“a[0-9]c”等价于“a[0123456789]c”就可以匹配“a0c”、“a1c”、“a2c”等字符串;还可以制定多个区间,例如“[A-Za-z]”可以匹配任何大小写字母,“[A-Za-z0-9]”可以匹配任何的大小写字母或者数字。
( )     将 () 之间括起来的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域;这个元字符在字符串提取的时候非常有用。把一些字符表示为一个整体

有两个作用:1、改变优先级;2、定义提取组两个作用
| :将两个匹配条件进行逻辑“或”运算。'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"

*:匹配0至多个在它之前的子表达式,和通配符*没关系。例如正则表达式“zo*”能匹配 “z” 、“zo”以及 “zoo”;因此“.*”意味着能够匹配任意字符串

    "z(b|c)*"→zb、zbc、zcb、zccc、zbbbccc。"z(ab)*"能匹配z、zab、zabab(用括号改变优先级)。
+ :匹配前面的子表达式一次或多次,和*对比(0到多次)。例如正则表达式9+匹配9、99、999等。“zo+”能匹配 “zo”以及 “zoo” ,不能匹配"z"。
? :匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。一般用来匹配“可选部分”。
{n} :匹配确定的 n 次。"zo{2}"→zoo。例如,“e{2}” 不能匹配“bed”中的“e”,但是能匹配“seed”中的两个“e”。
{n,} :至少匹配n次。例如,“e{2,}”不能匹配“bed”中的“e”,但能匹配 “seeeeeeeed”中的所有“e”。
{n,m} :最少匹配 n 次且最多匹配 m 次。“e{1,3}”将匹配“seeeeeeeed”中的前三个“e”。
^(shift+6) :匹配一行的开始。例如正则表达式“^regex”能够匹配字符串“regex我会用”的开始,但是不能匹配“我会用regex”。
^另外一种意思:非!
$ :匹配行结束符。例如正则表达式“浮云$” 能够匹配字符串“一切都是浮云”的末尾,但是不能匹配字符串“浮云呀” 

   

     注意这些简写表达式是不考虑转义符的,这里的\就表示字符\,而不是C#字符串级别的\,在C#代码中需要使用@或者\双重转义。区分C#级别的转移和正则表达式级别的转移,恰好C#的转义符和正则表达式的转义符都是\而已。正则表达式的转移是在C#之后的(层层盘剥)。把C#的转义符想成%就明白了。在C#看来@"\-"就是\-这个普通的字符串,只不过在正则表达式分析引擎看来他有了特殊含义。"\\d"或者@"\d"
\d:代表一个数字,等同于[0-9]
\D:代表非数字,等同于[^0-9]
\s:代表换行符、Tab制表符等空白字符
\S:代表非空白字符
\w:匹配字母或数字或下划线或汉字,即能组成单词的字符
\W:非\w ,等同于[^\w]
d:digital;s:space、w:word。大写就是“非”

 

.Net中的正则表达式

正则表达式在.Net就是用字符串表示,这个字符串格式比较特殊,无论多么特殊,在C#语言看来都是普通的字符串,具体什么含义由Regex类内部进行语法分析
正则表达式(Regular Expression)的主要类:Regex
常用的3种情况:(C#语法)

  1、判断是否匹配:Regex.IsMatch(“字符串”,”正则表达式”)

  2、字符串提取:Regex.Match(“字符串”,“要提取的字符串的正则表达式”); 字符串提取(循环提取所有):Regex.Matches()
    3、字符串替换:Regex.Replace(“字符串”,”正则”,”替换内容”);






        正则表达式是对字符串进行匹配的语法,像name like '%李%'一样,定义了一些特殊的“元字符”,用来判断一个字符串是否满足某个规则。正则表达式非常深,编译器都是基于正则表达式,掌握基本使用即可。



接上篇--.NET高级技术之正则表达式


基本元字符

接上篇--.NET高级技术之正则表达式

字  符

描  述

\

转义字符,将一个具有特殊功能的字符转义为一个普通字符,或反过来

^

匹配输入字符串的开始位置

$

匹配输入字符串的结束位置

*

匹配前面的零次或多次的子表达式

+

匹配前面的一次或多次的子表达式

?

匹配前面的零次或一次的子表达式

{n}

n是一个非负整数,匹配前面的n次子表达式

{n,}

n是一个非负整数,至少匹配前面的n次子表达式

{n,m}

mn均为非负整数,其中n<=m,最少匹配n次且最多匹配m

?

当该字符紧跟在其他限制符(*,+,?,{n},{n,},{nm})后面时,匹配模式尽可能少的匹配所搜索的字符串

.

匹配除“\n”之外的任何单个字符

(pattern)

匹配pattern并获取这一匹配

(?:pattern)

匹配pattern但不获取匹配结果

(?=pattern)

正向预查,在任何匹配pattern的字符串开始处匹配查找字符串

(?!pattern)

负向预查,在任何不匹配pattern的字符串开始处匹配查找字符串

x|y

匹配xy。例如,‘z|food’能匹配“z”或“food”。‘(z|f)ood’则匹配“zood”或“food”

[xyz]

字符集合。匹配所包含的任意一个字符。例如,‘[abc]’可以匹配“plain”中的‘a’

[^xyz]

负值字符集合。匹配未包含的任意字符。例如,‘[^abc]’可以匹配“plain”中的‘p’

[a-z]

匹配指定范围内的任意字符。例如,‘[a-z]’可以匹配'a'到'z'范围内的任意小写字母字符

[^a-z]

匹配不在指定范围内的任意字符。例如,‘[^a-z]’可以匹配不在‘a’~‘z’'内的任意字符

\b

匹配一个单词边界,指单词和空格间的位置

\B

匹配非单词边界

\d

匹配一个数字字符,等价于[0-9]

\D

匹配一个非数字字符,等价于[^0-9]

\f

匹配一个换页符

\n

匹配一个换行符

\r

匹配一个回车符

\s

匹配任何空白字符,包括空格、制表符、换页符等

\S

匹配任何非空白字符

\t

匹配一个制表符

\v

匹配一个垂直制表符。等价于\x0b和\cK

\w

匹配包括下划线的任何单词字符。等价于‘'[A-Za-z0-9_]’

\W

匹配任何非单词字符。等价于‘[^A-Za-z0-9_]’

说明:

      由于在正则表达式中“ \ ”、“ ? ”、“ * ”、“ ^ ”、“ $ ”、“ + ”、“(”、“)”、“ | ”、“ { ”、“ [ ”等字符已经具有一定特殊意义,如果需要用它们的原始意义,则应该对它进行转义,例如希 望在字符串中至少有一个“ \ ”,那么正则表达式应该这么写:\\+ 。




       使用Regex.IsMatch(被匹配字符串, 正则表达式)判断是否匹配。C#中表示正则表达式最好前面加上@,可以避免转义带来的困扰。





反向引用

反向引用,指把匹配出来的组引用到表达式本身其它地方,比如,在匹配HTML的标记时,我们匹配出一个<a>,我们要把匹配出来的a引用出来,用来找到</a>,这个时候就要用到反向引用。
语法
    a、反向引用编号的组,语法为\number
    b、反向引用命名的组,语法为\k<name>
举例
    a、匹配成对的HTML标签

接上篇--.NET高级技术之正则表达式@"<(?<tag>[^\s>]+)[^>]*>.*</\k<tag>>"        

    b、匹配两个两个重叠出现的字符  

接上篇--.NET高级技术之正则表达式        public static void Main()
接上篇--.NET高级技术之正则表达式        
{    
接上篇--.NET高级技术之正则表达式            
string s = "aabbc11asd";
接上篇--.NET高级技术之正则表达式            Regex reg 
= new Regex(@"(\w)\1");
接上篇--.NET高级技术之正则表达式            MatchCollection matches 
= reg.Matches(s);
接上篇--.NET高级技术之正则表达式            
foreach(Match m in matches)
接上篇--.NET高级技术之正则表达式                Console.WriteLine(m.Value);
接上篇--.NET高级技术之正则表达式            Console.ReadLine();
接上篇--.NET高级技术之正则表达式        }
      

返回结果为aa bb 11 

辅助匹配组

以下几种组结构,括号中的Pattern都不作为匹配结果的一部分进行保存
    1、正声明(?=)
    涵义:括号中的模式必须出现在声明右侧,但不作为匹配的一部分

接上篇--.NET高级技术之正则表达式        public static void Main()
接上篇--.NET高级技术之正则表达式        
{    
接上篇--.NET高级技术之正则表达式            
string s = "C#.net,VB.net,PHP,Java,JScript.net";
接上篇--.NET高级技术之正则表达式            Regex reg 
= new Regex(@"[\w\#]+(?=\.net)",RegexOptions.Compiled);
接上篇--.NET高级技术之正则表达式            MatchCollection mc 
= reg.Matches(s);
接上篇--.NET高级技术之正则表达式            
foreach(Match m in mc)
接上篇--.NET高级技术之正则表达式                Console.WriteLine(m.Value); 
接上篇--.NET高级技术之正则表达式            Console.ReadLine();
接上篇--.NET高级技术之正则表达式            
//输出 C# VB JScript
接上篇--.NET高级技术之正则表达式
        }
接上篇--.NET高级技术之正则表达式

    可以看到匹配引擎要求匹配.net,但却不把.net放到匹配结果中
    2、负声明(?!)
     涵义:括号中的模式必须不出现在声明右侧
        下例演示如何取得一个<a>标签对中的全部内容,即使其中包含别的HTML tag


接上篇--.NET高级技术之正则表达式        public static void Main()
接上篇--.NET高级技术之正则表达式        

接上篇--.NET高级技术之正则表达式            
string newsContent = @"url:<a href=""1.html""><img src=""1.gif"">test<span style=""color:red;"">Regex</span></a>.";
接上篇--.NET高级技术之正则表达式            Regex regEnd 
= new Regex(@"<\s*a[^>]*>([^<]|<(?!/a))*<\s*/a\s*>",RegexOptions.Multiline);
接上篇--.NET高级技术之正则表达式            
接上篇--.NET高级技术之正则表达式            Console.WriteLine(regEnd.Match(newsContent).Value);
接上篇--.NET高级技术之正则表达式
//Result: <a href="1.html"><img src="1.gif">test<span style="color:red;">Regex</span></a>
接上篇--.NET高级技术之正则表达式
            Console.ReadLine();
        }

    3、反向正声明(?<=) 
    涵义:括号中的模式必须出现在声明左侧,但不作为匹配的一部分
    4、反向负声明(?<!)
    涵义:括号中的模式必须不出现在声明左侧

非回溯匹配
语法:(?>)
涵义:该组匹配后,其匹配的字符不能通过回溯用于后面的表达式的匹配。呵呵,光看这句话肯定搞不懂,我当初为了搞懂这个也花了不少的时间,还是通过实例来说明吧:
"www.csdn.net" 可以通过@"\w+\.(.*)\.\w+"来匹配,却不能通过@"\w+\.(?>.*)\.\w+"来匹配!为什么呢?

原因是正则匹配是贪婪的,匹配时它会尽可能多的匹配最多的结果,所以,上例两个正则式中的.*都会把csdn.net匹配完, 这个时候,第一个表达式在开始匹配时发现\.\w+没得字符给它匹配了,所以它会进行回溯,所谓回溯,就是把.*匹配的结果往回推,回推留出来的字符再用来匹配\.\w+,直到\.\w+匹配成功,整个表达式返回成功的匹配结果。而第二个表达式,因使用的是非回溯匹配,所以,.*匹配完后,不允许通过回溯来匹配\.\w+,所以整个表达式匹配失败。

请注意,回溯匹配是很浪费资源的一种匹配方式,所以,请尽量避免您的正则式要通过回溯来成功匹配,如上例,可以换成@"\w+\.([^\.]+\.)+\w+"+"。