正则表达式的概念:
用来匹配和处理文本的字符串。人们常用模式(pattern)来表示实际的正则表达式。正则表达式是由正则表达式语言创建的。正则表达式语言是内置于其他语言或软件产品里的”迷你“语言,但它并不是一种完备的程序设计语言。不同的编程语言或应用程序里,正则表达式的语法和功能会有所不同。
正则表达式的用途:
(1)搜索(匹配):在一个字符串中搜索出一个或多个与正则表达式相匹配的子字符串。搜索又分为匹配和子字符串搜索。匹配是对用户所提供的整个字符串进行判断,看其是否匹配正则表达式,比如电子邮件地址的匹配。子字符串搜索是“搜索”的普遍含义,指的是将与正则表达式相匹配的所有子字符串找出来,比如将一段英文文本中的所有单词给找出来。
(2)替换(匹配并替换):将一个字符串中与正则表达式相匹配的子字符串找出来并替换成另一些子字符串,比如将一个字符串中的所有的cos替换成sin。
匹配单个字符
(1)匹配纯文本
正则表达式里可以包含纯文本(甚至可以只包含纯文本)。
匹配纯文本时可能会有多个匹配结果,绝大多数的正则表达式的实现都提供了一种能够将所有的匹配结果都找出来的机制(通常是返回一个数组)。
Java中的正则表达式是区分大小写的。
文本:
Hello,my name is Ben.
正则表达式:
Ben
结果:
Hello,my name is Ben.
(2)匹配任意字符
.字符可以匹配任意一个单个的字符(字符、字母、数字、.字符本身,与行结束符可能匹配也可能不匹配)。
(3)匹配特殊字符
这里的特殊字符指的是正则表达式里的特殊字符,注意与Java语言里的转义字符相区分。
正则表达式里的特殊字符指的是该字符在正则表达式中有其特殊的含义,不是简单的纯文本。
如果要匹配特殊字符本身,就需要在在特殊字符的开头就加上\。不过在Java里到底要加多少个\,这个问题是比较令人头疼的,具体解析详见[关于JAVA正则表达式里的](http://zhidao.baidu.com/question/1637942975153285780.html)。
正则表达式中需要转义的特殊字符:
$ 匹配输入字符串的结尾位置。
( ) 标记一个子表达式的开始和结束位置。
* 匹配前面的子表达式零次或多次。
+ 匹配前面的子表达式一次或多次。
. 匹配除换行符 \n之外的任何单字符。
[ ] 标记一个中括号表达式的开始。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。
\ 转义下一个字符标记。
^ 匹配输入字符串的开始位置。
{ } 标记限定符表达式的开始。
| 指明两项之间的一个选择。
Java语言中转义字符:
1.八进制转义序列:
\0n 带有八进制值 0 的字符 n (0 <= n <= 7)
\0nn 带有八进制值 0 的字符 nn (0 <= n <= 7)
\0mnn 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
2.十六进制转义序列:
\xhh 带有十六进制值 0x 的字符 hh
\uhhhh 带有十六进制值 0x 的字符 hhhh
3. 特殊字符:
\" 双引号
\' 单引号
\\ 反斜线
\a 报警 (bell) 符
\e 转义符
\cx 对应于x的控制符
4. 控制字符:
\t 制表符 ('\u0009')
\n 换行符 ('\u000A')
\r 回车符 ('\u000D')
\f 换页符 ('\u000C')
匹配一组字符
(1)匹配多个字符中的某一个
使用元字符[]来定义一个字符集合,这两个元字符之间的所有字符都是该集合的组成部分,字符集合匹配结果是能够与该集合里的任意一个字符相匹配的字符。
文本:
The phrase “regular expression” is often abbreviates as RegEx or regex.
正则表达式:
[Rr]eg[Ee]x
结果:
The phrase “regular expression” is often abbreviates as RegEx or regex.
(2)利用字符集合区间
为了简化字符区间的定义,正则表达式提供了一个特殊的元字符-,字符区间可以用-(连字符)来定义。
字符区间的首、尾字符可以是ASCII字符表里的任意字符,但在实际的应用中,常用的还是数字字符区间和 字母字符区间。
在定义一个字符区间的时候一定要避免让这个字符区间的尾字符小于它的首字符。
在同一个字符集合里可以有多个字符区间。
-字符是一个特殊的元字符,作为元字符它只能是用在[]之间。在字符集合以外的地方,-只是一个普通的字符,只能与-本身相匹配,因此,在正则表达式里,-字符不需要被转移。
(3)取非匹配
用元字符^来对一个字符区间进行取非匹配。表示出了那个字符集合里的字符,其他字符都可以匹配。
^的效果将作用于给定字符集合里的所有字符和字符区间,而不是仅限于紧跟在^字符后的那一个字符或字符区间。
使用元字符
元字符大致可分为两种,一种是用来匹配文本的(比如.),另一种是正则表达式的语法所要求的(比如[和])。
某些元字符之间的差异:.和[是元字符,但前提是你没有对它进行转义;t和n也是元字符,但前提是你对它进行了转义。
(1)对特殊字符进行转义
任何一个元字符都可以通过给它加上一个\字符作为前缀的办法来转义。
(2)匹配空白字符
\r\n是Windows操作系统所使用的文本行结束标签;
\n是Unix和Linux操作系统所使用的文本行结束标签。
空白元字符:
[\b] 回退(并删除)一个字符(Backspace键)
\f 换页符
\n 换行符
\r 回车符
\t 制表符(Tab键)
\v 垂直制表符(\x0B)
(3)匹配特定的字符类别
\d 任何一个数字字符(等价于[0-9])
\D 任何一个非数字字符(等价于[^0-9])
\w 任何一个数字、字母、下划线字符(等价于[a-zA-Z0-9_])
\W 任何一个非(数字、字母、下划线字符)(等价于[^a-zA-Z0-9_])
\s 任何一个空白字符(等价于[\f\n\r\t\v])
\S 任何一个非空白字符(等价于[^\f\n\r\t\v])
(4)在Java中使用POSIX字符类
\p{Lower} 小写字母字符:[a-z]
\p{Upper} 大写字母字符:[A-Z]
\p{ASCII} 所有 ASCII:[\x00-\x7F]
\p{Alpha} 字母字符:[\p{Lower}\p{Upper}]
\p{Digit} 十进制数字:[0-9]
\p{Alnum} 字母数字字符:[\p{Alpha}\p{Digit}]
\p{Punct} 标点符号:!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
\p{Graph} 可见字符:[\p{Alnum}\p{Punct}]
\p{Print} 可打印字符:[\p{Graph}\x20]
\p{Blank} 空格或制表符:[ \t]
\p{Cntrl} 控制字符:[\x00-\x1F\x7F]
\p{XDigit} 十六进制数字:[0-9a-fA-F]
\p{Space} 空白字符:[ \t\n\x0B\f\r]
重复匹配
(1)有多少个匹配
再给一个字符集合加上+后缀的时候,必须把+放在这个字符集合的外面。比如说:[0-9]+是正确的,[0-9+]则不是。[0-9+]其实也是合法的正则表达式,但它匹配的不是一个或多个数字,它定义了一个由数字0到9和+构成的字符集合。*和?依次类推。
+是一个元字符。如果需要匹配+本身,就必须使用它的转义序列+。*和?依次类推。
当在字符集合里使用的时候,像.和+这样的元字符将被解释成普通字符,不需要被转义,但转义了也没有坏处。即[.]的使用效果与[.]是一样的。
1.匹配一个或多个字符 +
2.匹配零个或多个字符 *
3.匹配零个或一个字符 ?
(2)匹配的重复次数
1.为重复匹配的次数设定一个精确的值,例如[0-9]{3}。
2.为重复匹配的次数设定一个区间,例如[0-9]{3,5},注意是逗号,不是连接符。
3.匹配”至少重复多少次“,例如[0-9]{3,},注意别漏了逗号。
(3)防止过度匹配
文本:
This offer is not available to customers living in < B>AK and < B>HI< /B>
正则表达式:
<[Bb]>.*< /[Bb]>
结果:
This offer is not available to customers living in < B>AK< /B> and < B>HI< /B>
解释:
因为*和+都是所谓的“贪婪型”元字符,它们在进行匹配时的行为模式是多多益善而不是适可而止。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头到碰到第一个匹配时为止。
与贪婪模式相反的额是懒惰模式,只要给“贪婪型”字符加上一个?后缀即可。
“贪婪型”元字符 “懒惰型”元字符
* *?
+ +?
{n,} {n,}?
文本:
This offer is not available to customers living in < B>AK and < B>HI< /B>
正则表达式:
<[Bb]>.*?< /[Bb]>
结果:
This offer is not available to customers living in < B>AK< /B> and < B>HI< /B>
位置匹配
如果只需要对某段文本里的特定位置进行匹配,通过位置匹配可以解决这个问题,也就是使用边界限定符。在正则表达式里用一些特殊的元字符来表明想让匹配操作在什么位置(可以理解成边界)发生。边界分为单词边界、非单词边界、行边界、字符串边界。
^ 字符的开头(在(?m)限定下为行的开头)
$ 字符串的结尾 (在(?m)限定下为行的结尾)
\b 单词边界(包括开头和结尾)
\B 非单词边界(包括开头和结尾)
\A 字符串的开头
\Z 字符串的结尾
(1)单词边界
由限定符\b指定的单词边界 (b 指 boundary边界), \b用来匹配一个单词的开始或结尾。
\b 匹配的是这样一个位置: 这个位置位于一个能够用来构成单词的字符(字母、数字和下划线) 和一个不能用来构成单词的字符 之间。
\b只匹配一个位置,不匹配任何字符。用\b cat \b匹配到的字符串是长度为3的字符串(c、a、t),不是长度为5的字符串。
如果要匹配一个完整的单词,必须在要匹配的文本的前后都加上\b限定符。
文本:
The cat scattered his food all over the room
正则表达式:
\bcat\b
结果:
The cat scattered his food all over the room
(2)非单词边界
如果不匹配一个单词边界使用\B
文本:
Please enter the nine@@@digit id as it appears on your color @@@ coded pass-key
正则表达式:
\B@@@\B
结果:
Please enter the nine@@@digit id as it appears on your color @@@ coded pass-key
(3)字符串边界
用来定义字符串边界的元字符有: 一个用来定义字符串开头的 ^, 另一个是用来定义字符串结尾的 $.
^ 是几个有着多种用途的元字符之一。只有当它出现在一个字符集合里(放在[和]之间) 并紧路在左方括号 [ 的后面时,它才能发挥 “求非” 作用。如果是在一个字符集合的外面并位于一个模式的开头, ^ 将匹配字符串的开头.
用来定义字符串边界的元字符还有: 一个用来定义字符串开头的 \A, 另一个是用来定义字符串结尾的 \Z.
下面的例子只是示范了^元字符的用法,并没有示范元字符的用法。元字符的用法类似。
文本:
<wsdl:definitions targetNamespace=http://tips.cf>
正则表达式:
^\s*<\?xml.*?\?>
结果:
文本的第一行将会被匹配
解释:
在这里 ^\s* 将匹配一个字符串的开头位置和随后的零或多个空白字符, 注意还用到了懒惰型元字符。
(4)行边界
正则表达式支持使用一些特殊的元字符去改变另外一些元字符行为的做法,用来启用分行匹配模式(multiline mode) 的 (?m) 记号就是一个能够改变其他元字符行为的元字符序列。
默认情况下,正则表达式 ^ 和 忽略行结束符,仅分别与整个输入序列(即整个字符串)的开头和结尾匹配。通过嵌入式标志表达式(?m)可以启用多行模式。分行匹配模式将使得正则表达式引擎把行分隔符当作字符串分隔符来对待。在分行匹配模式下,不仅匹配正常的字符串结尾,还匹配行分隔符后面的结束位置。
但是\A和\Z不受分行匹配模式的影响。
在使用时, (?m) 必须出现在整个模式的最前面。
子表达式
子表达式是一个更强大的表达式的一部分,把一个表达式划分为一系列的字表达式的木的时为了把那些子表达式当作一个独立的元素来使用。子表达式必须用()括起来。
子表达式的用途:对子表达式的重复次数做出精确的设定和控制、对|操作符的OR条件做出准确的定义、回溯引用。
(和)都是元字符。如果需要匹配(和)本身,就必须使用它的转义序列(和)。
子表达式还允许嵌套使用。
(1)对子表达式的重复次数做出精确的设定和控制
文本:
ping www.baidu.com [192.168.1.200]
正则表达式:
(\d{1,3}.){3}\d{1,3}
结果:
ping www.baidu.com [192.168.1.200]
(2)正则表达式的或操作符|和子表达式的嵌套使用
文本:
ping www.baidu.com [192.168.1.200]
正则表达式:
(((\d{1,2})|(1\d{2})|(24\d)|(25[0-5])).){3}((\d{1,2})|(1\d{2})|(24\d)|(25[0-5]))
结果:
ping www.baidu.com [192.168.1.200]
(3)回溯引用
将在下章详细介绍。
回溯引用(backreference)
(1)回溯引用的问题引入
一个在HTML页面中匹配标题标签(H1—H6)的问题:
文本:
<body>
<h1>Welcome to my pageH1>
Content is divided into twosections:<br>
<h2>Introductionh2>
Information about me.
<H2>HobbyH2>
Information about my hobby.
<h2>This is invalid HTMLh3>
body>
正则表达式:
<[hH][1-6]>.*?[hH][1-6]>
结果:
<body>
【<h1>Welcome to my pageH1>】
Content is divided into twosections:<br>
【<h2>Introductionh2>】
Information about me.
【<H2>HobbyH2>】
Information about my hobby.
【<h2>This is invalid HTMLh3>】
body>
分析:
模式<[hH][1-6]>匹配任何一级标题的开始标签,而且不区分大小写,在这个例子中它匹配到了<h1>、<h2>,[hH][1-6]>匹配到了h1>、h2>、h3>;这里使用了懒惰型元字符来匹配标签中的文本,否则会匹配到从第一个开始标签到最后一下结束标签之间的内容。但是从结果可以看出,有一个无效的标签也匹配上了,即<h2>h3>,它们根本不能配对。要解决这个问题,就需要使用到回溯引用。
(2)回溯引用匹配
回溯引用是指模式的后半部分引用在前半部分中定义的子表达式。
回溯引用只能用来引用模式里的字表达式(用(和)括起来的正则表达式片段)。
同一个子表达式可以被引用多次。
\1代表的是模式里的第一个子表达式,\2代表的时模式里的第二个子表达式,依次类推。可以把回溯引用理解成是变量的引用。Java中第0个匹配\0可以用来代表整个正则表达式。
子表达式在回溯引用中也称为捕获组。
捕获组索引的计算方式:
在模式 ((A)(B(C))) 中,存在四个这样的捕获组:
\1 ((A)(B(C)))
\2 (A)
\3 (B(C))
\4 (C)
当然\0代表((A)(B(C)))
文本:
<body>
<h1>Welcome to my pageH1>
Content is divided into twosections:<br>
<h2>Introductionh2>
Information about me.
<H2>HobbyH2>
Information about my hobby.
<h2>This is invalid HTMLh3>
body>
正则表达式:
<[hH]([1-6])>.*?[hH]\1>
结果:
<body>
【<h1>Welcome to my pageH1>】
Content is divided into twosections:<br>
【<h2>Introductionh2>】
Information about me.
【<H2>HobbyH2>】
Information about my hobby.
<h2>This is invalid HTMLh3>
分析:
首先匹配开始标题标签的模式<[hH]([1-6])>,使用括号把[1-6]做为子表达式,而匹配结束标题标签模式为[hH]\1>,其中\1表示引用第一个子表达式,即([1-6]),如果([1-6])匹配到的是1,那\1也匹配到1,如果匹配到2,那\1也匹配到2,所以最后一个无效的标题标签就不会被匹配到了。
(3)回溯引用在替换中的操作(后向分组引用(1,2…))
Java正则表达式支持替换时用 1,2 那样的后向分组引用。
String s = "abc def".replaceAll("(\\w+)\\s+(\\w+)", "$2 $1");
执行完上语句后,s 就是 "def abc",replaceFirst 也可以用 $1, $2 的替换。
前后查找(lookaround)
前后查找中的前、后是指模式与被查找文本的相对位置而言的,左为前,右为后。即向前查找为:xxx(?=xxx),而向后查找为(?<=xxx)xxx。
前后查找模式一定要放在()里括起来。
有了前后查找,就可以对最终的匹配结果包含哪些内容做出精确的控制。前后查找操作使我们可以利用子表达式来指定文本匹配操作发生的位置,并收到只匹配不消费的效果。
(1)前后查找的问题引入
在HTML页面中,匹配出一对标签之间的文本,如匹配出页面的标签,即<title>与title>之间的文本:
文本:
<head><TITLE>welcome to my pagetitle>head>
正则表达式:
<[Tt][Ii][Tt][Ll][Ee]>.*? [Tt][Ii][Tt][Ll][Ee]>
结果:
<head>【<TITLE>welcome to my pagetitle>】head>
分析:
<[Tt][Ii][Tt][Ll][Ee]>表示不区分大小写,这个模式匹配到了title标签以及它们之间的文本,但是并不完美,因为我们只想要title标签之间的文本,而不包括标签本身。解决这个问题我们就需要用到前后查找。
(2)向前查找(lookahead)
向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际上就是一个子表达式,它以?=开头,需要匹配的文本跟在=的后面。
看一个匹配出一个URL地址中协议部分的例子:
文本:
http://blog.csdn.net/mhmyqn
正则表达式:
.+(?=:)
结果:
【http】://blog.csdn.net/mhmyqn
分析:
URL地址中协议部分是在:之前的部分,模式.+匹配任意文本,子表达式(?=:)匹配:,但是被匹配到的:并没有出现在结果中。我们使用?=向正则表达式引擎表明,只要找到:就行了,但不包括在最终的返回结果里。这里如果不使用向前匹配(?=:),而是直接使用(:),那么匹配结果就会是http:了,它包括了:,并不是我们想要的。
(3)向后查找(lookbehind)
向后查找操作符是?<=。但是并不是所有的正则表达式实现都支持向后查找,javascript就不支持,java语言支持向后查找。
比如要查找文本当中的价格(以$开头,后面跟数字),结果不包含货币符号:
文本:
category1:$136.25,category2:$28,category3:$88.60
正则表达式:
(?<=\$)\d+(\.\d+)?
结果:
category1:$【136.25】,category2:$【28】,category3:$【88.60】
分析:(?<=\$)模式匹配$,\d+(\.\d+)?模式匹配整数或小数。从结果可以看出,结果不没有包括货币符号,只匹配出了价格。如果不使用向后查找,情况会是什么样呢?使用模式$\d+(\.\d+)?,这样会把$包含在结果中。使用模式\d+(\.\d+)?,又会把categery1(23)中的数字也匹配出来,都不是我们想要的。
注意:
向前查找模式的长度是可变的,它们可以包含.、*、+之类的元字符;而向后查找模式只能是固定长度,不能包含.、*、+之类的元字符。
(4)把向前查找和向后查找结合起来
把向前查找和向后查找结合起来使用,即可解决前面HTML标签之间的文本的问题:
文本:
<head><TITLE>welcome to my pagetitle>head>
正则表达式:
(?<=<[Tt][Ii][Tt][Ll][Ee]>).*?(?= [Tt][Ii][Tt][Ll][Ee]>)
结果:
<head><TITLE>【welcome to my page】title>head>
分析:从结果可以看出,问题完美的解决了。(?<=<[Tt][Ii][Tt][Ll][Ee]>)是一个向后操作,它匹配<title>但不消费它,(?=[Tt][Ii][Tt][Ll][Ee]>)是一个向前操作,它匹配title>但不消费它。最终返回的匹配结果只包含了标签之间的文本了。
(5)对前后查找取非
前面说到的向前查找和向后查找通常都是用来匹配文本,其目的是为了确定将被返回的匹配结果的文本的位置(通过指定匹配结果的前后必须是哪些文本)。这种用法叫正向前查找和正向后查找。还有一种负向前查找和负向后查找,是查找那些不与给定模式相匹配的文本。
前后查找的操作符:
(?=) 正向前查找
(?!) 负向前查找
(?<=) 正向后查找
(?$开头,后面跟数字)和数量,我们要找出价格和数量,先来看查找价格:
文本:
I paid $30 for 10 apples, 15 oranges, and 10 pears. I saved $5 onthis order.
正则表达式:
(?<=\$)\d+
结果:
I paid 【$30】 for 10 apples, 15 oranges, and 10 pears. I saved 【$5】 on thisorder.
查找数量:
文本:
I paid $30 for 10 apples, 15 oranges, and 10 pears. I saved $5 onthis order.
正则表达式:
\b(?$)\d+\b
结果:
I paid $30 for 【10】 apples, 【15】 oranges, and 【10】pears. I saved $5 on this order.
分析:
(?$)表示一个负向后查找,它使得结果只包含那些不以$开头的数值。