正则表达式的匹配规则是从左到右按规则匹配。
如果正则表达式有特殊字符,那就需要用\
转义。
比如:
正则表达式
a\&c
,其中\&
是用来匹配特殊字符&
的,它能精确匹配字符串"a&c"
,但不能匹配"ac"
、"a-c"
、"a&&c"
等。
注意正则表达式在Java代码中也是一个字符串,所以,对于正则表达式
a\&c
来说,对应的Java字符串是"a\\&c"
,因为\
也是Java字符串的转义字符,两个\\
实际上表示的是一个\。
我们可以使用 "." 来匹配一个任意的字符。
例如:String reg = "a.c"; 可以匹配如下字符串:
"abc"
,因为.
可以匹配字符b
;"a&c"
,因为.
可以匹配字符&
;"acc"
,因为.
可以匹配字符c;
但是 "." 不能匹配
"ac"
、"a&&c"
,因为"."
匹配一个字符且仅限一个字符。
用 "." 可以匹配任意字符,范围太大了。而 \d 可以用来匹配 0~9 这样的数字,且仅匹配单个数字字符。
比如: String reg = "00\d"; 可以匹配如下字符串:
"007"
,因为\d
可以匹配字符7
;"008"
,因为\d
可以匹配字符8
。用 \w 可以匹配一个字母、数字、下划线。w的意思是 word。
比如:String reg = "java\w" 可以匹配如下字符串:
"javac"
,因为\w
可以匹配英文字符c
;"java9"
,因为\w
可以匹配数字字符9
;。"java_"
,因为\w
可以匹配下划线_
。用 \s 可以匹配一个空格字符,空格字符包括 (" " 和 "\t" 即tab字符)。
比如:String reg = "a\sc";可以匹配如下字符串:
"a c"
,因为\s
可以匹配空格字符;"a c"
,因为\s
可以匹配tab字符\t
。用 \d 可以匹配一个数字,相反,用 \D 可以匹配一个非数字。
类似的,\W 可以匹配 \w 不能匹配的字符。
类似的,\S 可以匹配 \s 不能匹配的字符。
例如:String reg = "00\D";可以匹配如下字符串:
"00A"
,因为\D
可以匹配非数字字符A
;"00#"
,因为\D
可以匹配非数字字符#
。比如一个 \d 可以匹配一个数字字符,但如果要匹配多个数字字符,该怎么进行匹配呢?
1.7.1 修饰符 * 可以匹配任意个字符,包括0个字符。
比如:String reg = "A\d*"; 可以匹配如下字符串:
A
:因为\d*
可以匹配0个数字;A0
:因为\d*
可以匹配1个数字0
;A380
:因为\d*
可以匹配多个数字380
。1.7.2 修饰符 + 可以匹配至少一个字符。
比如:String reg = "A\d+"; 可以匹配如下字符串:
A0
:因为\d+
可以匹配1个数字0
;A380
:因为\d+
可以匹配多个数字380
。1.7.3 修饰符 ?可以匹配 0或1 个字符。
比如:String reg = "A\d?";可以匹配如下字符串:
A
:因为\d?
可以匹配0个数字;A0
:因为\d?
可以匹配1个数字0
。1.7.4 用 {n} 精确匹配 n 个字符。
比如:String reg = "A\d{3}";可以匹配如下字符串:
A380
:因为\d{3}
可以匹配3个数字380
。1.7.5 用 {n, m} 精确匹配 n~m 个字符。
比如:String reg = "A\d{3,5}";可以匹配如下字符串:
A380
:因为\d{3,5}
可以匹配3个数字380
;A3800
:因为\d{3,5}
可以匹配4个数字3800
;A38000
:因为\d{3,5}
可以匹配5个数字38000
。如果不设置上限,那么修饰符 {n,} 可以匹配至少 n 个字符。
用正则表达式进行多行匹配时,我们用 ^
表示开头,用 $
表示结尾。
例如,String reg = "^A\d{3}$"
,可以匹配"A001"
、"A380"
。
使用 [......] 可以匹配范围内的字符。比如 [123456789] 可以匹配到 1~9 。
比如: String reg = "[123456789]\d{6,7}";可以匹配到不以0开头的一个7~8位的电话号码。
[......] 还有一种简便的写法,如:[1-9]。
比如:String reg = "[0-9a-fA-F]";可以匹配到字符串 "1A2b3c"。
比如:String reg = "[0-9a-fA-F]{6}";可以匹配到一个6位的 十六进制数。
[......] 还有一种排除法,即不包含指定范围的字符。
比如我们要匹配任意字符,但不包括数字,可以用 String reg = "[^1-9]{3}" 来进行匹配:
"ABC"
,因为不包含字符1
~9
;"A00"
,因为不包含字符1
~9
;"A01"
,因为包含字符1
;"A05"
,因为包含字符5
。用|
连接的两个正则表达式是 或 的规则。
比如:String reg = "AB|CD";可以匹配到 AB 、CD。
现在我们想要匹配字符串learn java
、learn php
和learn go
怎么办?一个最简单的规则是learn\sjava|learn\sphp|learn\sgo
,但是这个规则太复杂了,可以把公共部分提出来,然后用(...)
把子规则括起来表示成learn\\s(java|php|go)
。
前面我们讲到 (......) 可以用来把一个子规则括起来,这样写 learn\s(java|php|go) 就可以更方便的匹配长字符串了。
实际上 (......) 还有一个重要作用,就是分组匹配。
比如:我们想要使用正则来匹配一个 区号-电话号,我们可以用 String reg = "\d{3, 4}\-\d{6, 8}";这个正则很简单,但是在实际开发中,往往需要在匹配成功之后,还要进行下一步,也就是对提取的 区号 和 电话号码,进行分别存储。那么问题来了,我们如何提取匹配的子串呢?·
正确的方法是,用(...)
先把要提取的规则分组,把上述正则表达式变为:
(\d{3,4})\-(\d{6,8})
。
那么匹配之后,我们如何按括号分组,来提取子串呢?
我们没办法用String.matches()
这样简单的判断方法了,而是必须引入java.util.regex
包,用Pattern
对象匹配,匹配后获得一个Matcher
对象,如果匹配成功,就可以直接从Matcher.group(index)
返回子串:
public class Main {
public static void main(String[] args) {
Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
Matcher m = p.matcher("010-12345678");
if (m.matches()) {
String g1 = m.group(1);
String g2 = m.group(2);
System.out.println(g1);
System.out.println(g2);
} else {
System.out.println("匹配失败!");
}
}
}
//输出结果
010
12345678
注意!Matcher.group(index)
方法的参数用1表示第一个子串,2表示第二个子串。如果我们传入0则是整个正则匹配到的字符串。
Pattern
我们在前面的代码中用到的正则表达式代码是String.matches()
方法,而我们在分组提取的代码中用的是java.util.regex
包里面的Pattern
类和Matcher
类。实际上这两种代码本质上是一样的,因为String.matches()
方法内部调用的就是Pattern
和Matcher
类的方法。
其实反复使用String.matches()
对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern
对象。而我们完全可以先创建出一个Pattern
对象,然后反复使用,就可以实现编译一次,多次匹配了:
import java.util.regex.*;
public class Main {
public static void main(String[] args) {
Pattern pattern = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
pattern.matcher("010-12345678").matches(); // true
pattern.matcher("021-123456").matches(); // false
pattern.matcher("022#1234567").matches(); // false
// 获得Matcher对象:
Matcher matcher = pattern.matcher("010-12345678");
if (matcher.matches()) {
String whole = matcher.group(0); // "010-12345678", 0表示匹配的整个字符串
String area = matcher.group(1); // "010", 1表示匹配的第1个子串
String tel = matcher.group(2); // "12345678", 2表示匹配的第2个子串
System.out.println(area);
System.out.println(tel);
}
}
}
注意: 使用Matcher
时,必须首先调用matches()
判断是否匹配成功,匹配成功后,才能调用group()
提取子串。
在了解非贪婪匹配前,我们先来一起看一个简单的问题:
给定一个字符串表示的数字,判断该数字末尾
0
的个数。例如:
"123000"
:3个0
"10100"
:2个0
"1001"
:0个0
可以很容易地写出该正则表达式:
(\d+)(0*)
,Java代码如下:import java.util.regex.*; public class Main { public static void main(String[] args) { Pattern pattern = Pattern.compile("(\\d+)(0*)"); Matcher matcher = pattern.matcher("1230000"); if (matcher.matches()) { System.out.println("group1=" + matcher.group(1)); // "1230000" System.out.println("group2=" + matcher.group(2)); // "" } } } //输出结果 打印的第二个子串是个空字符串 “”。
而我们期望的匹配结果是:
但实际的分组匹配结果是这样的:
其实通过观察,这个结果是合理的。因为 \d+ 确实可以匹配后面的任意个0。这是因为正则表达式默认使用的是 贪婪匹配的模式即:任何一个规则,它总是尽可能多地向后匹配,因此,
\d+
总是会把后面的0
包含进来。
而要让 \d+ 尽量的少匹配后面的字符,让 0* 尽量的多匹配,我们就必须让 \d+ 使用贪婪匹配模式。在规则 \d+ 后面加一个 ? ,就可以表示非贪婪匹配了。
5.1 分割字符串
使用正则表达式,分割字符串,可以实现更加灵活的功能。String.split() 方法传入的是正则表达式。我们看下面的代码实现:
"a b c".split("\\s"); // { "a", "b", "c" }
"a b c".split("\\s"); // { "a", "b", "", "c" }
"a, b ;; c".split("[\\,\\;\\s]+"); // { "a", "b", "c" }
如果我们想让用户输入一组标签,然后把标签提取出来,因为用户的输入往往是不规范的,这时,使用合适的正则表达式,就可以消除多个空格、混合,
和;
这些不规范的输入,直接提取出规范的字符串。
5.2 搜索字符串
使用正则表达式,可以搜索字符串,我们看代码实现:
import java.util.regex.*;
public class Main {
public static void main(String[] args) {
String s = "the quick brown fox jumps over the lazy dog.";
Pattern p = Pattern.compile("\\wo\\w");
Matcher m = p.matcher(s);
while (m.find()) {
String sub = s.substring(m.start(), m.end());
System.out.println(sub);
}
}
}
//输出结果
row
fox
dog
我们获取到Matcher
对象后,不需要调用matches()
方法(因为匹配整个串肯定返回false),而是反复调用find()
方法,在整个串中搜索能匹配上\\wo\\w
规则的子串,并打印出来。这种方式比String.indexOf()
要灵活得多,因为我们搜索的规则是3个字符:中间必须是o
,前后两个必须是字符[A-Za-z0-9_]
。
5.3 字符串替换
使用正则表达式替换字符串可以直接调用String.replaceAll()
,它的第一个参数是正则表达式,第二个参数是待替换的字符串。我们看下代码实现:
public class Main {
public static void main(String[] args) {
String s = "The quick\t\t brown fox jumps over the lazy dog.";
String r = s.replaceAll("\\s+", " ");
System.out.println(r); // "The quick brown fox jumps over the lazy dog."
}
}
//输出结果
The quick brown fox jumps over the lazy dog.
5.4 反向引用
如果我们把从字符串中搜索到的指定内容,按一定规则进行替换。
比如: 在匹配到的内容前后加一个xxxx
,这个时候,使用replaceAll()
的时候,我们传入的第二个参数可以使用$1
、$2
来反向引用匹配到的子串:
public class Main {
public static void main(String[] args) {
String s = "the quick brown fox jumps over the lazy dog.";
String r = s.replaceAll("\\s([a-z]{4})\\s", " $1 ");
System.out.println(r);
}
}
实现替换的关键就在于" $1 "
,它用匹配的分组子串([a-z]{4})
替换了$1
。