贝网博客

我的分类
流水记事
源码下载
Asp.net
其它
数据库
Javascript
.Net技术
我的相册
友情链接
博客园
CSDN博客
Start0
最新回复
嗷嗷的cCC
fasdfasdf
[:..
lz这个东西好厉害,我..
哈哈,好照片
不错,以前一直用黄色..
终于找到支持ff的修正..
终于找到支持ff的修正..
新鲜性
看看,试试,好不好使。
分类 =》.Net技术
正则学习笔记 主要是C#或Javascript
发表于:2010-07-10 17:52:18
更新于:2013-05-28 15:47:42

概念相关笔记

这是俺学习正则时的一些正则学习笔记
可能理解会有些不对,谁看到谁提哈,嘿嘿

1、容易混淆的单行模式和多行模式:
单行模式只影响.(小数点)的匹配,关闭单行模式,.匹配换行以外的任意字符;开启单行模式,.匹配任意字符
多行模式只影响^和$的匹配,关闭多行模式,^只能匹配字符串开头,$只能匹配字符串结尾;
        开启多行模式,^匹配字符串开头或行的开头,$匹配字符串结尾或行的结尾
因为正则发展的历史原因,造成这2个概念好像是相反的概念,实际这2个概念是没有任何关系的2个概念

2、全局模式(C#没有,js有):关闭时,只匹配一次;开启时,匹配全部字符串,
在js中,关闭全局模式,等效于C#中的Match方法;开启全局模式,等效于C#中的Matches方法

3、贪婪模式与懒惰模式:举例说明:有字符串:0<div>a1</div>b1<div>c1</div><div>d1</div>9
贪婪模式的正则:<div>.*</div>,只有一个匹配结果:<div>a1</div>b1<div>c1</div><div>d1</div>
懒惰模式的正则:<div>.*?</div>,有3个匹配结果,分别是:<div>a1</div>    <div>c1</div>       <div>d1</div>
注:贪婪模式的原理是匹配优先,而懒惰模式的原理是忽略优先,比如:
字符串 abd
贪婪模式正则:ab?c    在匹配时,会先尝试进行ab匹配,再比对c,不匹配了,进行回溯,进行ac的匹配
懒惰模式正则:ab??c  在匹配时,会先尝试进行ac匹配,不匹配了,进行回溯,进行abc的匹配

4、非回溯匹配(也叫固化分组):(?>):举例说明:字符串:张三是中国人,李四是中国人,王五是韩国人
正则:(.*)中国人,因为正则引擎的贪婪特性,.*第一次扫描时会匹配全部字符串,发现后面没有字符了,不能匹配正则里的“中国人”,于是把.*的匹配往前递推一个,发现“国”也不能匹配正则里的“中国人”,于是再把.*的匹配往前递推,一直推到“张三是中国人,李四是”,此时匹配到了“中国人”,于是.*匹配的结果就是:张三是中国人,李四是  这里说的往前递推就是回溯
把正则改为:(?>.*)中国人  匹配就会失败,因为正则式里指定了.*不允许回溯,所以.*第一次扫描时会匹配全部字符串,再往后扫描时匹配不到,就直接返回了,而不会往前递推。注意:非回溯组也是非捕获组,就是这个括号里的值不会被捕获
之所以有这个非回溯,是因为在正则表达式引擎时,回溯是很耗资源和时间的,要尽量避免回溯,比如:
字符串:<a href="http://www.beinet.cn">这是我的网站</a>,要用正则匹配里面的url和文本,可以用下面2个正则,都可以实现:
<a href="(.+?)">(.+?)</a>
<a href="([^"]+)">([^<]+)</a>
但是第一个正则,在匹配时会有回溯,比如href是懒惰匹配,这个.+?会先匹配h,然后看后面是不是",不是,再递推下一个字符t,一直递推19次
而第二个正则,直接就匹配到"前面,不存在回溯,所以在写正则时,要尽量使用没有回溯,或者回溯少的正则

其它笔记

1、\b:表示单词的起始或结束
\B:表示非单词边界(不在单词的开始或结束)
^:表示字符串的起始位置,指定多行模式时,表示行的起始位置
$:表示字符串的结束位置,指定多行模式时,表示行的结束位置

