正则表达式


十二月的第四周,来学习正则表达式。

工作时经常需要用到正则表达式来校验字符串,每次遇到都怵怵的,这周来扫除一下盲区。


正则表达式(regular expression),根据英文也可简写为 regex。“正则”这个中文翻译颇有民国风度,所谓公正而乎法则,今已查不到何处还在用此词,似乎只有编程领域在用。

正则表达式只用于字符串,用来检查字符串是否符合某种规定(例如只有字母、又例如没有数字等),或者是取出符合规定的字符串子串(用于后续替换等)。

例如下图,为匹配以 Hello 开头的字符串的正则表达式,成功匹配到了两条。

1576558383714

(上图截图自 https://regexr.com/ 网站,是一个很好用的正则表达式在线测试网站)

此外再安利一组入门正则表达式的视频:《表严肃讲正则表达式》,供入门使用。这组视频讲得很用心,不像大部分编程公开课跟倒泔水一样地倾泻几十个小时垃圾,我能感受到它是精心准备过的,很是喜欢。



正则表达式的概念不难理解,若不清晰用过一两次便懂。使用正则表达式若有困难,都是因为语法繁多,需要记忆的内容量大。下面整理了三张正则表达式的常用语法字符表,主要来源为菜鸟教程的正则表达式教程。分三张表,分别是:

  • 非打印字符:转义后的字符,代表某一类字符
  • 特殊字符:含特殊含义的字符
  • 限定字符:字符次数限制

非打印字符

字符 描述 正确示例 错误示例
\w 数字、字母、下划线 1/a/A/_ ./&/の/【/(空格)/(换行)
\W 除数字、字母、下划线以外 ./&/の/【/(空格)/(换行) 1/a/A/_
\s 空白字符 (空格)/→|(制表符)/(换行)/(换页) 1/a/A/_/./&/♂
\S 除空白字符以外 1/a/A/_/./&/♂/张 (空格)/→|(制表符)/(换行)/(换页)
\d 数字(单个) 1/2/3/4/5/6/7/8/9/0 a/?/&/1234(整体)
\D 除数字以外 a/B/,/%/张/(空格) 1/2/3/1234(整体)
\n 换行符 (换行) 1/a/A/_/?
\t 制表符 →|(制表符) 1/a/A/_/?

特殊字符

特殊字符 描述 正确示例 错误示例
. 除换行以外任意字符 1/a/A/%/_ (换行)/123(整体)
^ 从字符串开始的位置处匹配 ^12 -> 12/123/12a ^12 -×-> a12/abc12/
$ 从字符串结束的位置处匹配(倒着) 12$ -> 12/abc12/012 12$ -×-> 123/12aaa
| [1|2|3] -> 1/2/3 [1|2|3] -×-> a/4/&
[] 方框运算符,字符集合/范围/反向
()
{}

限定字符

限定字符 描述 正确示例 错误示例
* 匹配零个、一个或多个字符 12*3 -> 13/123/1223 12*3 -×-> 1
+ 匹配一个或多个字符 12+3 -> 123/12223 12+3 -×-> 13
? 匹配零个或一个字符 12?3 -> 13/123 12?3 -×-> 12223
{n} 字符限定n次 2{4} -> 2222 2{4} -×-> 222/22222(整体)
{n,} 字符限定最少n次 2{2,} -> 22/222/22222 2{2,} -×-> 2
{n,m} 字符限定n-m次 2{2,3} -> 22/222 2{2,3} -×-> 2/2222(整体)


除以上常用的语法字符之外,还有以下规则,需要单独注意:

[ ]

方括号表示字符集合,可配合 ^ 符合表示反向的字符集合,常见用法有:

示例 说明
[12a] 可匹配 ‘1’ 或 ‘2’ 或 ‘a’
[1|2|a|,] 可匹配 ‘1’ 或 ‘2’ 或 ‘a’ 或 ‘,’
[1-9] 可匹配 ‘1’ 或 ‘2’ 或 …… 或 ‘9’
[a-z] 可匹配 ‘a’ 或 ‘b’ 或 …… 或 ‘z’
[^12a] 可匹配除了 ‘1’ 或 ‘2’ 或 ‘a’ 以外的字符
[^1-9] 可匹配除了 ‘1’ 或 ‘2’ 或 …… 或 ‘9’ 以外的字符

()

圆括号代表同时匹配多个字符,如匹配 hello 这五个字符,可以使用 (hello) 来匹配。

但是除此之外,() 匹配到的字符串还会被缓存起来,缓存起来的子表达式可以在之后使用。

例如使用([nN]o)匹配 No, get out!,不光能匹配到 No,还能通过 $1oooooooo 将原文替换成 Nooooooooo, get out!

示例 说明
(hello) 可以匹配 “hello”,并缓存起来
(?:hello) 可以匹配 “hello”,但不缓存结果
(用于匹配多字符但并不需要结果,有时能让正则表达式更简洁)
hello(?=AA) 可以匹配 “helloAA……” 中的 “hello”,但是不能匹配 “helloBB……” 中的 “hello”,且不缓存结果
hello(?!AA) 不能匹配 “helloAA……” 中的 “hello”,但是可以匹配 “helloBB……” 中的 “hello”,且不缓存结果
(?<=AA)hello 可以匹配 “AAhello……” 中的 “hello”,但是不能匹配 “BBhello……” 中的 “hello”,且不缓存结果
(?<!AA)hello 不能匹配 “AAhello……” 中的 “hello”,但是可以匹配 “BBhello……” 中的 “hello”,且不缓存结果

贪婪

贪婪是编程和算法中常见的概念,意思是越多越好。

* 和 + 这两个限定符都是贪婪的,如果用 hello* 或者 hello+ 来匹配 hellooooooo,它们的匹配结果都是 hellooooooo,而不是 hellhellohellooo 或是其他的。

在 * 和 + 后面加上 ? 就可以使匹配结果是非贪婪的(最小匹配)。按照上面的例子,hello*? 匹配的结果是 hellhello+? 匹配的结果是 hello



写几个看到过的正则表达式,举几个例子

  • 身份证校验(简易版)

    ^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$

  • 不能包含数字

    ^[^\d]+$

  • 浮点数

    ^(-?\d+)(\.\d+)?$

  • 域名

    ^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$



学习过正则表达式的基本语法之后,感觉在工作中还是不够用,还要学习 Java 中正则表达式的使用。

正则表达式从 JDK 1.4 之后出现,涉及到两个新类:Pattern 和 Matcher。Pattern 类代表一个正则表达式,而 Matcher 类代表一个正则表达式对一个字符串进行校验后的结果,例如:

1
2
3
4
5
6
7
// 正则表达式,规则:不能包含数字
String regex = "^[^\\d]+$";
Pattern compile = Pattern.compile(regex);

// 匹配abcABC?&
String str = "abcABC?&";
Matcher matcher = compile1.matcher(str);

Pattern 类没有(对外的)构造方法,因此生成一个 pattern 对象只能通过 Pattern 类的静态方法。

如果只是想检验字符串是否符合要求(即只需要一个boolean值),那么使用 Pattern 类的静态方法就可以,pattern 对象是为了更多操作。

1
2
3
4
5
String regex = "^[^\\d]+$";
String str = "abcABC?&";
boolean pass = Pattern.matches(regex, str);

// ------结果:pass:true

Pattern 类多实现一些,用代码实现正则表达式的功能,而不是完全只用字符串来实现。例如它能指定正则表达式可以同时匹配大小写字母(指定后,正则表达式字符串即使只有 ‘a’,匹配时也可以同时匹配 ‘a’ 和 ‘A’),又或者可以用指定正则表达式分隔字符串,例如将一串带有数字的字符串,以数字为分隔拆分成多个子字符串。

Matcher 类多实现一些对匹配结果的处理,这一部分具体没怎么看,需要时再补吧。

最后提一句,Java 中使用正则表达式时,基本上要出现 \ 的地方,都要转义成 \\,例如 \d -> \\d

本周就学习到这里了。