0%

Java 正则表达式(一):语法

1. 什么是正则表达式

  • 正则表达式是一串字符,它描述了一个文本模式
  • 利用它可以方便地处理文本,包括文本的查找、替换、验证、切分等。

2. 正则表达式中字符的分类

  • 普通字符:就是要匹配的字符本身。
  • 元字符:有特殊含义的字符,这些元字符及其特殊含义构成了正则表达式的语法。

3. 正则表达式的历史是怎样的

  • 正则表达式有一个比较长的历史
  • 各种与文本处理有关的工具、编辑器和系统都支持正则表达式,大部分编程语言也都支持正则表达式。
  • 虽然都叫正则表达式,但由于历史原因,不同语言、系统和工具的语法不太一样

4. 正则表达式中单个字符的语法有哪些

  • 大部分的单个字符就是用字符本身表示的,比如字符 03a 等,但有一些单个字符使用多个字符表示,这些字符都以反斜杠 \ 开头,比如:
    • 特殊字符,比如 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}
    • 反斜杠 \ 本身:反斜杠 \ 是一个元字符,如果要匹配它自身,使用两个反斜杠表示,即 \\
    • 元字符本身:除了 \,正则表达式中还有很多元字符,比如 .*?+ 等,要匹配这些元字符自身,需要在前面加转义字符 \,比如 \.

5. 怎样理解 . 点号元字符

  • 点号字符 . 是一个元字符,默认模式下,它匹配除了换行符以外的任意字符
  • 可以指定另外一种匹配模式,一般称为单行匹配模式或者点号匹配模式。在此模式下,. 匹配任意字符,包括换行符。可以有两种方式指定匹配模式:
    • 一种是在正则表达式中(?s) 开头,s 表示 single line,即单行匹配模式。比如:(?s)a.f
    • 另外一种是在程序中指定,在 Java 中,对应的模式常量是 Pattern.DOTALL

6. 怎样理解字符组这个概念

  • 在单个字符和任意字符之间,有一个字符组的概念,匹配组中的任意一个字符,用中括号 [] 表示

  • 比如:[abcd] 匹配 abcd 中的任意一个字符;[0123456789] 匹配任意一个数字:

    • 为方便表示连续的多个字符,字符组中可以使用连字符 -。比如:[0-9][a-z]

      • 可以有多个连续空间,可以有其他普通字符。比如:[0-9a-zA-Z_]
      • 在字符组中,- 是一个元字符,如果要匹配它自身,可以使用转义,即\-,或者把它放在字符组的最前面,比如:[-0-9]
    • 字符组支持排除的概念,[ 后紧跟一个字符 ^。比如:[^abcd],表示匹配除了 abcd 以外的任意一个字符;[^0-9] 表示匹配一个非数字字符。

      • 排除不是不能匹配,而是匹配一个指定字符组以外的字符,要表达不能匹配的含义,需要使用环视语法
      • ^ 只有在字符组的开头才是元字符,如果不在开头,就是普通字符。匹配它自身,比如:[a^b] 就是匹配字符 a^b
    • 在字符组中,除了 ^-[]\ 外,其他在字符组的元字符不再具备特殊含义,变成了普通字符。比如字符 .*[.*] 就是匹配 . 或者 * 本身。

7. Java 中预定义的字符组有哪些

  • 有一些特殊的\ 开头的字符,表示一些预定义的字符组,比如:

    • \dd 表示 digit,匹配一个数字字符,等同于 [0-9]
    • \ww 表示 word,匹配一个单词字符,等同于 [a-zA-Z_0-9]
    • \ss 表示 space,匹配一个空白字符,等同于 [\t\n\x0B\f\r]
  • 它们都有对应的排除型字符组,用大写表示,即:

    • \D:匹配一个非数字字符,即 [^\d]
    • \W:匹配一个非单词字符,即 [^\w]
    • \S:匹配一个非空白字符,即 [^\s]

8. 什么是 POSI 字符组

  • POSIX 字符组是 POSIX 标准定义的一些字符组。
  • 在 Java 中,这些字符组的形式是 \p{…}

9. 正则表达式中的量词是什么意思

  • 量词指的是指定出现次数的元字符,有三个常见的元字符:+*?
    • +:表示前面字符的一次或多次出现。比如 ab+c,既能匹配 abc,也能匹配 abbcabbbc
    • *:表示前面字符的零次或多次出现。比如 ab*c,既能匹配 abc,也能匹配 acabbbc
    • ?:表示前面字符可能出现、也可能不出现。比如 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)dbc 就是一个分组。

    • 分组可以嵌套,比如 a(de(fg))
    • 分组默认都有一个编号,按照括号的出现顺序,从 1 开始从左到右依次递增。比如表达式:a(bc)((de)()fg),字符串 abcdefg 匹配这个表达式,第一个分组为 bc,第二个分组为 defg,第三个分组为 de,第四个分组为 fg。分组 0 是一个特殊分组,内容是整个匹配的字符串,这里是 abcdefg
    • 分组匹配的子字符串可以在后续访问,好像被捕获了一样,所以默认分组称为捕获分组
    • 可以对分组使用量词,表示分组的出现次数,比如 a(bc)+d,表示 bc 出现一次或多次。
  • 中括号 [] 表示匹配其中的一个字符,小括号 () 和元字符 | 一起,可以表示匹配其中的一个子表达式。比如:(http|ftp|file) 匹配 httpftpfile| 用于 [] 就不再有特殊含义,比如:[a|b],它的含义不是匹配 ab,而是 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] 表示以一个不是 abc 的字符开始。
    • 默认情况下,$ 匹配整个字符串的结束。不过,如果整个字符串以换行符结束$ 匹配的是换行符之前的边界。比如,abc$ 表示整个表达式以 abc 结束,或者以 abc\r\nabc\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,还包括中文字符。
    • 边界匹配不同于字符匹配,可以认为,在一个字符串中,每个字符的两边都是边界,而上面介绍的这些特殊字符,匹配的都不是字符,而是特定的边界。

14. 怎么理解环视边界匹配

  • 对于边界匹配,还有一种更为通用的方式,那就是环视

  • 环视的字面意思就是左右看看,需要左右符合一些条件。本质上,它也是匹配边界,对边界有一些要求,这个要求是针对左边或右边的字符串的。根据要求不同,分为 4 种环视:

    • 肯定顺序环视:语法是 (?=...),要求右边的字符串匹配指定的表达式。比如,表达式 abc(?=def)(?=def) 在字符 c 右面,即匹配 c 右面的边界。对这个边界的要求是:它的右边有 def,比如 abcdef,如果没有,比如 abcd,则不匹配。
    • 否定顺序环视:语法是 (?!...)。要求右边的字符串不能匹配指定的表达式。比如,表达式 s(?!ing),匹配一般的 s,但不匹配后面有 ings。注意:避免与排除型字符组混淆,比如 s[^ing]s[^ing] 匹配的是两个字符,第一个是 s,第二个是 ing 以外的任意一个字符。
    • 肯定逆序环视:语法是 (?<=...),要求左边的字符串匹配指定的表达式。比如,表达式 (?<=\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 个 \,即 \\\\

16. 不区分大小写的匹配模式是

  • 指定方式有两种:
    • 一种是在正则表达式开头使用 (?i)iignore。比如,(?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 之间的所有字符视为普通字符
\. \* \? 转义元字符为普通字符
-------------------- 本文结束感谢您的阅读 --------------------