正则表达式 基础篇

简介

一个正则表达式就是一个描述规则的字符串,所以,只需要编写正确的规则,我们就可以让正则表达式引擎去判断目标字符串是否符合规则。

正则表达式是一套标准,它可以用于任何语言。Java标准库的java.util.regex包内置了正则表达式引擎,在Java程序中使用正则表达式非常简单。

举个例子:要判断用户输入的年份是否是20##年,我们先写出规则如下:

一共有4个字符,分别是:200~9任意数字0~9任意数字

对应的正则表达式就是:20\d\d,其中\d表示任意一个数字。

在java中就是

1
20\\d\\d

注意Java字符串用\\表示\

匹配规则

正则表达式的匹配规则是从左到右按规则匹配

&

正则表达式

1
a\&c

它能精确匹配字符串"a&c"

但不能匹配"ac""a-c""a&&c"

如果想匹配非ASCII字符,例如中文,那就用\u####的十六进制表示,例如:a\u548cc匹配字符串"a和c",中文字符的Unicode编码是548c

这上面是精确匹配,但是用处不大。

因为我们可以直接String.equals()即可

.

可以用.匹配一个任意字符。

例如,正则表达式a.c中间的.可以匹配一个任意字符,例如,下面的字符串都可以被匹配:

  • "abc",因为.可以匹配字符b
  • "a&c",因为.可以匹配字符&
  • "acc",因为.可以匹配字符c

但它不能匹配"ac""a&&c",因为.匹配一个字符且仅限一个字符。

\d

如果我们只想匹配0~9这样的数字,可以用\d匹配。例如,正则表达式00\d可以匹配:

  • "007",因为\d可以匹配字符7
  • "008",因为\d可以匹配字符8

它不能匹配"00A""0077",因为\d仅限单个数字字符。

\w

\w可以匹配一个字母、数字或下划线,w的意思是word。例如,java\w可以匹配:

  • "javac",因为\w可以匹配英文字符c
  • "java9",因为\w可以匹配数字字符9;。
  • "java_",因为\w可以匹配下划线_

它不能匹配"java#""java ",因为\w不能匹配#、空格等字符。

\s

\s可以匹配一个空格字符,注意空格字符不但包括空格 ,还包括tab字符(在Java中用\t表示)。例如,a\sc可以匹配:

  • "a c",因为\s可以匹配空格字符
  • "a c",因为\s可以匹配tab字符\t

它不能匹配"ac""abc"等。

\D

\d可以匹配一个数字,而\D则匹配一个非数字。例如,00\D可以匹配:

  • "00A",因为\D可以匹配非数字字符A
  • "00#",因为\D可以匹配非数字字符#

00\d可以匹配的字符串"007""008"等,00\D是不能匹配的。

类似的,\W可以匹配\w不能匹配的字符,\S可以匹配\s不能匹配的字符,这几个正好是反着来的。

*

修饰符*可以匹配任意个字符,包括0个字符。我们用A\d*可以匹配:

  • A:因为\d*可以匹配0个数字;
  • A0:因为\d*可以匹配1个数字0
  • A380:因为\d*可以匹配多个数字380

+

修饰符+可以匹配至少一个字符。我们用A\d+可以匹配:

  • A0:因为\d+可以匹配1个数字0
  • A380:因为\d+可以匹配多个数字380

但它无法匹配"A",因为修饰符+要求至少一个字符。

修饰符?可以匹配0个或一个字符。我们用A\d?可以匹配:

  • A:因为\d?可以匹配0个数字;
  • A0:因为\d?可以匹配1个数字0

{}

如果我们想精确指定n个字符怎么办?用修饰符{n}就可以。A\d{3}可以精确匹配:

  • A380:因为\d{3}可以匹配3个数字380

如果我们想指定匹配n~m个字符怎么办?用修饰符{n,m}就可以。A\d{3,5}可以精确匹配:

  • A380:因为\d{3,5}可以匹配3个数字380
  • A3800:因为\d{3,5}可以匹配4个数字3800
  • A38000:因为\d{3,5}可以匹配5个数字38000

如果没有上限,那么修饰符{n,}就可以匹配至少n个字符。

练习

请编写一个正则表达式匹配国内的电话号码规则:3~4位区号加7~8位电话,中间用-连接,例如:010-12345678。