2、反向引用:\1这样的转义数字,代表前面捕获的内容,如果我们想匹配重复的单词,就可以用这种转义数字
举例:this isa a this this a a file list file filea,我们要找出其中重复的单词,可以用正则:
\b([a-z]+)\b \1\b 来匹配,\1表示第一个括号里的内容,\2表示第2个,如此类推

3、捕获的顺序是按左括号的出现顺序,从1开始顺序递增
注:捕获就是把括号里的内容压入堆栈
例如:([+-])?(\d+(\.\d+)?)(.*)
([+-])为捕获的第一个内容,通常为$1,
      C#中可以用Match.Groups[0].Value来得到捕获的值(在正则中可以用\1反向引用,以下类推)
               也可以用Match.Result("$1")来得到捕获的值
(\d+(\.\d+)?)为捕获的第二个内容,通常为$2
而$2中的(\.\d+)为捕获的第三个内容,通常为$3
最后的(.*)为捕获的第四个内容,通常为$4
注意:如果补获组进行了命名,则未命名的第1个左括号为$1,未命名的第2个左括号为$2,以此类推,直到没有未命名的补获为止,再开始按顺序推算有命名的补获组

4、如果对某个括号里的内容不想进行捕获,可以使用?:
例如:例3修改为:([+-])?(\d+(?:\.\d+)?)(.*)
例3里的$3就变成了(.*),而$4就不存在了
技巧:如果不想加?:,可以在匹配时增加选项:RegexOptions.ExplicitCapture,这个选项只会捕获用(?<name>...)的组,但是如果指定了反射引用时,必须对引用的的捕获显式命名,比如正则:<([^\s]+)></\1>,如果指定RegexOptions.ExplicitCapture时会报错

5、替换时保留匹配内容,例如字符串:http://beinet.cn/
要替换成超链接形式<a href=‘http://beinet.cn/’>beinet.cn</a>,可以用C#语句:
    Regex.Replace(@"http://beinet.cn/", @"(http://(.*)/)", @"<a href='$1'>$2</a>");

    Regex.Replace(@"http://beinet.cn/", @"(?<url>http://(?<host>.*)/)", @"<a href='${url}'>${host}</a>");

6、\s匹配空白字符,包括:空格、Tab、换行、回车,等价于 [\t\r\n ]
  \S匹配上述4个字符以外的其它所有字符

  所以:[\s\S] 就可以匹配任意字符了   

7、\w :匹配包括下划线的任何单词字符,等价于 [A-Za-z0-9_]
\W :匹配任何非单词字符,等价于 [^A-Z a-z 0-9_] 

  所以:[\w\W] 也可以匹配任意字符了   

8、\d :匹配所有数字,一般等价于 [0-9]  注:在C#里,默认情况下也匹配全角的0-9
\D :匹配任何非数字
  所以:[\d\D] 也可以匹配任意字符了   

9、.(句点字符。): 匹配除 \n 以外的任何字符。
注意1:[.\n]并不能匹配任意字符,因为在[ ]里,.只是代表自己,不匹配其它字符
              所以要匹配任意字符,请参考:5,6,7
注意2:如果指定正则选项为Singleline,则此时.匹配任意字符了

10、\nnn:匹配一个3位的8进制Ascii字符,如\103匹配大写C字符
\xnn:匹配一个2位的16进制Ascii字符,如\x43匹配大写C字符
\unnnn:匹配一个4位的16进制Unicode字符
\cV:匹配一个控制字符,如\cV匹配Ctrl-V
注意1:为了跟反向引用区分开,表示8进制字符时,比如\43,请写成\043

11、零宽度断言:有的地方称之为环视,或者预搜索,或声明,就是根据表达式匹配一个位置,而不是匹配字符,举例:
有字符串为:abcdefghijklmnopqrstuvwxyz
正声明?=
(?=opq):匹配n与o中间的位置,此时:mn(?=opq),就可以匹配到mn,而m(?=opq)匹配不到东西
                 (?=opq)op,就可以匹配到op,而(?=opq)p匹配不到东西
    举例:Languages have: Java C#.Net C++ Javascript VB.Net JScript.Net Pascal
                正则:\S+(?=\.Net) 将得到结果:C# VB JScript 

