Javascript正则表达式教程.docx
《Javascript正则表达式教程.docx》由会员分享,可在线阅读,更多相关《Javascript正则表达式教程.docx(17页珍藏版)》请在冰豆网上搜索。
![Javascript正则表达式教程.docx](https://file1.bdocx.com/fileroot1/2023-8/13/1bb79038-d9f0-4b38-8a90-3de4255b5e8b/1bb79038-d9f0-4b38-8a90-3de4255b5e8b1.gif)
Javascript正则表达式教程
Javascript正则表达式教程
1.什么是正则表达式
简单的说,正则表达式是一种可以用于文字模式匹配和替换的强有力的工具。
是由一系列普通字符和特殊字符组成的能明确描述文本字符串的文字匹配模式。
正则表达式并非一门专用语言,但也可以看作是一种语言,它可以让用户通过使用一系列普通字符和特殊字符构建能明确描述文本字符串的匹配模式。
除了简单描述这些模式之外,正则表达式解释引擎通常可用于遍历匹配,并使用模式作为分隔符来将字符串解析为子字符串,或以智能方式替换文本或重新设置文本格式。
正则表达式为解决与文本处理有关的许多常见任务提供了有效而简捷的方式。
正则表达式具有两种标准:
· 基本的正则表达式(BRE–BasicRegularExpressions)
· 扩展的正则表达式(ERE–ExtendedRegularExpressions)。
ERE包括BRE功能和另外其它的概念。
正则表达式目前有两种解释引擎:
· 基于字符驱动(text-directedengine)
· 基于正则表达式驱动(regex-directedengine)
JefferyFriedl把它们称作DFA和NFA解释引擎。
约定:
为了描述起来方便,在本文中做一些约定:
1. 本文所举例的所有表达时都是基于NFA解释引擎的。
2. 正则表达式,也就是匹配模式,会简写为Regex。
3. Regex的匹配目标,也就是目标字符串,会简写为String。
4. 匹配结果用会用黄色底色标识。
5. 用1\+1=2括起来的表示这是一个regex。
6. 举例会用以下格式:
test
Thisisatest
会匹配test,testcase等
2.正则表达式的起源
正则表达式的“祖先”可以一直上溯至对人类神经系统如何工作的早期研究。
WarrenMcCulloch和WalterPitts这两位神经生理学家研究出一种数学方式来描述这些神经网络。
1956年,一位叫StephenKleene的美国数学家在McCulloch和Pitts早期工作的基础上,发表了一篇标题为“神经网事件的表示法”的论文,引入了正则表达式的概念。
正则表达式就是用来描述他称为“正则集的代数”的表达式,因此采用“正则表达式”这个术语。
随后,发现可以将这一工作应用于使用KenThompson的计算搜索算法的一些早期研究,KenThompson是Unix的主要发明人。
正则表达式的第一个实用应用程序就是Unix中的qed编辑器。
从那时起直至现在正则表达式都是基于文本的编辑器和搜索工具中的一个重要部分。
具有完整语法的正则表达式使用在字符的格式匹配方面上,后来被应用到熔融信息技术领域。
自从那时起,正则表达式经过几个时期的发展,现在的标准已经被ISO(国际标准组织)批准和被OpenGroup组织认定。
3.正则表达式使用祥解
最简单的正则表达式相信大家都已熟悉并且经常使用,那就是文字字符串。
特定的字符串可通过文字本身加以描述;像test这样的Regex模式可精确匹配输入的字符串”test”,但是它也可以匹配thisisatestcase,这就不是我们想要得结果。
当然,使用正则表达式匹配等于它自身的精确字符串是没有价值的实现,不能体现正则表达式的真正作用。
但是,假如要查找的不是test,而是所有以字母t开头的单词,或所有4个字母的单词,那该怎么办?
这超出了文字字符串的合理范围。
所以我们才需要深入地研究正则表达式。
3.1基本语法
虽然正则表达式并非一门专用语言,但它也有一些特殊的规定,也可以称之为基本语法。
正则表达式是由普通字符(例如字符a到z)以及特殊字符(称为元字符)组成的文字模式。
该模式描述在查找文字主体时待匹配的一个或多个字符串。
正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
构造正则表达式的方法和创建数学表达式的方法一样。
也就是用多种元字符与操作符将小的表达式结合在一起来创建更大的表达式。
可以通过在一对分隔符之间放入表达式模式的各种组件来构造一个正则表达式。
3.1.1普通字符
由所有那些未显式指定为元字符的打印和非打印字符组成。
这包括所有的大写和小写字母字符,所有数字,所有标点符号以及一些符号。
3.1.2非打印字符
非打印字符也是普通字符,单独列出来便于参考。
非打印字符参考(有错大家矫正,JS手册都有)
\cx
匹配由x指明的控制字符。
例如,\cM匹配一个Control-M或回车符。
x的值必须为A-Z或a-z之一。
否则,将c视为一个原义的'c'字符。
\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。
Regex中可以使用非打印字符。
\t会匹配一个tab字符(ASC||),\r会匹配一个回车(0x0D),\n会匹配一个换行符(0x0A)。
应该注意的是:
Windows使用\r\n表示一行的结束,而UNIX使用\n。
同样,我们可以在Regex中使用16进制的ASCⅡ码或者ANSI标准码。
在拉丁语中,版权符号的代码是0xA9,所以我们也可以这样来匹配版权符号\xA9。
另外一个匹配tab的写法是:
\x09。
但是注意,第一位的“0”必须去掉。
特殊字符
特殊字符也叫做元字符,保留字符(Metacharactor),在Regex中表示特殊的意义,大部分的意思在不同的上下文中的意义是不同的,这里只列出最普遍的意义。
特殊字符共有11个:
$
匹配输入字符串的结尾位置。
如果设置了RegExp对象的Multiline属性,则$也匹配'\n'或'\r'。
要匹配$字符本身,请使用\$。
()
标记一个子表达式的开始和结束位置。
子表达式可以获取供以后使用。
要匹配这些字符,请使用\(和\)。
*
匹配前面的子表达式零次或多次。
要匹配*字符,请使用\*。
+
匹配前面的子表达式一次或多次。
要匹配+字符,请使用\+。
.
匹配除换行符\n之外的任何单字符。
要匹配.,请使用\。
[
标记一个中括号表达式的开始。
要匹配[,请使用\[。
?
匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。
要匹配?
字符,请使用\?
。
\
将下一个字符标记为或特殊字符、或原义字符、或反向引用、或八进制转义符。
例如,'n'匹配字符'n'。
'\n'匹配换行符。
序列'\\'匹配"\",而'\('则匹配"("。
^
匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。
要匹配^字符本身,请使用\^。
{
标记限定符表达式的开始。
要匹配{,请使用\{。
|
指明两项之间的一个选择。
要匹配|,请使用\|。
在元字符前加\转义符,可以把特殊字符当作普通字符来使用。
比如:
如要要匹配1+1=2,正确的正则表达式应该为1\+1=2。
否则,+会被当作特殊字符对待。
除了特殊字符,所有的其他字符都不应该加\。
因为\也是一个特殊字符。
\和普通字符组合在一起也可以创造一种特殊的意义。
比如\d表示匹配所有的数字。
作为程序员,单引号和双引号不是特殊字符会也许让我们感到很惊讶。
但这是正确的。
因为我们在编程的时候,编程语言会知道引号之间的哪些字符表示特殊意义,编译器在把字符串x传递给regex解释引擎之前,会把它们处理成regex。
比如,在C#中,如果我们要匹配1\+1=2,在程序中我们要这样写:
“1\\+1=2”,C#编译器会把“\\”,处理为一个“\”。
同样,如果要匹配C:
\Temp,首先,正则表达式要这样写C:
\\Temp,然后在程序中我们应该这样写:
“C:
\\\\temp”。
3.1.4字符集
字符集描述了一组字符,Regex解释器会认为匹配字符集中的一个字符就可以认为匹配成功。
字符集用[]括起来即可。
比如gr[ae]y就可以匹配gray或者grey。
字符集只能匹配一个字符,gr[ae]y就不能和graey匹配。
字符集中的字符顺序是任意的,得到的结果都是唯一的。
可以在字符集中用连字符“-”来表示一个范围。
[0-9]的结果和[0123456789]的匹配结果都是相同的。
字符集中的范围可以有多种。
比如[0-9a-fA-F]表示匹配所有的16进制,包括大小写。
也可以把范围和单个字符组合在一起用,[0-9a-fxA-FX]表示匹配所有的16进制或者一个字符X。
字符集的顺序不会影响结果。
在字符集的开始标志“[”后面加上一个“^”符号,表示否定,表示匹配除字符集中定义的字符以外的所有字符。
包括非打印字符和行结束符。
注意:
字符集匹配的一个字符,而不是一个位置。
所以。
q[^u]的意义不是“q后面的字符不是u”。
而是“q后面的字符可以是除了u以外的所有字符”。
q[^u]不会和Iraq匹配。
但是会和Iraqisacountry匹配,因为q后面的空格字符是一个“不是u的字符”。
3.1.5在字符集中使用元字符
字符集中的元字符只能是‘]’,‘\’,‘^’,和‘-‘。
其他元字符在字符集中都失去了特殊意义,表示的只是一个普通字符。
也不需要用加“\”。
比如:
匹配一个“*”或者“+”,用[*+]就足够了。
即使给他们加上“\”,regex解释器也会把他们忽略掉。
四种特殊字符的处理:
在字符集中要表示“]”,“^”和“-”需要在后面加上转义符“\”,来表示它们代表的分别是普通字符“]”,“^”和“-”。
也可以把它们放在一个不能表示特殊意义的位置,后一个方法比较好,因为他们不会影响可读性。
“^”
要想匹配一个“^”,可以把它放在除了紧跟“[”的任意一个位置。
Regex
String
Description
[x^]
Astringwithxand^.
匹配x或者“^”
“]”
可以把“]”放在紧跟着“[”的位置,或者使用否定字符集。
Regex
String
Description
[]x]
Astringwithxand]
匹配x或者“]”
[^]x]
Astringwithxand]
匹配除了x和”]”以外的所有字符
“\”
要想把“\”当作一个普通字符来匹配,而不是一个特殊字符,必须把“\”再用一个“\”括起来。
[\\x]
Astringwithxand\
匹配x或者“\”
“-”
连字符可以放在紧跟着“[”的后面,或者正好“]”的前面,或者紧跟着“^”的后面。
[-x]
Astringwithxand-
匹配x或者“-”
[x-]
Astringwithxand-
匹配x或者“-”
预定义字符集
因为很多字符集是经常使用的,所以Regex解释器预定义了一些常用字符集:
d
[0-9]
所有数字
\w
[a-zA-Z]
表示所有的字符,和文化字体有关
\s
[\t\r\n]
空格,回车和tab。
和文化字体有关
预订一字符集可以既可以用在字符集里面,也可以用在字符集外面。
\s\d
1+2=3
匹配后面紧跟着一个数字的空白符
[\s\d]
1+2=3
匹配一个单独的字符或者一个数字或者一个空白符
[\da-fA-F]和[0-9a-fA-F]的匹配结果是一样的。
同样,在预定义字符集前面加一个“^”符号表示否定。
它们也有预先定义好的表示:
\D
[^\d]
非数字
\W
[^\w]
非字符,和文化字体有关
\S
[^\s]
非空格,回车和tab。
和文化字体有关
在“[]”使用否定预订一字符集时要特别心。
[\D\S]不等于[^\d\s]。
[^\d\s]会匹配除了数字和空白符以外的所有字符。
而[\D\S]会匹配要么不是一个数字,要么是空白符。
因为数字不是空白符,空白符也不是数字,所以[\D\S]会匹配任意的字符。
限定符
有时候不知道要匹配多少字符。
为了能适应这种不确定性,正则表达式支持限定符的概念。
这些限定符可以指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。
下表给出了各种限定符及其含义的说明:
*匹配前面的子表达式零次或多次。
例如,zo*能匹配"z"以及"zoo"。
*等价于{0,}。
+匹配前面的子表达式一次或多次。
例如,'zo+'能匹配"zo"以及"zoo",但不能匹配"z"。
+等价于{1,}。
?
匹配前面的子表达式零次或一次。
例如,"do(es)?
"可以匹配"do"或"does"中的"do"。
?
等价于{0,1}。
{n}n是一个非负整数。
匹配确定的n次。
例如,'o{2}'不能匹配"Bob"中的'o',但是能匹配"food"中的两个o。
{n,}n是一个非负整数。
至少匹配n次。
例如,'o{2,}'不能匹配"Bob"中的'o',但能匹配"foooood"中的所有o。
'o{1,}'等价于'o+'。
'o{0,}'则等价于'o*'。
{n,m}m和n均为非负整数,其中n<=m。
最少匹配n次且最多匹配m次。
刘,"o{1,3}"将匹配"fooooood"中的前三个o。
'o{0,1}'等价于'o?
'。
请注意在逗号和两个数之间不能有空格。
对一个很大的输入文档而言,章节数很轻易就超过九章,因此需要有一种方法来处理两位数或者三位数的章节号。
限定符就提供了这个功能。
下面的JScript正则表达式可以匹配具有任何位数的章节标题:
/Chapter[1-9][0-9]*/
下面的VBScript正则表达式执行同样的匹配:
"Chapter[1-9][0-9]*"
请注意限定符出现在范围表达式之后。
因此,它将应用于所包含的整个范围表达式,在本例中,只指定了从0到9的数字。
这里没有使用'+'限定符,因为第二位或后续位置上并不一定需要一个数字。
同样也没有使用'?
'字符,因为这将把章节数限制为只有两位数字。
在'Chapter'和空格字符之后至少要匹配一个数字。
如果已知章节数限制只有99章,则可以使用下面的JScript表达式来指定至少有一位数字,但不超过两个数字。
/Chapter[0-9]{1,2}/
对VBScript可以使用下述正则表达式:
"Chapter[0-9]{1,2}"
上述表达式的缺点是如果有一个章节号大于99,它仍只会匹配前两位数字。
另一个缺点是某些人可以创建一个Chapter0,而且仍能匹配。
一个更好的用来匹配两位数的JScript表达式如下:
/Chapter[1-9][0-9]?
/
或者
/Chapter[1-9][0-9]{0,1}/
对VBScript而言,下述表达式与上面等价:
"Chapter[1-9][0-9]?
"
或者
"Chapter[1-9][0-9]{0,1}"
'*'、'+'和'?
'限定符都称之为贪婪的,也就是说,他们尽可能多地匹配文字。
有时这根本就不是所希望发生的情况。
有时则正好希望最小匹配。
例如,你可能要搜索一个HTML文档来查找一处包含在H1标记中的章节标题。
在文档中该文字可能具有如下形式:
Chapter1–IntroductiontoRegularExpressions
下面的表达式匹配从开始的小于号(<)到H1标记结束处的大于号之间的所有内容。
/<.*>/
VBScript的正则表达式为:
"<.*>"
如果所要匹配的就是开始的H1标记,则下述非贪婪地表达式就只匹配
。
/<.*?
>/
或者
"<.*?
>"
通过在'*'、'+'或'?
'限定符后放置'?
',该表达式就从贪婪匹配转为了非贪婪或最小匹配。
我们都明白
()可以为以规则匹配后获取匹配集合,用RegExp.$1-$9来获取;
但规则匹配以非贪婪限制匹配后,将无匹配集合返回;
我们来讨论下非贪婪匹配与贪婪匹配
首先
(?
:
pattern)
匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。
这在使用"或"字符(|)来组合一个模式的各个部分是很有用。
例如,'industr(?
:
y|ies)就是一个比'industry|industries'更简略的表达式。
(?
=pattern)
正向预查,在任何匹配pattern的字符串开始处匹配查找字符串。
这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
例如,'Windows(?
=95|98|NT|2000)'能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。
预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?
!
pattern)
负向预查,在任何不匹配Negativelookaheadmatchesthesearchstringatanypointwhereastringnotmatchingpattern的字符串开始处匹配查找字符串。
这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
例如'Windows(?
!
95|98|NT|2000)'能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。
预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
什么是贪婪匹配?
“贪婪匹配”原则是说,当解释器将代码中的字符解析成一个个的编译器在处理代码时眼中看到的最小语法单元时,编译器会使用一种贪婪匹配算法,也就是说会尽可能让一个单元包含更多的字符。
非贪婪匹配的效率
可能有不少的人和我一样,有过这样的经历:
当我们要匹配类似"
内容 | "或者"加粗"这样的文本时,我们根据正向预搜索功能写出这样的表达式:
"
([^<]|<(? ! /td>))* | "或者"
((? ! | ).)*"。
当发现非贪婪匹配之时,恍然大悟,同样功能的表达式可以写得如此简单:
"
.*? | "。
顿时间如获至宝,凡是按边界匹配的地方,尽量使用简捷的非贪婪匹配".*?
"。
特别是对于复杂的表达式来说,采用非贪婪匹配".*?
"写出来的表达式的确是简练了许多。
然而,当一个表达式中,有多个非贪婪匹配时,或者多个未知匹配次数的表达式时,这个表达式将可能存在效率上的陷阱。
有时候,匹配速度慢得莫名奇妙,甚至开始怀疑正则表达式是否实用。
效率陷阱的产生:
非贪婪匹配:
“如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,非贪婪模式会最小限度的再匹配一些,以使整个表达式匹配成功。
”
具体的匹配过程是这样的:
"非贪婪部分"先匹配最少次数,然后尝试匹配"右侧的表达式"。
如果右侧的表达式匹配成功,则整个表达式匹配结束。
如果右侧表达式匹配失败,则"非贪婪部分"将增加匹配一次,然后再尝试匹配"右侧的表达式"。
如果右侧的表达式又匹配失败,则"非贪婪部分"将再增加匹配一次。
再尝试匹配"右侧的表达式"。
依此类推,最后得到的结果是"非贪婪部分"以尽可能少的匹配次数,使整个表达式匹配成功。
或者最终仍然匹配失败。
当一个表达式中有多个非贪婪匹配,以表达式"d(\w+?
)d(\w+?
)z"为例,对于第一个括号中的"\w+?
"来说,右边的"d(\w+?
)z"属于它的"右侧的表达式",对于第二个括号中的"\w+?
"来说,右边的"z"属于它的"右侧的表达式"。
当"z"匹配失败时,第二个"\w+?
"会"增加匹配一次",再尝试匹配"z"。
如果第二个"\w+?
"无论怎样"增加匹配次数",直至整篇文本结束,"z"都不能匹配,那么表示"d(\w+?
)z"匹配失败,也就是说第一个"\w+?
"的"右侧"匹配失败。
此时,第一个"\w+?
"会增加匹配一次,然后再进行"d(\w+?
)z"的匹配。
循环前面所讲的过程,直至第一个"\w+?
"无论怎么"增加匹配次数",后边的"d(\w+?
)z"都不能匹配时,整个表达式才宣告匹配失败。
其实,为了使整个表达式匹配成功,贪婪匹配也会适当的“让出”已经匹配的字符。
因此贪婪匹配也有类似的情况。
当一个表达式中有较多的未知匹配次数的表达式时,为了让整个表达式匹配成功,各个贪婪或非贪婪的表达式都要进行尝试减少或增加匹配次数,由此容易形成一个大循环的尝试,造成了很长的匹配时间。
本文之所以称之为“陷阱”,因为这种效率问题往往不易察觉。
举例:
"d(\w+?
)d(\w+?
)d(\w+?
)z"匹配"ddddddddddd..."时,将花费较长一段时间才能判断出匹配失败。
效率陷阱的避免:
避免效率陷阱的原则是:
避免“多重循环”的“尝试匹配”。
并不是说非贪婪匹配就是不好的,只是在运用非贪婪匹配的时候,需要注意避免过多“循环尝试”的问题。
情况一:
对于只有一个非贪婪或者贪婪匹配的表达式来说,不存在效率陷阱。
也就是说,要匹配类似"
内容 | "这样的文本,表达式"
([^<]|<(? ! /td>))* | "和"
((? ! | ).)*"和"
.*? | "的效率是完全相同的。
情况二:
如果一个表达式中有多个未知匹配次数的表达式,应防止进行不必要的尝试匹配。
比如,对表达式")'>(.*?
)"来说,如果前面部分表达式在遇到""时匹配成功后,而后边的"(.*?
)"却匹配失败,将导致第一个".*?
"增加匹配次数再尝试。
而对于表达式真正目的,让第一个".*?
"增加匹配成“vbscript'>”是不对的,因此这种尝试是不必要的尝试。
因此,对依靠边界来识别的表达式,不要让未知匹配次数的部分跨过它的边界。
前面的表达式中,第一个".*?
"应该改写成"[^']*"。
后边那个".*?
"的右边再没有未知匹配次数的表