“No pain, no palm; no thorns, no throne; no gall, no glory; no cross, no crown.”
1. 正则表达式的定义
简单地说,正则表达式是一些用来匹配和处理文本的字符串。正则表达式语言用来构造正则表达式(最终构造出来的字符串就称为正则表达式),正则表达式用来完成搜索和替换操作。
2. 基本语法
2.1 匹配纯文本
例如:(利用在线工具测试)
1
2
3
4
5
6
文本: Hello, my name is Ben. Please visit my website at http://baidu.com
正则表达式: Ben
匹配结果: 共找到 1 处匹配:
Ben
- 有多个匹配结果:绝大多数正则表达式引擎的默认行为是只返回第1个匹配结果,但是绝大多数正则表达式的实现都提供了一种能够把所有的匹配结果全部找出来的机制(通常返回为一个数组或者是其他的专用格式)。例如,在JavaScript中,可选的g(global,全局)标志将返回一个包含着所有匹配的结果数组。
- 字母的大小写问题:正则表达式是区分大小写的,所有Ben不匹配ben。不过绝大多数的正则表达式实现也支持不区分大小写的匹配操作。
2.2 元字符
| 字符 | 含义 |
|---|---|
| . | 用来匹配任何一个单个字符 |
| [xyz] | 元字符 [ 和 ] 用来定义一个字符集合,其含义是必须匹配该集合里的字符之一。有两种定义字符集合方式:一是把所有的字符都列举出来;二是利用元字符’-‘,以字符区间的方式给出。 |
| [^xyz] | 取非匹配, ‘^’的效果是将给定的字符集合强行排除在匹配操作之外,即除了该字符集合里的字符,其他字符都可以被匹配。 |
| [\b] | 回退(并删除)一个字符(Backspace键) |
| \f | 换页符 |
| \n | 换行符 |
| \r | 回车符 |
| \t | 制表符(Tab键) |
| \v | 垂直制表符 |
| \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]) |
2.3 POSIX字符类
对元字符以及各种字符集合进行的讨论,必须要提到POSIX字符类。POSIX字符类是许多正则表达式都支持的一种简写模式(例如MYSQL就支持,但是JavaScript不支持使用)
| 字符类 | 说明 |
|---|---|
| [:alnum:] | 任何一个字母或数字( 等价于[a-zA-Z0-9] ) |
| [:alpha:] | 任何一个字母(等价于[a-zA-Z]) |
| [:blank:] | 空格或制表符(等价于 [\t ]) |
| [:cntrl:] | ASCII控制字符 (ASCII 0到31,再加上ASCII 127) |
| [:digit:] | 任何一个数字 (等价于[0-9]) |
| [:graph:] | 和[:print:]一样,但不包含空格 |
| [:lower:] | 任何一个小写字母(等价于[a-z]) |
| [:print:] | 任何一个可打印字符 |
| [:punct:] | 既不属于[:alnum:]也不属于[:cntrl:]的任何一个字符 |
| [:space:] | 任何一个空白字符,包括空格(等价于[^\f\n\r\t\v ]) |
| [:upper:] | 任何一个大写字母(等价于[A-Z]) |
| [:xdigit:] | 任何一个十六进制数字(等价于[a-fA-F0-9]) |
2.4 重复匹配
+: 匹配一个或多个字符。最少要匹配一次*: 匹配零个或多个字符。可以没有匹配?: 匹配零个或一个字符,最多不超过一次。{a,b}:重复匹配最少出现a次,最多出现b次。
2.5 防止过度匹配
’?’ 只能匹配零个或者一个字符,{n}和{n,m}也有一个重复次数的上限;换句话说,这几种语法所定义的“重复次数”都是有限的。但是+,*等重复匹配语法在重复次数上都没有上限值,而这样做有时候会导致过度匹配的现象。给出一个例子如下所示:
1
2
3
4
5
6
7
文本: linving in <B>AK</B> and <B>HI</B>
正则表达式: <[Bb]>.*</[Bb]>
结果:
共找到 1 处匹配:
<B>AK</B> and <B>HI</B>
上面的例子,我们可以看到这个模式只找到了一个匹配而不是预期中的两个。为什么会这样呢?这是因为’*‘和’+’都是所谓的“贪婪型”元字符,它们在进行匹配的时候的行为模式是多多益善而不是适可而止的。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配时为止。
当我们不需要这种贪婪行为的时候该怎么办?答案就是使用这些元字符的“懒惰型”版本(“懒惰”在这里的含义是匹配尽可能少的字符)。懒惰型元字符的写法很简单,只要给贪婪型元字符加上一个’?’后缀即可。
| 贪婪型元字符 | 懒惰型元字符 |
|---|---|
* |
*? |
+ |
+? |
{n, } |
{n, }? |
还是之前的例子,修改如下:
1
2
3
4
5
6
7
8
文本: linving in <B>AK</B> and <B>HI</B>
正则表达式: <[Bb]>.*?</[Bb]>
结果:
共找到 2 处匹配:
<B>AK</B>
<B>HI</B>
2.6 位置匹配
\b: 限定符代表的单词边界,用来匹配一个单词的开始或结尾。^: 定义匹配字符串的开头。$: 用来定义字符串结尾,匹配一个字符串的结尾。(?m): 分行匹配模式,其能改变其他元字符行为的元字符序列,其必须出现在整个模式的最前面。
分行匹配模式:有许多正则表达式都支持一些特殊的元字符去改变另外一些元字符行为的做法,用来启用分行匹配模式的(?m)记号就是一个能够改变其他元字符行为的元字符序列。分行匹配模式将使得正则表达式引擎把行分隔符当做一个字符串分隔符来对待。在分行匹配模式下,’^’不仅匹配正常的字符串开头,还将匹配行分隔符(换行符)后面的开始位置(这个位置是不可见的);类似地,’$’不仅匹配正常的字符串结尾,还将匹配行分隔符(换行符)后面的结束位置。(许多正则表达式实现不支持该语法)
2.7 使用子表达式
子表达式:它是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。子表达式必须用’(‘ 和 ‘)’括起来。
例如匹配ip地址的一个正则表达式案例:
1
(\d{1,3}\.){3}\d{1,3}
上面的案例虽然也能够正确匹配,但是这个模式之下不合法的IP地址也能与之相互匹配(因为IP地址里的每一组数字都不能大于255,可是上面的那个模式还能匹配诸如345,700之类的数字序列,这些数字在IP地址中都是非法的)。
在构造一个正则表达式的时候,一定要把你想要匹配什么和你不想匹配什么详尽地定义清楚。例如,下面是一个合法的IP地址里的各组数字都必须且只能符合的规则,后面我们会根据这个规则来构造一个对应的模式:
- 任何一个1位或2位数字;
- 任何一个以1开头的3位数字;
- 任何一个以2开头、第二位数字在0~4之间的3位数字;
- 任何一个以25开头、第三位数字在0~5之间的3位数字。
给出正则表达式如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
文本: hahha [12.159.46.200]
nanivnsn 188.231.16.85
vbdsvbbvdsb 298.66.980.45
正则表达式:
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))
匹配结果:
共找到 2 处匹配:
12.159.46.20
188.231.16.85
分析:来分析一下这个正则表达式,(\d{1,2})匹配任意一位或两位数字(0~99);(1\d{2})匹配以1开头的任意三位数字(100~199);(2[0-4]\d)匹配整数200~249;(25[0-5])匹配整数250~255。这几个子表达式通过 |
操作符结合为一个更大的子表达式(其含义是只需要匹配这4个表达式之一即可)。随后的.用来匹配.字符,它与前面的4个子表达式构成的子表达式又构成了一个更大的子表达式(4组数字选项和.),而接下来的{3}表明了需要重复3次。最后,数值范围又重复了一次(这次省略了尾部的.)以匹配IP地址里的最后一组数字。通过把4组数字的取值范围都限制在0-255之间,这个模式准确无误地做到了值匹配合法的IP地址、不匹配非法的IP地址。 |
2.8 回溯引用
给出一个例子:假设你有一段文本,你想把这个段文本里所有连续重复出现的单词(打字错误,其中一个单词输了两遍)找出来。显然,在搜索某个单词的第二次出现时,这个单词必须是已知的。回溯引用允许正则表达式模式引用前面的匹配结果(具体到这个例子,就是前面匹配到的单词)。
我们来具体看一个例子,下面是一段包含了3组重复单词的文本,它就是我们要找的东西:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
文本:
This is a block of of text,
serveral words here are are
repeated, and and they
should not be.
正则表达式:
[ ]+(\w+)[ ]+\1
结果:
共找到 3 处匹配:
of of
are are
and and
分析:这个模式找到了我们想要的东西,它是如何做到的呢?首先,[ ]+ 匹配一个或多个空格,\w+匹配一个或多个字母数字字符,[ ]+匹配随后的空格。注意,\w+是包含在括号里的,说明它是一个子表达式。这个子表达式不是用来进行重复匹配的,这里根本不涉及重复匹配的问题。这个子表达式只是把整个模式的一部分单独划分出来以便在后面引用。这个模式的字后一部分是\1;这是一个回溯引用,而它引用的正是前面划分出来的那个子表达式:当(\w+)匹配到单词of的时候,\1也匹配单词of;当(\w+)匹配单词and的时候,\1也匹配单词and.
定义:回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式。\1到底代表着什么?其实它代表着模式里的第1个子表达式,\2代表着第2个子表达式,\3代表着第3个;以此类推。其实可以把回溯引用看成是变量的引用。
回溯引用在替换操作中的作用,给出下面这个例子(将电话号码重新排版):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
文本:
313-555-1234
248-555-9000
810-555-9000
正则表达式:
(\d{3})(-)(\d{3})(-)(\d{4})
替换表达式:
($1) $3-$5
替换结果:
(313) 555-1234
(248) 555-9000
(810) 555-9000
分析:在这里我们使用了两个正则表达式模式。第1个模式(\d{3})(-)(\d{3})(-)(\d{4})用来匹配电话号码:第1个子表达式(\d{3})匹配前3位数字,第2个子表达式(-)匹配-字符等。这5个部分都可以单独拿出来使用,负责重新排版电话号码的替换模式($1) $3-$5只用到了它们当中的3个,剩下的两个没有用到。
在对文本进行重新排版的时候,把文本分解成多个子表达式的做法往往非常有用,这可以让我们对文本的排版效果做出更精确的控制。
用来进行大小写转换的元字符(java中不支持使用)
| 元字符 | 说明 |
|---|---|
| \E | 结束\L或\U转换 |
| \l | 把下一个字符转换为小写 |
| \L | 把\L到\E之间的字符全部转换为小写 |
| \u | 把下一个字符转换为大写 |
| \U | 把\U到\E之间的字符全部转换为大写 |
2.9 前后查找
1.向前查找
向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际上就是一个子表达式,而且从格式上看也是如此。从语法上来看,一个向前查找模式其实就是一个以?=开头的子表达式,需要匹配的文本跟在=的后面。
例如:(找到:就行了,不要把它包括在最终的匹配结果里)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
文本:
http://www.forta.com/
https://mail.forta.com/
ftp://ftp.forta.com/
正则表达式:
.+(?=:)
匹配结果:
共找到 3 处匹配:
http
https
ftp
2.向后查找
前面我们看到?=将向前查找(查找出现在被匹配文本之前的字符,但不消费那个字符)。因此?=被称为向前查找操作符。除了向前查找,许多正则表达式还支持向后查找,也就是查找出现在被匹配文本之后的字符,向后查找操作符是?<=。
向前查找模式的长度是可变的,它们可以包含
.和+之类的元字符。所以它们非常灵活。而向后查找模式只能是固定长度,这是一条几乎所有的正则表达式实现都遵循的限制。
3.前后查找取非
负向前查找将向前查找不与给定模式相匹配的文本,而负向后查找将向后查找不与给定模式匹配的文本。
前后查找必须使用!来取得(它将替换掉=),下面给出各种前后查找的操作符:
| 操作符 | 说明 |
|---|---|
| (?=) | 正向前查找 |
| (?!) | 负向前查找 |
| (?<=) | 正向后查找 |
| (?<!) | 负向后查找 |
给出一个使用案例,一段文本包含数值,其中有价格也有数量:
1
2
3
4
文本:
I paid $30 for 100 apples,
50 oranges, and 60 pears.
i saved $5 on this prder.
我们先来查找且只查找价格(正向后查找):
1
2
3
4
5
6
7
8
正则表达式:
(?<=\$)\d+
结果:
共找到 2 处匹配:
30
5
查找且只查找数量(负向后查找):
1
2
3
4
5
6
7
8
9
正则表达式:
\b(?<!\$)\d+\b
结果:
共找到 3 处匹配:
100
50
60
2.10 嵌入条件
1.回溯引用条件
回溯引用条件只在一个前面的子表达式搜索取得成功的情况下才允许使用一个表达式。给个例子来说明,比如你想把一段文本里的标签全都找出来,不仅如此,如果某个
标签是一个链接(被括在<A>和<\/A>标签之间的话),你还需要把整个连接标签匹配出来。
用来定义这种条件的语法是(?(backreference)true-regex),其中的?表明这是一个条件,括号里的backreference是一个回溯引用,true-regex是一个只在backreference存在时才会被执行的子表达式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
文本:
<TD>
<A HREF="/home"><IMG SRC="/images/home.gif"></A>
<IMG SRC="/images/space.gif">
<A HREF="/search"><IMG SRC="/images/search.gif"></A>
<IMG SRC="/images/spacer.gif">
<A HREF="/help"><IMG SRC="/images/help.gif"></A>
</TD>
正则表达式:
(<[Aa]\s+[^>]+>\s*)?<[Ii][Mm][Gg]\s+[^>]+>(?(1)\s*</[Aa]>)
结果:
共找到 3 处匹配:
<A HREF="/home"><IMG SRC="/images/home.gif">
<A HREF="/search"><IMG SRC="/images/search.gif">
<A HREF="/help"><IMG SRC="/images/help.gif">
3. Java正则表达式学习
正则表达式java.util.regex包主要包含以下三个类:
- Pattern类
pattern对象是一个正则表达式的编译表示。Pattern类没有公共构造方法。要创建一个Pattern对象,你必须首先调用其公共静态编译方法,它返回一个Pattern对象。该方法接收一个正则表达式作为它的第一个参数。
- Matcher类
Matcher对象是对输入字符串进行解释和匹配操作的引擎。与Pattern类一样,Matcher也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个Matcher对象。
- PatternSyntaxException:
PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
3.1 捕获组
捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。
例如,正则表达式 (dog) 创建了单一分组,组里包含”d”,”o”,和”g”。
捕获组是通过从左至右计算其开括号来编号。例如,在表达式( (A)(B (C) ) ),有四个这样的组:
- ((A)(B(C)))
- (A)
- (B(C))
- (C)
可以通过调用 matcher 对象的 groupCount 方法来查看表达式有多少个分组。groupCount 方法返回一个 int 值,表示matcher对象当前有多个捕获组。
还有一个特殊的组(group(0)),它总是代表整个表达式。该组不包括在 groupCount 的返回值中。
3.2 正则表达式语法
在其他语言中,\\ 表示:我想要在正则表达式中插入一个普通的(字面上的)反斜杠,请不要给它任何特殊的意义。
在 Java 中,\\ 表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。
所以,在其他的语言中(如Perl),一个反斜杠 \ 就足以具有转义的作用,而在 Java 中正则表达式中则需要有两个反斜杠才能被解析为其他语言中的转义作用。也可以简单的理解在 Java 的正则表达式中,两个 \\ 代表其他语言中的一个 \,这也就是为什么表示一位数字的正则表达式是 \\d,而表示一个普通的反斜杠是 \\\\。