逆向正声明?<=
(?<=opq):匹配q与r中间的位置,此时:pq(?<=opq),就可以匹配到pq,而p(?<=opq)匹配不到东西
                   (?<=opq)rst,就可以匹配到rst,而(?<=opq)st匹配不到东西
注:Javascript不支持逆向正声明
    举例:名单:张三 李四 张建四 王五
                 正则:(?<=张)\S+ 将得到结果:三 建四
负声明?|
(?!opq):匹配所有位置,除了n与o中间的位置,此时:[a-z](?!opq),就可以匹配到除n以外的所有字符,而n(?!opq)匹配不到东西
                   (?!opq)[a-z],就可以匹配到除o以外的所有字符,而(?!opq)o匹配不到东西
    举例:123A 456c 789 111C
                 正则:\d{3}(?![A-Z]) 将得到结果:456 789
逆向负声明?<!
(?<!opq):匹配所有位置,除了q与r中间的位置,此时:[a-z](?<!opq),就可以匹配到除q以外的所有字符,而q(?<!opq)匹配不到东西
                   (?<!opq)[a-z],就可以匹配到除r以外的所有字符,而(?<!opq)r匹配不到东西
注:Javascript不支持逆向负声明
    举例:123A 456C 789 111C
                 正则:(?<!1)\d{2}[A-Z] 将得到结果:56C
综合应用举例:
\b\w+(?=o)o\b:匹配所有以o结尾的单词
Regex.Replace("I have 1234567Yuan", @"(?<=\d)(?=(?:\d{3})+(?!\d))", ","):替换字符串里的数字为科学计数法(即3位数字一个逗号)
上面的是C#,Javascript因为不支持逆向环视,所以要用:'123456'.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, "$1,")

12、决策(也叫平衡组)是正则里的3目运算表达式,形如:(?(exp)yes|no),如果exp成立,就匹配yes,否则匹配no
(?!)表示返回匹配失败,如:(\d)(?(1).|(?!))可以匹配以数字开头的任意2个字符
举例1:
字符串:1a cb 3a 5c 3b 正则:(?(\d)\da|b) 可以匹配到结果:1a b 3a b
需要注意的是yes表达式是\da,如果是正则:(?(\d)a|b) 将只会匹配到:b b,
因为expression成立时,是匹配到这个expression的位置,后面的yes也必须要包含这个expression
当然:(?(\d)\wa|b)的匹配结果也是可以的,因为\w包含了\d
对于字符串a1234,正则(?(?<!a)\d\d|\d)可以匹配到1 和 23
举例2:引用前面的条件
字符串:10-12 z0-az 11-sd  正则:(\d)?(0)-(?(1)\d\d|[a-z][a-z]) 后面的?(1),表示前面的第1 个捕获如果匹配时,用yes匹配,否则用no匹配
这个正则可以匹配到:10-12  0-az
参考:http://blog.csdn.net/lxcnn/article/details/4402808
http://www.cnblogs.com/luckcs/articles/2212996.html
 

13、正则表达式选项,(?i:)指定括号内的匹配忽略大小写,比如正则:(?i:a) 表示匹配a或A,而不管是否指定了RegexOptions.IgnoreCase选项
(?n:):指定只有显式命名或编号的组才进行捕获,类似于RegexOptions.ExplicitCapture
(?x:):消除模式中的非转义空白并启用由 # 标记的注释,类似于RegexOptions.IgnorePatternWhitespace
(?m:):指定使用多行模式,类似于RegexOptions.Multiline
(?s:):指定使用单行模式,类似于RegexOptions.Singleline
注:选项可以叠加,比如:(?is:) 表示单行模式,忽略大小写

14、应用 | 时要注意,正则引擎总是选择第一个选项进行匹配,无法匹配时才考虑第2个选项,然后第3个,比如:
字符串:abcd  正则:a|ab 只匹配到 a       而正则:ab|a 则匹配到 ab

