正则表达式

1、什么是正则表达式?

正则表达式是使用字符串去描述一个匹配规则。

使用正则表达式可以快速判断给定的字符串是否符合匹配规则。

Java内建正则表达式引擎java.util.regex:

image-20200531111143482

2、匹配规则

从左到右按规则匹配。

字符 样例 说明
“abc” 精确匹配”abc”
“a\&c” 精确匹配”a&c”,特殊字符需转义字符’\‘修饰,java字符串还需两次转义’\\&’
\uxxxx “a\u548c’ ‘\uxxxx’可以匹配指定的Unicode字符,如”a和c”
. “a.c” ‘.’可以匹配一个任意字符,如”abc”、”a&c”等
\S “a\Sb” ‘\s’可以匹配一个非空白字符,如”a#b”、”a2b”、”a_c”等
\s “a\sb” ‘\s’可以匹配一个空白字符(空格、tab等),如”a b”、”a b”等
\D “00\D” ‘\D’可以匹配一个非数字字符,如”00x”、”00A”、”00 “等
\d “00\d” ‘\d’可以匹配一个数字字符(0~9),如”001”、”008”等
\W “java\W” ‘\W’可以匹配一个非字母、数字、下划线字符,如”java “、”java#”等
\w “java\w” ‘\w’可以匹配任何字符类字符(字母、数字),包括下划线,如”java8”、”java_”等
* “AB*” ‘*’修饰前面一个字符,可以匹配任意个字符,如”AB”、”ABBBB”等
+ “AB+” ‘+’修饰前面一个字符,可以匹配至少一个字符,如”AB”、”ABBB”等
? “AB?” ‘?’修饰前面一个字符,可以匹配一个或零个字符,如”A”、”AB”
{n} “A{3}B” ‘{n}’修饰前面一个字符,可以匹配n个字符,如”AAAB”
{n,m} “A{2,4}B” ‘{n,m}’修饰前面一个字符,可以匹配n~m个字符,如”AAB”、”AAAB”、”AAAAB”
{n,} “A{3,}B” ‘{n,}’修饰前面一个字符,可以匹配至少n个字符,如”AAAB”、”AAAAB”等
\B “P\BAP” ‘\B’可以匹配字符与字符、符号与符号的边界,如”PAP”
\b “,\bAP” ‘\b’可以匹配字符与符号的边界,如”,AP”

3、分组匹配

可以使用()来对所匹配的字符串进行分组。

例如:

  • 提取年月日:

    (\d{4})\-(\d{1,2})\-(\d{1,2})

    可以将年-月-日给提取出来:

    “2020-5-31” -> “2020”、”5”、”31”。

  • 提取电话号码 ###-########

    ^(\d{3,4})-(\d{6,8})$

    ^表示开头

    \d{3,4}即3-4位区号

    \d{6,8}即6-8位电话号码

    $表示结束

    在Java中:

    1
    String regex = "^(\\d{3,4})\\-(\\d{6,8})$";
  • 提取24小时时间 ##:##

    ^([0-1][0-9]|2[0-3])\:([0-5][0-9])$

    [0-1][0-9]表示0~19,然后|表示或,2[0-3]表示20~23,即一共24小时

    [0-5][0-9]则为0~59,共60秒

    1
    String regexTime = "^([0-1][0-9]|2[0-3]):([0-5][0-9])$";
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
37
38
39
package com.trtan.regex;

import com.sun.org.apache.xerces.internal.impl.xpath.regex.Match;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexTest {
public static void main(String[] args) {
//yyyy-mm-dd
String regexY = ".*^(\\d{4})\\-(\\d{1,2})\\-(\\d{1,2})$";
//xxxx-yyyyyyyy
String regex = "^(\\d{3,4})\\-(\\d{6,8})$";
//hh:mm
String regexTime = "^([0-1][0-9]|2[0-3]):([0-5][0-9])$";
Pattern pattern = Pattern.compile(regexY);
Matcher matcher = pattern.matcher("2014-12-21");
if(matcher.matches()) {
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
System.out.println(matcher.group(3));
}

Pattern pattern1 = Pattern.compile(regex);
Matcher matcher1 = pattern1.matcher("086-13845785");
if (matcher1.matches()) {
System.out.println(matcher1.group(1));
System.out.println(matcher1.group(2));
}

Pattern pattern2 = Pattern.compile(regexTime);
Matcher matcher2 = pattern2.matcher("21:45");
if (matcher2.matches()) {
System.out.println(matcher2.group(1));
System.out.println(matcher2.group(2));
}

}
}

正则表达式分组,可以通过Matcher对象快速提取子串。

group(0)表示匹配整个字符串,group(1)表示匹配第一个子串,…

4、复杂匹配规则