3.3 常用的正则表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
验证数字:^[0-9]*$
验证n位的数字:^\d{n}$
验证至少n位数字:^\d{n,}$
验证m-n位的数字:^\d{m,n}$
验证零和非零开头的数字:^(0|[1-9][0-9]*)$
验证有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
验证有1-3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
验证非零的正整数:^\+?[1-9][0-9]*$
验证非零的负整数:^\-[1-9][0-9]*$
验证非负整数(正整数 + 0) ^\d+$
验证非正整数(负整数 + 0) ^((-\d+)|(0+))$
验证长度为3的字符:^.{3}$
验证由26个英文字母组成的字符串:^[a-za-z]+$
验证由26个大写英文字母组成的字符串:^[a-z]+$
验证由26个小写英文字母组成的字符串:^[a-z]+$
验证由数字和26个英文字母组成的字符串:^[a-za-z0-9]+$
验证由数字和26个英文字母9位组成的字符串:^[0-9a-zA-Z]{9}$
验证由数字、26个英文字母或者下划线组成的字符串:^\w+$
验证用户密码:^[a-za-z]\w{5,17}$ 正确格式为:以字母开头,长度在6-18之间,只能包含字符、数字和下划线。
验证是否含有 ^%&',;=?$\" 等字符:[^%&',;=?$\x22]+
验证汉字:^[\u4e00-\u9fa5],{0,}$
验证Email地址:^\w+[-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
验证InternetURL:^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$ ;^[a-za-z]+://(w+(-w+)*)(.(w+(-w+)*))*(?S*)?$
验证电话号码:^(|\d{3,4}-)?\d{7,8}$:--正确格式为:XXXX-XXXXXXX,XXXX-XXXXXXXX,XXX-XXXXXXX,XXX-XXXXXXXX,XXXXXXX,XXXXXXXX。
验证身份证号(15位或18位数字):^\d{15}|\d{18}$
验证一年的12个月:^(0?[1-9]|1[0-2])$ 正确格式为:“01”-“09”和“1”“12”
验证一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$ 正确格式为:01、09和1、31。
整数:^-?\d+$
非负浮点数(正浮点数 + 0):^\d+(\.\d+)?$
正浮点数 : ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
非正浮点数(负浮点数 + 0): ^((-\d+(\.\d+)?)|(0+(\.0+)?))$
负浮点数: ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数: ^(-?\d+)(\.\d+)?
匹配所有键盘上可见的非字母和数字的符号:((?=[\x21-\x7e]+)[^A-Za-z0-9])
匹配所有键盘上所有可见的非字母和数字的符号:((?=[\x21-\x7e]+)[^A-Za-z0-9])/g
匹配包括换行符在内的任意字符: ([\s\S]*) 同时,也可以用 “([\d\D]*)”、“([\w\W]*)” 来表示;([\s\S]*?) 加上问号 表示最短匹配

例题:由数字和字母组成,并且要同时含有数字和字母,且长度要在8-16位之间。
答案是:
1
^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$
分开来注释一下:
1
2
3
4
5
6
7
8
分开来注释一下:
^ 匹配一行的开头位置
(?![0-9]+$) 预测该位置后面不全是数字
(?![a-zA-Z]+$) 预测该位置后面不全是字母
[0-9A-Za-z] {8,16} 由8-16位数字或这字母组成
$ 匹配行结尾位置
注:(?!xxxx) 是正则表达式的负向零宽断言一种形式,标识预该位置后不是xxxx字符。
3.4 Matcher类的方法
索引方法提供了有用的索引值,精确表明输入字符串中在哪能找到匹配:

研究方法用来检查输入字符串并返回一个布尔值,表示是否找到该模式:

替换方法是替换输入字符串里文本的方法:

3.5 PatternSyntaxException类的方法
PatternSyntaxException 是一个非强制异常类,它指示一个正则表达式模式中的语法错误。
PatternSyntaxException 类提供了下面的方法来帮助我们查看发生了什么错误
