正则表达式-基础
前言
正则表达式,是一种特殊的字符串模式,用来匹配一系列符合某个规则的字符串。就好比用模具做产品,而正则就是这个模具,定义一种规则去匹配符合规则的字符串。
正则的意思是正规、规则。正则表达式的英文名是 Regular Expression,可以直译为描述某种规则的表达式,一般缩写为 regex。正则表达式是从左往右匹配的。
初窥门径
我们先给出一个很常见的例子,判断一个字符串是否为一个有效的电话号码。
正常的思路是先判断字符串是否为11位,再判断每一为是否为数字即可。
public static boolean isValidTelphone(String number) {
//判断长度为11位
if(number.length() != 11) {
return false;
}
//判断是否为数字
for(int i=0;i<number.length();i++) {
if(number.charAt(i)<'0' || number.charAt(i)>'9') {
return false;
}
}
return true;
}
使用正则表达式
public static boolean isValidTelphone(String number) {
return number.matches("\\d{11}");
}
这个例子告诉我们,使用正则表达式的代码实现相当简洁高效。当然我们不急着讲解这个例子的正则表达式是如何构造的,继续学习之后自然也就明白了。
我们先来讲讲正则表达式的精确匹配,一个普通的字符串,例如 a b c abc abc,它如果用来做正则表达式匹配的话,只能匹配它自己。也就是说它只能匹配 a b c abc abc,不能匹配 a b ab ab 或者 A b c Abc Abc等其他任何字符串。
System.out.println("abc".matches("abc")); //输出true
System.out.println("abc".matches("ab")); //输出false
System.out.println("abc".matches("Abc")); //输出false
事实上正则表达式的精确匹配很少用到,但它作为一条基本规则应该被了解。
对于精确匹配,实际上就是判断字符串相等,我们使用 Java 语言的 String.equals()方法同样也可以实现。
如果需要匹配的字符串含有特殊字符,就需要用 \ 进行转义。比如 a&b,进行正则表达式匹配时,需要使用 a\ &b,又因为 \ 也是特殊字符,它也需要转义,所以 a\ &b 对应的 Java 字符串是 a\ \ &b,它是用来匹配 a&b 的。
System.out.println("a&b".matches("a\\&b")); //输出true
请记住,这两个反斜杠的意义并不一样,一个是正则的转义,一个是字符串的转义。
回到我们最开始的判断电话号码的例子,\ \d 的本意其实就是 \d。 \d 在正则表达式中表示匹配任意数字 ,d 是 digital 的意思。比如 00\d 就可以匹配 000、007、008 等。注意一下,\d 只能匹配单个数字。我们不可以使用 00\d 去匹配 0066 。
那么问题来了,怎么才能匹配多个数字呢?
- 如果数字不多的话,我们可以写多次 \d。比如 \d\d 能匹配两个数字,\d\d\d 能匹配三个数字。
System.out.println("1".matches("\\d\\d")); //输出false
System.out.println("11".matches("\\d\\d")); //输出true
System.out.println("111".matches("\\d\\d\\d")); //输出true
- 如果数字太多的话,我们在 \d 后面打上花括号 { },{n}表示匹配 n 次 ,\d{10000} 就表示匹配10000个数字,
System.out.println("123456789".matches("\\d{10}")); //输出false
System.out.println("1234567890".matches("\\d{10}")); //输出true
如果要匹配 n 至 m 次,用 {n,m} 即可。如果要至少匹配 n 次,用{n,} 。注意“,”之后没有空格。
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("1234".matches("\\d{2,}")); //输出true
如果要至多匹配 m 次,用 {0,m} 即可 。千万不要想当然地使用 {,m},只是因为正无穷不好表示我们才用的 {n,},实际上根本没有 {,m} 这样的写法。
现在你应该完全理解了判断电话号码的例子,让我们继续进行学习。
渐入佳境
正则的基础规则中,除了 \d,还有 \w 和 \s。
- w是 w o r d word word 的缩写,匹配一个常用字符,包括字母、数字、下划线。
- s是 s p a c e space space 的缩写,匹配一个空白字符,包括空格键打出来的空格,以及 \t \n \r \f。
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
记住上面的三个规则之后,我们可以顺带记住几个新规则。因为在正则表达式中,把字母换成大写,就表示相反的意思。
- 用 \d 表示匹配一个数字,\D 则表示匹配一个非数字的字符
- 用 \w 表示匹配一个常用字符,\W 则表示匹配一个不是字母、数字、下划线的字符
- 用 \s 表示匹配一个空白字符,\S 则表示匹配一个非空白字符
System.out.println("1".matches("\\D")); //输出false
System.out.println("*".matches("\\D")); //输出true
System.out.println("b".matches("\\W")); //输出false
System.out.println("&".matches("\\W")); //输出true
System.out.println("\r".matches("\\S")); //输出false
System.out.println("%".matches("\\S")); //输出true
有时候,我们对某些位置的字符没有要求,仅仅需要占个位置而已,我们可以使用 . . . 字符,可以理解为 . . . 能够匹配任意字符。
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
同样地,有时候我们对匹配次数也没有要求,匹配任意次均可。这时,我们使用 * 字符。 ∗ * ∗ 是指可以匹配任意次,包括 0 次,也就是说, ∗ * ∗ 等价于 ${0,} 。
System.out.println("1234".matches("\\d*")); //输出true
System.out.println("1".matches("\\d*")); //输出true
System.out.println("".matches("\\d*")); //输出true
如果要判断某个字符至少出现一次,那就可以使用 + + + 字符进行匹配, + + + 表示至少匹配一次,等价于 1 , {1,} 1, 。
System.out.println("1234".matches("\\d+")); //输出true
System.out.println("1".matches("\\d+")); //输出true
System.out.println("".matches("\\d+")); //输出false
还有一种场景,如果某个字符要么匹配 0 次,要么匹配 1 次,我们就可以用 ? ? ? 匹配,它等价于 0 , 1 {0,1} 0,1 。
System.out.println("".matches("\\d?")); //输出true
System.out.println("1".matches("\\d?")); //输出true
System.out.println("1234".matches("\\d?")); //输出false
请一定要记住这些规则哦。
- . . . 表示匹配任意字符
- ∗ * ∗ 匹配任意次,包括 0 次, ∗ * ∗ 等价于 0 , {0,} 0,
- + + + 匹配至少 1 次,它等价于 1 , {1,} 1,
- ? ? ? 匹配 0 次或 1 次
神功小成
学习到这里,你已经掌握了不少正则表达式规则了,但是不能骄傲自满,我们还要把一些容易埋坑的地方给踩掉。
回到我们最开始的判断电话号码的例子,如果我们规定电话号码不能以 0 开头,那么应该怎么写正则表达式呢?
很明显,如果用 d 11 \\d{11} d11 来匹配的话,0 是会进行匹配的,不满足我们的要求。这样的场景我们需要用 [ ] 来匹配, [ ] 用于匹配指定范围内的字符,比如 [ 123456789 ] [123456789] [123456789] 可以匹配 1-9。
聪明如你,看到这里应该就明白了,解决这个问题的正则表达式是
[123456789]\\d{10}
这里有一个小技巧, [ 123456789 ] [123456789] [123456789] 写起来太麻烦,可以写作 [ 1 − 9 ] [1-9] [1−9]。类似地, [ a − g ] [a-g] [a−g] 表示 [ a b c d e f g ] [abcdefg] [abcdefg], [ U − Z ] [U-Z] [U−Z] 表示 [ U V W X Y Z ] [UVWXYZ] [UVWXYZ]。
如果既可以是数字 1-9,又可以是字母 a-g,还可以是字母 U-Z,我们可以这么写 [ 1 − 9 a − g U − Z ] [1-9a-gU-Z] [1−9a−gU−Z]。
System.out.println("3".matches("[1-9a-gU-Z]")); //输出true
System.out.println("d".matches("[1-9a-gU-Z]")); //输出true
System.out.println("Y".matches("[1-9a-gU-Z]")); //输出true
System.out.println("A".matches("[1-9a-gU-Z]")); //输出false
如果是 0-1,8-9 这样的组合,我们可以直接写为 [ 0189 ] [0189] [0189],这样非常简洁。我们也可以按照规则把它写为 [ 0 − 18 − 9 ] [0-18-9] [0−18−9],因为正则一次只匹配一个字符,所以这样写不会产生歧义,计算机不会把它误解成要匹配 0-18 之类的。
System.out.println("9".matches("[0189]")); //输出true
System.out.println("8".matches("[0-18-9]")); //输出true
System.out.println("5".matches("[0-18-9]")); //输出false
还有一种写法可以实现这一点,那就是“或”运算符。正则的“或”运算符是 ∣ | ∣, [ 0189 ] [0189] [0189] 也可以写成 [ 0 ∣ 1 ∣ 8 ∣ 9 ] [0|1|8|9] [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("ab".matches("ab|AB")); //输出true
System.out.println("AB".matches("ab|AB")); //输出true
System.out.println("12".matches("ab|AB")); //输出false
如果我们想排除某些字符,比如这个位置不能是 [ 123 ] [123] [123]。之前说的取反是针对字母的, [ ] [] []没有大写这一说, [ ] [] []取反的方式是 [^],比如不能是 [ 123 ] [123] [123] 的表示方法是 [^123] 或者 [^1-3]。
System.out.println("5".matches("[^1-3]")); //输出true
System.out.println("2".matches("[^1-3]")); //输出false
我们再介绍一个贪婪模式与非贪婪模式。贪婪模式在基础部分仅做了解,在进阶部分我们会用例子详细说明。
- 贪婪就是尽量往多的匹配,比如"+",匹配一次或多次 ,优先匹配更多的。
- 非贪婪模式就是尽量少的匹配,比如"?",匹配0次或1次,优先匹配少的。
- 编程中如何区分呢,在贪婪匹配后面加个"?",就变成了非贪婪模式。
学习到了这一步,可以恭喜你完成了对新手的全部教程。你现在所学习的正则知识已经足够你应付许多应用场景了。
现在你可以多看看练功宝典,投入到实践的海洋中,多多练习,扎实基础。也可以选择继续深入地进行学习,掌握更高深的正则规则。正则表达式-进阶
练功宝典
常用的匹配规则
模式 | 描述 |
---|---|
\d | 匹配任意数字,等价于[0-9] |
\D | 匹配任意非数字的字符 |
\w | 匹配字母、数字及下划线 |
\W | 匹配不是字母、数字及下划线的字符 |
\s | 匹配任意空白字符,等价于[\t\n\r\f] |
\S | 匹配任意非空字符 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
^ | 匹配一行字符串的开头 |
$ | 匹配一行字符串的结尾 |
. | 匹配任意字符,不特殊指定话默认不匹配换行符 |
[…] | 用来表示一组字符,比如[ack]匹配a、c、k |
[^…] | 不在[…]中的字符,比如[^abc]匹配除了a、b、c之外的字符 |
* | 匹配0个或多个表达式 |
+ | 匹配1个或多个表达式 |
? | 匹配0个或1个前面的正则表达式定义的片段,非贪婪方式 |
{n} | 精确匹配n个前面的表达式 |
{n,m} | 匹配n到m次前面正则表达式定义的片段,贪婪方式 |
a|b | 匹配a或b |
( ) | 匹配括号内的表达式,也表示一个组 |