以下内容均为个人学习笔记。
01
正则的意思是正规、规则。正则表达式的英文名是 Regular Expression,可以直译为描述某种规则的表达式,一般缩写为 regex。
02
判断一个字符串是不是有效的电话号码?
public static boolean isValidPhoneNumber(String number) {
return number.matches("\\d{11}");
}
03
正则表达式的精确匹配。一个普通的字符串,比如 abc
,它如果用来做正则表达式匹配的话,只能匹配自己。也就是说它只能匹配字符串 abc
,不能匹配 ab
,Abc
等其他任何字符串。
System.out.println("abc".matches("abc")); // 输出为 true
System.out.println("ab".matches("abc")); // 输出为 false
System.out.println("Abc".matches("abc")); // 输出为 false
如果需要匹配的字符串含有特殊字符,那就需要用 \
转义。比如 a&b
,在用正则表达式匹配时,需要使用 a\&b
,又由于在 Java 字符串中,\
也是特殊字符,它也需要转义,所以 a\&b
对应的 Java 字符串是 a\\&b
,它是用来匹配 a&b
的。
System.out.println("a&b".matches("a\\&b")); // 输出为 true
这两个反斜杠的意义竟然还不一样:一个是正则的转义,一个是 Java 字符串的转义。
\d
在正则表达式中表示匹配任意数字,d 是 digital 的简写。比如 00\d
就可以匹配 000
, 007
,008
等等。
- 在
\d
后面打上花括号{}
,{n}
表示匹配 n 次。\d{10000}
就表示匹配 10000 个数字。 - 如果要匹配 n ~ m 次,用
{n,m}
即可,如果要匹配至少 n 次,用{n,}
即可。需要注意 , 后不能有空格。 - 如果要匹配最多 m 次, 直接用
{0,m}
, 没有{,m}
这样的写法。
System.out.println("1".matches("\\d{1,2}")); // 输出为 true
System.out.println("12".matches("\\d{1,2}")); // 输出为 true
System.out.println("123".matches("\\d{1,2}")); // 输出为 false
System.out.println("123".matches("\\d{2,}")); // 输出为 true
04
正则的基础规则中,除了 \d
,还有 \w
和\s
,w 是 word 的简写,表示匹配一个常用字符,包括字母、数字、下划线。s 是 space 的简写,表示匹配一个空格,包括三种:
- 空格键打出来的空格
- Tab 键打出来的空格
\t
- 回车键打出来的空格
\n
System.out.println("LeetCode_666".matches("\\w{12}")); // 输出为 true
System.out.println("\t \n".matches("\\s{3}")); // 输出为 true
System.out.println("Leet\tCode 666".matches("\\w{4}\\s\\w{4}\\s\\d{3}")); // 输出为 true
05
将字母换成大写,就表示相反的意思。用 \d
你可以匹配一个数字,\D
则表示匹配一个非数字。
System.out.println("a".matches("\\d")); // 输出为 false
System.out.println("1".matches("\\d")); // 输出为 true
System.out.println("a".matches("\\D")); // 输出为 true
System.out.println("1".matches("\\D")); // 输出为 false
类似地,\W
可以匹配 \w
不能匹配的字符,\S
可以匹配 \s
不能匹配的字符。
06
有时候,我们对某些位置的字符没有要求,仅需要占个位置即可。这时候我们就可以用 .
字符。
System.out.println("a0b".matches("a.b")); // 输出为 true
System.out.println("a_b".matches("a.b")); // 输出为 true
System.out.println("a b".matches("a.b")); // 输出为 true
.
可以匹配任意字符。
有时候,我们对匹配的次数没有要求,匹配任意次均可,这时,我们就可以用 *
字符。
System.out.println("1".matches("\\d*")); // 输出为 true
System.out.println("123".matches("\\d*")); // 输出为 true
System.out.println("".matches("\\d*")); // 输出为 true
*
是指可以匹配任意次,包括 0 次。也就是说,*
等价于 {0,}
用 +
匹配,+
表示 至少匹配一次。它等价于 {1,}
System.out.println("1".matches("\\d+")); // 输出为 true
System.out.println("123".matches("\\d+")); // 输出为 true
System.out.println("".matches("\\d+")); // 输出为 false
还有一种场景,如果某个字符要么匹配 0 次,要么匹配 1 次,我们就可以用 ?
匹配。它等价于 {0,1}
System.out.println("".matches("\\d?")); // 输出为 true
System.out.println("1".matches("\\d?")); // 输出为 true
System.out.println("123".matches("\\d?")); // 输出为 false
-
.
匹配任意字符 -
*
匹配任意次,包括 0 次 -
+
号匹配至少 1 次 -
?
匹配 0 次或 1 次
07
[]
用于匹配指定范围内的字符,比如 [123456789]
可以匹配 1~9。
这里还有一个语法糖,[123456789]
写起来太麻烦,可以写作 [1-9]
。
比如 [a-g]
表示 [abcdefg]
,[U-Z]
表示 [UVWXYZ]
。
但如果既可以是数字 1~9,又可以是字母 a~g,还可以是字母 U~Z,还是得把所有范围列出来。不必,你还可以这么写:[1-9a-gU-Z]
。
System.out.println("1".matches("[1-9a-gU-Z]")); // 输出为 true
System.out.println("b".matches("[1-9a-gU-Z]")); // 输出为 true
System.out.println("X".matches("[1-9a-gU-Z]")); // 输出为 true
System.out.println("A".matches("[1-9a-gU-Z]")); // 输出为 false
如果是 0 ~ 1,8 ~ 9 可以这样组合吗?
[0189]
更简洁。
[0-18-9]
正是你想要的。由于正则一次只匹配一个字符,所以这样写并不会有歧义,也就是说计算机不会把这种写法误解成要匹配 0~18 之类的。
System.out.println("1".matches("[0-18-9]")); // 输出为 true
System.out.println("5".matches("[0-18-9]")); // 输出为 false
还有一种写法可以实现这一点,那就是用 或
运算符,正则的 或
运算符是 |
,[0189]
也可以写作 0|1|8|9
。
System.out.println("1".matches("0|1|8|9")); // 输出为 true
System.out.println("5".matches("0|1|8|9")); // 输出为 false
或
可以实现更多的功能,它并不局限于单个字符。
System.out.println("abc".matches("abc|ABC")); // 输出为 true
System.out.println("ABC".matches("abc|ABC")); // 输出为 true
System.out.println("123".matches("abc|ABC")); // 输出为 false
如果我想排除某些字符呢?比如这个位置不能是 [123]
。我记得你之前说正则王国以大写表示取反,[]
要怎么大写呢?
[]
可没有大写之说,[]
取反的方式是:[^]
,比如不能是 [123]
的表示方法为 [^123]
或者 [^1-3]
08
Name:Aurora Age:18
其中还夹杂着一些无关紧要的数据
Name:Bob Age:20
错误的数据有着各种各样错误的格式
Name:Cassin Age:22
...
System.out.println("Name:Aurora Age:18".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 true
System.out.println("其中还夹杂着一些无关紧要的数据".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 false
System.out.println("Name:Bob Age:20".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 true
System.out.println("错误的数据有着各种各样错误的格式".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 false
System.out.println("Name:Cassin Age:22".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 true
一般来说,下一步你要做的就是取出这些表达式中的姓名和年龄,以便把它们存到数据库中。
Pattern pattern = Pattern.compile("Name:(\\w+)\\s*Age:(\\d{1,3})");
Matcher matcher = pattern.matcher("Name:Aurora Age:18");
if(matcher.matches()) {
String group1 = matcher.group(1);
String group2 = matcher.group(2);
System.out.println(group1); // 输出为 Aurora
System.out.println(group2); // 输出为 18
}
只要用 ()
将需要取值的地方括起来,传给 Pattern 对象,再用 Pattern 对象匹配后获得的 Matcher 对象来取值就行了。每个匹配的值将会按照顺序保存在 Matcher 对象的 group 中。
你可以看到我用 ()
把 \\w+
和 \\d{1,3}
分别括起来了,判断 Pattern 对象与字符串是否匹配的方法是 Matcher.matches()
,如果匹配成功,这个函数将返回 true,如果匹配失败,则返回 false。
为什么 group 是从下标 1 开始取值的,计算机不都从 0 开始数吗?
这是因为 group(0) 被用来保存整个匹配的字符串了。
System.out.println(matcher.group(0)); // 输出为 Name:Aurora Age:18
阅读源码之后,你可以发现,每次调用 String.matches
函数,都会新建出一个 Pattern 对象。所以如果要用同一个正则表达式多次匹配字符串的话,最佳的做法不是直接调用 String.matches
方法,而应该先用正则表达式新建一个 Pattern 对象,然后反复使用,以提高程序运行效率。
// 错误的做法,每次都会新建一个 Pattern,效率低
boolean result1 = "Name:Aurora Age:18".matches("Name:(\\w+)\\s*Age:(\\d{1,3})");
boolean result2 = "Name:Bob Age:20".matches("Name:(\\w+)\\s*Age:(\\d{1,3})");
boolean result3 = "Name:Cassin Age:22".matches("Name:(\\w+)\\s*Age:(\\d{1,3})");
// 正确的做法,复用同一个 Pattern,效率高
Pattern pattern = Pattern.compile("Name:(\\w+)\\s*Age:(\\d{1,3})");
boolean result4 = pattern.matcher("Name:Aurora Age:18").matches();
boolean result5 = pattern.matcher("Name:Bob Age:20").matches();
boolean result6 = pattern.matcher("Name:Cassin Age:22").matches();
09
二分,回溯,递归,分治
搜索;查找;旋转;遍历
数论 图论 逻辑 概率
split 函数的源码,这个函数传入的参数实际上是一个正则表达式。
用正则表达式模糊匹配,只要能匹配成功,就以其分割。
System.out.println(Arrays.toString("二分,回溯,递归,分治".split("[,;\\s]+")));
System.out.println(Arrays.toString("搜索;查找;旋转;遍历".split("[,;\\s]+")));
System.out.println(Arrays.toString("数论 图论 逻辑 概率".split("[,;\\s]+")));
输出为:
[二分, 回溯, 递归, 分治]
[搜索, 查找, 旋转, 遍历]
[数论, 图论, 逻辑, 概率]
可以用正则表达式模糊匹配,将符合规则的字符串全部替换掉。比如就现在这个例子,我们可以把用户输入的所有数据统一规范为使用 ;
分隔,那我们就可以这样写。
System.out.println("二分,回溯,递归,分治".replaceAll("[,;\\s]+", ";"));
System.out.println("搜索;查找;旋转;遍历".replaceAll("[,;\\s]+", ";"));
System.out.println("数论 图论 逻辑 概率".replaceAll("[,;\\s]+", ";"));
输出为:
二分;回溯;递归;分治
搜索;查找;旋转;遍历
数论;图论;逻辑;概率
还不止这一点,在 replaceAll 的第二个参数中,我们可以通过 $1
,$2
,...来反向引用匹配到的子串。只要将需要引用的部分用 ()
括起来就可以了。
System.out.println("二分,回溯,递归,分治".replaceAll("([,;\\s]+)", "---$1---"));
System.out.println("搜索;查找;旋转;遍历".replaceAll("([,;\\s]+)", "---$1---"));
System.out.println("数论 图论 逻辑 概率".replaceAll("([,;\\s]+)", "---$1---"));
输出为:
二分---,---回溯---,---递归---,---分治
搜索---;---查找---;---旋转---;---遍历
数论--- ---图论--- ---逻辑--- ---概率
有时候我们不需要替换,只需要将正则匹配出来的部分添加一些前缀或后缀,就可以用这种方式!
10
给你一些字符串,统计其末尾 e 的个数
LeetCode
LeetCodeeee
LeetCodeee
Pattern pattern = Pattern.compile("(\\w+)(e*)");
Matcher matcher = pattern.matcher("LeetCode");
if (matcher.matches()) {
String group1 = matcher.group(1);
String group2 = matcher.group(2);
System.out.println("group1 = " + group1 + ", length = " + group1.length());
System.out.println("group2 = " + group2 + ", length = " + group2.length());
}
输出为:
group1 = LeetCode, length = 8
group2 = , length = 0
我期望的结果是 group1 等于 LeetCod,group2 等于 e 才对啊!
这是因为 e 仍然属于 \w
能匹配的范畴,正则表达式默认会尽可能多地向后匹配,我们王国将其称之为 贪婪匹配。
贪婪匹配和贪心算法原理是一致的。与之对应的匹配方式叫做 非贪婪匹配,非贪婪匹配会在能匹配目标字符串的前提下,尽可能少的向后匹配。
在需要非贪婪匹配的正则表达式后面加个 ?
即可表示非贪婪匹配。
Pattern pattern = Pattern.compile("(\\w+?)(e*)");
Matcher matcher = pattern.matcher("LeetCode");
if (matcher.matches()) {
String group1 = matcher.group(1);
String group2 = matcher.group(2);
System.out.println("group1 = " + group1 + ", length = " + group1.length());
System.out.println("group2 = " + group2 + ", length = " + group2.length());
}
输出为:
group1 = LeetCod, length = 7
group2 = e, length = 1
这里也用的是 ?
,我记得之前 ?
表示的是匹配 0 次或者 1 次,两个符号不会混淆吗?
不会混淆的,你仔细想一想就能明白了,如果只有一个字符,那就不存在贪婪不贪婪的问题,如果匹配多次,那么表示非贪婪匹配的 ?
前面必有一个标志匹配次数的符号。所以不会出现混淆。
非贪婪匹配的定义是在能匹配目标字符串的前提下,尽可能少的向后匹配。
11
我们王国有一个人口吃,请你帮忙矫正他。
他今天说:肚...子。。好饿........,....早知道.....当.....初...。。。多.....刷.....点。。。力.....扣了.........!
String message = "肚...子。。好饿........,....早知道.....当.....初...。。。多.....刷.....点。。。力.....扣了.........!";
System.out.println(message.replaceAll("[.。\\s]+", ""));
输出为:
肚子好饿,早知道当初多刷点力扣了!