1
^\d{3,4}-\d{7,8}$
  • ^:表示字符串的开始。
  • \d{3,4}:匹配 3 到 4 位数字(区号)。
  • -:匹配一个连接符 -
  • \d{7,8}:匹配 7 到 8 位数字(电话号码)。
  • $:表示字符串的结束。

复杂匹配

用正则表达式进行多行匹配时,我们用^表示开头,$表示结尾。例如,^A\d{3}$,可以匹配"A001""A380"

[…]

如果我们规定一个7~8位数字的电话号码不能以0开头,应该怎么写匹配规则呢?\d{7,8}是不行的,因为第一个\d可以匹配到0

使用[...]可以匹配范围内的字符,例如,[123456789]可以匹配1~9,这样就可以写出上述电话号码的规则:[123456789]\d{6,7}

把所有字符全列出来太麻烦,[...]还有一种写法,直接写[1-9]就可以。

所以就是

1
[1-9]\d{6,7}

要匹配大小写不限的十六进制数,比如1A2b3c,我们可以这样写:[0-9a-fA-F],它表示一共可以匹配以下任意范围的字符:

  • 0-9:字符0~9
  • a-f:字符a~f
  • A-F:字符A~F

如果要匹配6位十六进制数,前面讲过的{n}仍然可以继续配合使用:[0-9a-fA-F]{6}

[...]还有一种排除法,即不包含指定范围的字符。假设我们要匹配任意字符,但不包括数字,可以写[^1-9]{3}

  • 可以匹配"ABC",因为不包含字符1~9
  • 可以匹配"A00",因为不包含字符1~9
  • 不能匹配"A01",因为包含字符1
  • 不能匹配"A05",因为包含字符5

或匹配

|连接的两个正则规则是规则,例如,AB|CD表示可以匹配ABCD

现在我们想要匹配字符串learn javalearn phplearn go怎么办?一个最简单的规则是learn\sjava|learn\sphp|learn\sgo,但是这个规则太复杂了,可以把公共部分提出来,然后用(...)把子规则括起来表示成learn\s(java|php|go)

上面的规则仍然不能匹配learn Javalearn Go这样的字符串。试修改正则,使之能匹配大写字母开头的learn Javalearn Phplearn Go。这个要自己去进行修改

分组匹配

实际上(...)还有一个重要作用,就是分组匹配。

正确的方法是用(...)先把要提取的规则分组,把上述正则表达式变为(\d{3,4})\-(\d{6,8})。就是先进行这个

现在我们没办法用String.matches()这样简单的判断方法了,必须引入java.util.regex包,用Pattern对象匹配,匹配后获得一个Matcher对象,如果匹配成功,就可以直接从Matcher.group(index)返回子串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class reg_exp {
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(g2);
System.out.println(g1);
}else {
System.out.println("error");
}
}

}

运行上述代码,会得到两个匹配上的子串01012345678

1
2
12345678
010

要特别注意,Matcher.group(index)方法的参数用1表示第一个子串,2表示第二个子串。如果我们传入0会得到什么呢?答案是010-12345678,即整个正则匹配到的字符串。

这里面的括号就是用来进行分组的

但是反复使用String.matches()对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern对象。完全可以先创建出一个Pattern对象,然后反复使用,就可以实现编译一次,多次匹配:

实现:

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
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class reg_exp {
public static void main(String[] args) {
// 创建正则表达式对象,匹配形如 "1234-567890" 的电话号码
Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");

// 定义要测试的字符串,可以包括多个电话号码
String[] phoneNumbers = {"010-1245678", "210-5555555", "7895-554446", "040-12345678"};

// 遍历所有电话号码进行匹配
for (String phoneNumber : phoneNumbers) {
Matcher matcher = p.matcher(phoneNumber);

if (matcher.matches()) {
// 获取匹配的整个字符串以及子组信息
String area = matcher.group(1); // 区号
String tel = matcher.group(2); // 电话号码
System.out.println("Area Code: " + area + ", Tel: " + tel);
} else {
System.out.println("No match for: " + phoneNumber);
}
}
}
}

主要是建了一个数组,然后增强for然后匹配

使用Matcher时,必须首先调用matches()判断是否匹配成功,匹配成功后,才能调用group()提取子串。