1. 什么是正则表达式
- 正则表达式是一串字符,它描述了一个文本模式
- 利用它可以方便地处理文本,包括文本的查找、替换、验证、切分等
2. 正则表达式中字符的分类
- 普通字符:就是要匹配的字符本身
- 元字符:有特殊含义的字符,这些元字符及其特殊含义构成了正则表达式的语法
3. 正则表达式的历史是怎样的
- 正则表达式有一个比较长的历史
- 各种与文本处理有关的工具、编辑器和系统都支持正则表达式,大部分编程语言也都支持正则表达式
- 虽然都叫正则表达式,但由于历史原因,不同语言、系统和工具的语法不太一样
4. 正则表达式中单个字符的语法有哪些
- 大部分的单个字符就是用字符本身表示的,比如字符
0
、3
、a
、马
等,但有一些单个字符使用多个字符表示,这些字符都以反斜杠\
开头,比如- 特殊字符,比如 tab 字符
\t
、换行符\n
、回车符\r
等 - 八进制表示的字符:以
\0
开头,后跟 1 ~ 3 位数字。比如\0141
,对应的是 ASCII 编码为 97 的字符,即字符a
- 十六进制表示的字符:以
\x
开头,后跟 2 位字符。比如\x6A
,对应的是 ASCII 编码为 106 的字符,即字符j
- Unicode 编号表示的字符:以
\u
开头,后跟 4 位字符。比如\u9A6C
,表示的是中文字符:马
。这只能表示编号在 0xFFFF 以下的字符,如果超出 0xFFFF,使用\x{...}
形式,比如\x{1f48e}
- 反斜杠
\
本身:反斜杠\
是一个元字符,如果要匹配它自身,使用两个反斜杠表示,即\\
- 元字符本身:除了
\
,正则表达式中还有很多元字符,比如.
、*
、?
、+
等,要匹配这些元字符自身,需要在前面加转义字符\
,比如\.
- 特殊字符,比如 tab 字符
5. 怎样理解 .
点号元字符
- 点号字符
.
是一个元字符,默认模式下,它匹配除了换行符以外的任意字符 - 可以指定另外一种匹配模式,一般称为单行匹配模式或者点号匹配模式。在此模式下,
.
匹配任意字符,包括换行符。可以有两种方式指定匹配模式- 一种是在正则表达式中,以
(?s)
开头,s 表示 single line,即单行匹配模式。比如:(?s)a.f
- 另外一种是在程序中指定,在 Java 中,对应的模式常量是
Pattern.DOTALL
- 一种是在正则表达式中,以
6. 怎样理解字符组这个概念
在单个字符和任意字符之间,有一个字符组的概念,匹配组中的任意一个字符,用中括号
[]
表示比如:
[abcd]
匹配a
、b
、c
、d
中的任意一个字符;[0123456789]
匹配任意一个数字为方便表示连续的多个字符,字符组中可以使用连字符
-
。比如:[0-9]
、[a-z]
- 可以有多个连续空间,可以有其他普通字符。比如:
[0-9a-zA-Z_]
- 在字符组中,
-
是一个元字符,如果要匹配它自身,可以使用转义,即\-
,或者把它放在字符组的最前面,比如:[-0-9]
- 可以有多个连续空间,可以有其他普通字符。比如:
字符组支持排除的概念,在
[
后紧跟一个字符^
。比如:[^abcd]
,表示匹配除了a
、b
、c
、d
以外的任意一个字符;[^0-9]
表示匹配一个非数字字符- 排除不是不能匹配,而是匹配一个指定字符组以外的字符,要表达不能匹配的含义,需要使用环视语法
^
只有在字符组的开头才是元字符,如果不在开头,就是普通字符。匹配它自身,比如:[a^b]
就是匹配字符a
,^
或b
在字符组中,除了
^
、-
、[]
、\
外,其他在字符组的元字符不再具备特殊含义,变成了普通字符。比如字符.
和*
,[.*]
就是匹配.
或者*
本身
7. Java 中预定义的字符组有哪些
有一些特殊的以
\
开头的字符,表示一些预定义的字符组,比如\d
:d
表示 digit,匹配一个数字字符,等同于[0-9]
\w
:w
表示 word,匹配一个单词字符,等同于[a-zA-Z_0-9]
\s
:s
表示 space,匹配一个空白字符,等同于[\t\n\x0B\f\r]
它们都有对应的排除型字符组,用大写表示,即
\D
:匹配一个非数字字符,即[^\d]
\W
:匹配一个非单词字符,即[^\w]
\S
:匹配一个非空白字符,即[^\s]
8. 什么是 POSI 字符组
- POSIX 字符组是 POSIX 标准定义的一些字符组
- 在 Java 中,这些字符组的形式是
\p{…}
9. 正则表达式中的量词是什么意思
- 量词指的是指定出现次数的元字符,有三个常见的元字符:
+
、*
、?
+
:表示前面字符的一次或多次出现。比如ab+c
,既能匹配abc
,也能匹配abbc
或abbbc
*
:表示前面字符的零次或多次出现。比如ab*c
,既能匹配abc
,也能匹配ac
或abbbc
?
:表示前面字符可能出现、也可能不出现。比如ab?c
,既能匹配abc
,也能匹配ac
,但不能匹配abbc
10. 更为通用的表示出现次数的语法是
{m,n}
逗号左右不能有空格,表示出现次数从 m 到 n,包括 m 和 n;如果 n 没有限制,可以省略。;如果 m 和 n 一样,可以写为
{m}
ab{1,10}c
:b 可以出现 1 次到 10 次ab{3}c
:b 必须出现三次,即只能匹配abbbc
ab{1,}c
:与ab+c
一样ab{0,}c
:与ab*c
一样ab{0,1}c
:与ab?c
一样
另外,
?
、*
、+
、{
是元字符,如果要匹配这些字符本身,需要使用\
转义。比如:a\*b
,匹配字符串a*b
这些量词出现在字符组中时,不是元字符,比如:
[?*+{]
就是匹配其中一个字符本身
11. 关于量词,它们的默认匹配是贪婪的。这句话的意思是
- 看个例子,正则表达式是:
<a>.*</a>
,如果要处理的字符串是:<a>first</a><a>second</a>
(目的是想得到两个匹配,一个匹配:<a>first</a>
,另一个匹配:<a>second</a>
) - 但默认情况下,得到的结果却只有一个匹配,匹配所有内容
- 这是因为
.*
可以匹配第一个<a>
和最后一个</a>
之间的所有字符。只要能匹配,.*
就尽量往后匹配,它是贪婪的 - 如果希望在碰到第一个匹配时就停止的话,应该使用懒惰量词:在量词的后面加一个符号
?
。将表达式改为:<a>.*?</a>
就能得到期望的结果 - 所有量词都有对应的懒惰形式,比如:
x??
、x*?
、x+?
、x{m,n}?
等
- 这是因为
12. 怎样理解分组的概念
表达式可以用小括号
()
括起来,表示一个分组,比如:a(bc)d
,bc
就是一个分组- 分组可以嵌套,比如
a(de(fg))
- 分组默认都有一个编号,按照括号的出现顺序,从 1 开始,从左到右依次递增。比如表达式:
a(bc)((de)()fg)
,字符串abcdefg
匹配这个表达式,第一个分组为bc
,第二个分组为defg
,第三个分组为de
,第四个分组为 fg。分组 0 是一个特殊分组,内容是整个匹配的字符串,这里是abcdefg
- 分组匹配的子字符串可以在后续访问,好像被捕获了一样,所以默认分组称为捕获分组
- 可以对分组使用量词,表示分组的出现次数,比如
a(bc)+d
,表示bc
出现一次或多次
- 分组可以嵌套,比如
中括号
[]
表示匹配其中的一个字符,小括号()
和元字符|
一起,可以表示匹配其中的一个子表达式。比如:(http|ftp|file)
匹配http
或ftp
或file
。但|
用于[]
就不再有特殊含义,比如:[a|b]
,它的含义不是匹配a
或b
,而是a
或|
或b
在正则表达式中,可以使用反斜杠
\
加分组编号引用之前匹配的分组,这称为回溯引用。比如:<(\w+)>(.*)</\1>
,\1
匹配之前的第一个分组(\w+)
,这个表达式可以匹配类似如下字符串:<title>bc</title>
。这里,第一个分组是title
使用数字引用分组,可能容易出现混乱,可以对分组进行命名,通过名字引用之前的分组。对分组命名的语法是:
(?<name>X)
,引用分组的语法是\k<name>
。比如,上面的例子可以写为:<(?<tag>\w+)>(.*)</\k<tag>>
默认分组都称为捕获分组,即分组匹配的内容被捕获了,可以在后续被引用。实现捕获分组有一定的成本,为了提高性能,如果分组后续不需要被引用,可以改为非捕获分组,语法是:
(?:...)
。比如:(?:abc|def)
13. 正则表达式中的特殊边界匹配什么意思
在正则表达式中,除了可以指定字符需满足什么条件,还可以指定字符的边界需满足什么条件,或者说匹配特定的边界。常用的表示特殊边界的元字符有:
^
、$
、\A
、\Z
、\z
和\b
- 默认情况下,
^
匹配整个字符串的开始。比如,^abc
表示整个字符串必须以abc
开始 - 元字符
^
的含义是,在字符组中它表示排除,但在字符组外,它匹配开始。比如,^[^abc]
表示以一个不是a
、b
、c
的字符开始 - 默认情况下,
$
匹配整个字符串的结束。不过,如果整个字符串以换行符结束,$
匹配的是换行符之前的边界。比如,abc$
表示整个表达式以abc
结束,或者以abc\r\n
或abc\n
结束
- 默认情况下,
以上
^
和$
的含义是默认模式下的,可以指定另外一种匹配模式:多行匹配模式。在此模式下,会以行为单位进行匹配,^
匹配的是行开始、$
匹配的是行结束。比如,^abc$
,字符串是abc\nabc\r\n
,就会有两个匹配。可以有两种方式指定匹配模式- 一种是在正则表达式中,以
(?m)
开头,m
表示 multiline,即多行匹配模式。上面的正则表达式可以写为:(?m)^abc$
- 另一种是在程序中指定,在 Java 中,对应的模式常量是
Pattern.MULTILINE
- 一种是在正则表达式中,以
多行匹配模式和单行匹配模式没有关系
- 单行模式影响的是点号元字符
.
的匹配规则,使得.
可以匹配换行符 - 多行模式影响的是
^
和$
的匹配规则,使得它们可以匹配行的开始和结束 - 两个模式可以一起使用
- 单行模式影响的是点号元字符
\A
与^
类似,但不管什么模式,它匹配的总是整个字符串的开始边界\Z
和\z
与$
类似,但不管什么模式,它们匹配的总是整个字符串的结束边界\Z
和\z
的区别是:如果字符串以换行符结束,\Z
与$
一样,匹配的是换行符之前的边界,而\z
匹配的总是结束边界- 在进行输入验证的时候,为了确保输入最后没有多余的换行符,可以使用
\z
进行匹配
\b
匹配的是单词边界,比如\bcat\b
,匹配的是完整的单词cat
,它不能匹配category
。\b
匹配的不是一个具体的字符,而是一种边界- 这种边界满足一个要求:即一边是单词字符,另一边不是单词字符。在 Java 中,
\b
识别的单词字符除了\w
,还包括中文字符 - 边界匹配不同于字符匹配,可以认为,在一个字符串中,每个字符的两边都是边界,而上面介绍的这些特殊字符,匹配的都不是字符,而是特定的边界
- 这种边界满足一个要求:即一边是单词字符,另一边不是单词字符。在 Java 中,
14. 怎么理解环视边界匹配
对于边界匹配,还有一种更为通用的方式,那就是环视
环视的字面意思就是左右看看,需要左右符合一些条件。本质上,它也是匹配边界,对边界有一些要求,这个要求是针对左边或右边的字符串的。根据要求不同,分为 4 种环视
- 肯定顺序环视:语法是
(?=...)
,要求右边的字符串匹配指定的表达式。比如,表达式abc(?=def)
,(?=def)
在字符c
右面,即匹配c
右面的边界。对这个边界的要求是:它的右边有def
,比如abcdef
,如果没有,比如abcd
,则不匹配 - 否定顺序环视:语法是
(?!...)
。要求右边的字符串不能匹配指定的表达式。比如,表达式s(?!ing)
,匹配一般的s
,但不匹配后面有ing
的s
。注意:避免与排除型字符组混淆,比如s[^ing]
,s[^ing]
匹配的是两个字符,第一个是s
,第二个是i
、n
、g
以外的任意一个字符 - 肯定逆序环视:语法是
(?<=...)
,要求左边的字符串匹配指定的表达式。比如,表达式(?<=\s)abc
,(?<=\s)
在字符a
左边,即匹配a
左边的边界。对这个边界的要求是:它的左边必须是空白字符 - 否定逆序环视:语法是
(?<!...)
,要求左边的字符串不能匹配指定的表达式。比如,表达式(?<!\w)cat
,(?<!\w)
在字符c
左边,即匹配c
左边的边界。对这个边界的要求是:它的左边不能是单词字符
- 肯定顺序环视:语法是
环视也使用小括号
()
,不过,它不是分组,不占用分组编号- 这些环视结构也被称为断言,断言的对象是边界,边界不占用字符,没有宽度,所以也被称为零宽度断言
- 顺序环视也可以出现在左边,逆序环视也可以出现在右边
- 环视匹配的是一个边界,里面的表达式是对这个边界左边或右边字符串的要求,对同一个边界,可以指定多个要求,即写多个环视
15. 怎样理解转义字符 \
字符
\
表示转义,转义有两种- 把普通字符转义,使其具备特殊含义。比如
\t
、\n
、\d
、\w
、\b
、\A
等 ,即这个转义把普通字符变为了元字符 - 把元字符转义,使其变为普通字符。比如
\.
、\*
、\?
、\(
、\\
等
- 把普通字符转义,使其具备特殊含义。比如
记住所有的元字符,并在需要的时候进行转义,这是比较困难的。有一个简单的办法:可以将所有元字符看作普通字符,就是在开始处加上
\Q
,在结束处加上\E
。比如,\Q(.*+)\E
,\Q
和\E
之间的所有字符都会被视为普通字符正则表达式用字符串表示
- 在 Java 中,字符
\
也是字符串语法中的元字符 - 这使得正则表达式中的
\
,在 Java 字符串表示中,要用两个\
,即\\
- 而要匹配字符
\
本身,在 Java 字符串表示中,要用 4 个\
,即\\\\
- 在 Java 中,字符
16. 不区分大小写的匹配模式是
指定方式有两种
- 一种是在正则表达式开头使用
(?i)
,i
为ignore
。比如,(?i)the
,既可以匹配the
,也可以匹配THE
,还可以匹配The
- 还可以在程序中指定,Java 中对应的变量是
Pattern.CASE_INSENSITIVE
- 一种是在正则表达式开头使用
匹配模式间不是互斥的关系,它们可以一起使用,在正则表达式中,可以指定多个模式,比如
(?smi)
17. 正则表达式语法总结一下
单个字符语法
语法 解释 语法 解释 \r \n \t
特殊字符 \uhhhh
基本 Unicode 字符,如 \u9A6C (马) \0n \0nn \0mnn
八进制字符,如 \0141 \x{h…h}
增补 Unicode 字符,如 \x{1f48e} \xhh
十六进制字符,如 \x6A 字符组语法
语法 解释 语法 解释 .
默认模式是换行符外的任意字符,单行模式是任意字符 [0-9a-z]
0 到 9、a 到 z 的任意一个字符 [abc]
a、b、c 中的任意一个字符 [-0-9]
0 到 9 或者连字符 - [\^abc]
a、b、c 以外的任意一个字符 [.* ]
. 或者 *,没有特殊含义 [a-z&&[\^de]]
a 到 z,但不包括 d 和 e \D
[^\d],非数字字符 [[abc][def]]
[abcdef] \W
[^\w],非单词字符 \d
[0-9] \S
[^\s],非空白字符 \w
[a-zA-Z_0-9] \p{…}
POSIX 字符组 \s
[\t\n\x0B\f\r] 量词语法
语法 解释 语法 解释 x?、x??
x 出现 0 次或 1 次,多一个 ? 的为懒惰形式,下同 x{m,n}、x{m,n}?
x 出现 m 次到 n 次 x 、x ?
x 出现 0 次或多次 x{m,}、x{m,}?
x 出现 m 次以上 x+、x+?
x 出现 1 次或多次 x{n}、x{n}?
x 出现正好 n 次 分组语法
语法 解释 语法 解释 ab 竖线 cd
匹配 ab 或 cd (?\X)
给分组命名,比如 <(?\w+)>,(\w+) 匹配的分组命名为了 tag (http 竖线 ftp 竖线 file)
匹配 http、ftp 或 file k\
引用命名分组,比如 <(?\w+)>(.* )</\k> a(bc)+d
bc 作为一个分组出现多次 (?:abc 竖线 def)
分组但不捕获,匹配 abc 或 def <(\w+)>(.*)</\1> (\w+)
捕获第一个分组,\1 回溯引用该分组 边界和环视语法
语法 解释 ^
默认模式是整个字符串的开始边界,多行模式是行的开始边界 $
默认模式是整个字符串的结束边界,多行模式是行的结束边界,如果结尾是换行符,为换行符之前的边界 \A
总是匹配整个字符串的开始边界 \Z
总是匹配整个字符串的结束边界,如果结尾是换行符,匹配换行符之前的边界 \z
总是匹配整个字符串的结束边界,不管结尾是否是换行符 \b
匹配单词边界,边界一边是单词字符,另一边不是 (?=…)
肯定顺序环视,匹配边界,该边界右边的字符串匹配指定表达式 (?!=…)
否定顺序环视,匹配边界,该边界右边的字符串不能匹配指定表达式 (?<=…)
肯定逆序环视,匹配边界,该边界左边的字符串匹配指定表达式 (?<!…)
否定逆序环视,匹配边界,该边界左边的字符串不能匹配指定表达式 匹配模式和转义语法
语法 解释 语法 解释 (?i)
不区分大小写匹配 [.*^(){}]
在字符组中,大部分元字符没有特殊含义 (?m)
多行模式,^ 匹配行开始,$ 匹配行结束 \\
\ 本身 (?s)
单行模式,. 匹配任意字符,包括换行符 \Q \E
\Q 到 \E 之间的所有字符视为普通字符 \. \* \?
转义元字符为普通字符