15、Javascript提取匹配中的内容举例,下面是在Html里循环提取超链接的Href和链接文本
var a = /<a\s+[^>]*href="([^"\s]*)"[^>]*>([\s\S]*?)<\/a>/ig;
while(a.test(html)){// 第二个test会从第一个test的lastIndex+1处开始匹配
    alert(RegExp.$1);
    alert(RegExp.$2);
}

正则举例

1、需求:如果小数在2位以内,就保持不变,如果有第3位小数,且第3位小数不是0,那也保留,如果是0就不保留,第3位以后的数字全部替换掉
比如:string str = 1.23=》1.23、1.234=1.234、1.230=》1.23、1.2345678=》1.234
此时,Regex.Replace(str, @"(\.\d\d[1-9]?)\d*", "$1")就可以实现,但是对于1.23和1.234,替换操作浪费了一点时间,因为结果相当于用23替换成23
有效率一点的做法是把正则改成:(\.\d\d(?>[1-9]?))\d+
注意里面的固化分组,如果不使用固化分组的正则:(\.\d\d[1-9]?)\d+ 在匹配1.625时,因为\d+至少要匹配一个数字,而[1-9]?可以不匹配,所以导致回溯,\d+匹配了5,导致替换结果成了1.62,与需求不符,所以这里要用固化分组,避免回溯

2、需求:匹配出字符串里的日期,比如:string str = January 31  我们要得到后面的日期31
一般我们会用正则:(0?[1-9]|[12]\d|3[01])  这个多选组合,0?[1-9]匹配01-09或1-9;[12]\d匹配10-29;3[01]匹配30-31,应该是没错的但是匹配的第一个
结果是3,而不是31      这是因为正则引擎按顺序测试组合,0?[1-9]可以匹配3,所以错误结果出现了
正确的正则应该是把能匹配最短数字的0?[1-9]放到最后,变成:([12]\d|3[01]|0?[1-9])  就OK了
或者使用下列正则之一:
(31|[123]0|[012]?[1-9])               (0[1-9]|[12]\d?|3[01]?|[4-9])

3、需求:匹配双引号和里面的内容,内容里允许出现\" 和\\这样的转义
如果内容不包含引号,那正则就是:"[^"]*"
如果加上允许转义的双引号时,我们先用逆序环视,正则变成:"([^"]|(?<=\\)")*",这个表示式可以匹配 "aa\"bb" 这样的文本,但是对于"aa\\" and "bb",它的匹配结果是错误的,因为它把转义的\\后面的这个斜杠去环视了,所以这个正则不能用
再改用:"(\\.|[^"])*",就是匹配\和一个字符,或非双引号,对于上面的这回可以匹配了,不过,对于没有结束双引号的字符串:"aa\"bb,它又匹配到了"aa\",因为正则引擎的回溯到\"时,[^"]能匹配前面的\,于是就返回了匹配成功
所以最终的正则应该是:"([^"\\]|\\.)*"   或者使用固化分组:"(?>([^"]|\\.)*)"
注:这个正则顺序交换一下,变成:"(\\.|[^"\\])*"  也是可以的,但是这个正则回溯比不交换前多,参见下图

4、需求:替换字符串前后的空白(C#的Trim方法已经可以实现,但是js没有这个功能)
在网上最常见的作法是用正则: (^\s*)|(\s*$)  可以搜索 Javascript trim,得到一大堆的类似结果如下:
String.prototype.trim= function(){return this.replace(/(^\s*)|(\s*$)/g, "");} 
这个当然没有问题,但是这个正则是可以改进的,首先,用\s*,这样也可以匹配空,没有空白的字符串也会进行2次替换,
    而且正则里有2个捕获,而实际上捕获没有使用到,
所以比较好的作法是把*改成+,并去掉括号,用:String.prototype.trim= function(){return this.replace(/^\s+|\s+$/g, "");}
注:虽然这点改进很小,但是如果不是替换成"",那结果就出错了,并且在做任何工作时,都想到这么一点点,那总的提升效率还是很多的

5、需求:匹配HTML标签,允许标签中的属性值包含<或>,例如:<input value="a>bc" type="text">
如果没有后面那个要求,那么匹配HTML标签,就是简单的:<[^>]+>
根据要求,我们可以知道,属性值是包含在单引号或双引号里的,所以可以得到下面正则:
<("[^"]*"|'[^']*'|[^'">])*> 