字符 样例 说明
^和$ ^A\d{3}$ ‘^’和’$’代表匹配的开头和结尾,如:A123、A845等
[…] [abc]1 […]可以匹配范围内的一个字符,如:a1、b1、c1
[a-f]1 如:a1、b1、d1等
[a-f0-9_]{6} […]{n}可以匹配范围内的n个字符,如:123f_45、aaacda、123485等
[^…] [^0-9]{6} [^…]{n}可以匹配非范围内的n个字符,如ffffff、AOj_s-、hoodss等
| AB|CD |或运算,可以匹配多个条件的任意一个,如AB、CD
\n (\d+)(a)\1 ‘\n’表示与第n组括号里的内容相同,如123a123,584a584等

(补一个MarkDown的问题:要用&#124 ; 表示制表符|)

5、非贪婪匹配

如果想实现一个这样的匹配:

给定一个数字字符串,找出末尾连续0的个数,比如:

1320000,4个0

1024000,3个0

124,0个0

那么使用^(\d+)(0*)$去匹配,结果是(\d+)将字符串全部匹配了,(0*)只能匹配空串。

这是因为正则默认使用贪婪匹配,尽可能的向后匹配。

?来实现非贪婪匹配,即改为^(\d+?)(0*)$去匹配。就会尽可能少的去匹配。

1
2
3
4
5
6
7
8
9
10
11
String str = "^(\\d??)(9*)$";
Pattern pattern4 = Pattern.compile(str);
Matcher matcher4 = pattern4.matcher("9999");
if(matcher4.matches()) {
System.out.println(matcher4.group(1));
System.out.println(matcher4.group(2));
}
/*

9999
*/

对于9999,第一个?表示匹配0个或1个数字,第二个?表示非贪婪匹配,就会按照最少的来匹配,即匹配0个,让后面那组匹配的数量更多一点。

6、搜索和替换

分割字符串

1
2
3
4
5
6
7
8
9
//把字符串拆为数组
//初始字符串格式很不规范
String example = "1, 2; 3, 4. 5, 6, 7, 8, 9";
String[] array_s = example.split("[,.;\\s]+");
Integer[] array = new Integer[array_s.length];
for (int i = 0; i < array.length; i++) {
array[i] = Integer.parseInt(array_s[i]);
System.out.println(array[i]);
}

搜索子串

Matcher.find()

1
2
3
4
5
6
7
8
//搜索text中的Spring
String text = "Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient. Spring NB";
Pattern pattern5 = Pattern.compile("Spring");
Matcher matcher5 = pattern5.matcher(text);
while (matcher5.find()) {
String sub = text.substring(matcher5.start(), matcher5.end());
System.out.println(sub);
}

替换字符串

String.replaceAll()

1
2
3
4
//比如要去掉text的所有空格,使用' '分割所有单词,然后对每个单词加粗(<br></br>包裹)
String text = "Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient. Spring NB";
System.out.println(text.replaceAll("[\\s,.]+", " ")
.replaceAll("(\\w+)", "<br>$1</br>"));

7、正则表达式练习网站

https://alf.nu/RegexGolf

第一题:

1
foo

第二题:

以k结尾,可使用”$”,也可使用”\b”表示字符与符号的边界

1
2
k$
k\b

第三题:

以u结尾,但是不允许使用 $ ,那就只能”\b”

1
u\b

第四题:

左边前四位为a-f的字母,右边不符合。

1
2
^[a-f]*$
^[a-f]{4}

第五题:

新知识:“\n”表示与第n组括号里的内容相同,如123a123,584a584等

左边前三位与中间三位相同

1
^(...).*\1

第六题:

右边有四位的回文串(abba、otto等)

新知识:

字符 样例 说明
(?:pattern) industr(?:y|ies) 不获取匹配结果,不进行存储,可以代替”industry|industries”
(?=pattern) Windows(?=95|98) 能匹配Windows98、Windows95中的Windows
(?!pattern) Windows(?!95|98) 能匹配Windows2020中的Windows
(?<=pattern) (?<=95|98)Windows 能匹配98Windows、95Windows中的Windows
(?<pattern) (?<95|98)Windows 能匹配2020Windows中的Windows

从上面表中可以知道,”?=”和”?!”可以用来前瞻匹配,题目要求的是排除回文串,那么使用”?!”。

首先回文串很容易写(.)(.)\2\1

然后^(?!(.)(.)\2\1)这个意思就是不以回文开头,发现右边好多匹配的。

于是^(?!.*(.)(.)\2\1)允许有前缀,就可以干掉右边匹配的了。

1
^(?!.*(.)(.)\2\1)

第七题:

发现左边首尾相同且为回文串,右边没有这个特征。

1
^(.)(.).*\2\1$

最后更新: 2020年06月13日 17:47

原始链接: http://tanruidd.github.io/2020/06/13/正则表达式/