6、需求:匹配嵌套div标签的最内层,例如:<div><div>2<div>3</div></div>1<div>2<div>3<a>3</a>3</div></div></div>,取出3的div
首先自然是两端的正则:<div[^>]*>.*?</div>,这个得到结果:<div><div>2<div>3</div>
那么要求在中间不能出现<div字样,用(?:(?!<div).)匹配前面不等于<div的任意一个字符,得到最终正则如下:
<div[^>]*>(?:(?!<div).)*?</div> 

7、需求:匹配嵌套div标签的最外层
 div>1<div>2<div>3<div>4</div>5 匹配到<div>4</div>
div>1<div>2<div>3<div>4</div>5</div> 匹配到<div>3<div>4</div>5</div>
div>1<div>2<div>3<div>4</div>5</div>6</div>7</div>8</div> 匹配到<div>2<div>3<div>4</div>5</div>6</div>
首先自然是两端的正则:<div> 和 </div>,
然后(?<o><div>)|(?<-o></div>)表示匹配div和相应的结束div,<o>表示把捕获压入堆栈,<-o>表示取出堆栈,堆栈没有数据就表示匹配失败,第三部分(?:(?!</?div)[\s\S])表示不包含div的任意字符,最后还有一块(?(o)(?!))表示如果堆栈中还有div,匹配失败
最终正则如下:
<div>((?<o><div>)|(?<-o></div>)|(?:(?!</?div)[\s\S]))*(?(o)(?!))</div>

8、需求:匹配有效的物理路径,如 c:\ d:/abc/ddd.txt e:\\\\abc////\\kk.exe(注:连续的\或/都被Windows认为是一个\,所以有效)
物理路径自然要以盘符开头,所以正则开始是:^[a-zA-Z]:[\\/]+
接着是后面的子目录,目录或文件名按Windows规定,不允许出现<>/\|:"*? 以及 回车换行共11个字符,所以匹配子目录的正则是:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+,因为可能有多级子目录,也可能没有子目录,所以匹配全部子目录的正则就是:(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*,最后完整的正则就是:
^[a-zA-Z]:[\\/]+(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*[^\<\>\/\\\|\:""\*\?\r\n]*$

9、需求:输入6~20位的密码,要求必须是大写字母、小写字母和数字的组合
完整的正则是:
^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])[a-zA-Z0-9]{6,20}$
另外一个使用平衡组的正则:
^(?:([0-9])|([a-z])|([A-Z])){6,20}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))$

10、需求:匹配HTML的a标签里的href,要求支持单引号、双引号或无引号的可能
可能的数据有:<a href='xxx' target='_self'> <a href=xxx target='_self'> <a href="xxx" target='_self'><a href="javascript:alert('1')"><a href='javascript:alert("1")'>
首先自然是匹配到href的正则:<a\s[^>]*href=,接着匹配引号,因为可能没引号,因此是:(['"])?,后面要判断出现了引号没有,用一个三目运算符:(?(1)yes|no)
如果前面出现了引号,用正则:(?:(?!\1).)*\1,表示不等于前面引号的多个字符+前面的引号(\1表示引用)
完整的正则是(当然这个正则没有考虑更复杂的情况,比如<a href="javascript:alert(\"1\")">,这个等你来扩展吧):
<a\s[^>]*href=(['"])?(?(1)((?:(?!\1).)*)\1|([^\s>]*))

评论列表(2条)
by: dfg
2011-08-13 11:31:49
by: (csdn)苦苦的潜行者
2011-12-14 22:35:14
很好的文章,在csdn能上首页了! 果断转载了,lz不介意吧!?

主人于2011-12-22 14:27:57回复:
哈,原来一直在那里写博客,现在有自己的空间,就在自己这里写了,感谢支持
发表评论
名称(*):
邮箱:
正文:

©2008 Beinet.cn 版权所有