正则表达式简介
- 正则表达式可以用字符串来描述规则,并用来匹配字符串。
在其他语言中,\ 表示:我想要在正则表达式中插入一个普通的(字面上的)反斜杠,请不要给它任何特殊的意义。在 Java 中,\ 表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。所以,在其他的语言中(如Perl),一个反斜杠 \ 就足以具有转义的作用,而在 Java 中正则表达式中则需要有两个反斜杠才能被解析为其他语言中的转义作用。也可以简单的理解在 Java 的正则表达式中,两个 \ 代表其他语言中的一个 \,这也就是为什么表示一位数字的正则表达式是 \d,而表示一个普通的反斜杠是 \\。
public class RegexDemo {
public static void main(String[] args) {
// \d表示任意一个数字
String regex = "20\\d\\d";
System.out.println("2019".matches(regex)); // true
System.out.println("2100".matches(regex)); //false
}
//判断手机号是否为11位
public static boolean isValidMobileNumber(String s) {
return s.matches("\\d{11}");
}
}
匹配规则
精准匹配
对于正则表达式abc
来说,它只能精确地匹配字符串"abc"
,不能匹配"ab"
,"Abc"
,"abcd"
等其他任何字符串。
- 匹配任意字符
精确匹配实际上用处不大,因为我们直接用String.equals()
就可以做到。大多数情况下,我们想要的匹配规则更多的是模糊匹配。我们可以用.
匹配一个任意字符。
- 匹配数字
如果我们只想匹配0
~9
这样的数字,可以用\d
匹配,仅限单个数字字符。例如,正则表达式00\d
可以匹配:
- 匹配常用字符
用\w
可以匹配一个字母、数字或下划线,w的意思是word。
- 匹配空格字符
用\s
可以匹配一个空格字符,注意空格字符不但包括空格,还包括tab字符(在Java中用\t
表示)。
- 匹配非数字
用\d
可以匹配一个数字,而\D
则匹配一个非数字。
类似的,\W
可以匹配\w
不能匹配的字符,\S
可以匹配\s
不能匹配的字符,这几个正好是反着来的。
- 重复匹配
- 修饰符
*
可以匹配任意个字符,包括0个字符。 - 修饰符
+
可以匹配至少一个字符。 - 修饰符
?
可以匹配0个或一个字符。 - 修饰符
{n}
可以精确指定n个字符 - 修饰符
{n,m}
可以指定匹配n~m个字符 修饰符
{n,}
就可以匹配至少n个字符public static void main(String[] args) {
//精准匹配
System.out.println("精准匹配:");
String re1 = "abc";
System.out.println("abc".matches(re1));
System.out.println("Abc".matches(re1));
System.out.println("abcd".matches(re1));
String re2 = "a\\&c"; // 对应的正则是a\&c
System.out.println("a&c".matches(re2)); //true
System.out.println("a-c".matches(re2));
System.out.println("a&&c".matches(re2));
//匹配任意字符
System.out.println("匹配任意字符:");
String re3 = "a.c";
System.out.println("abc".matches(re3)); //true
System.out.println("a&c".matches(re3)); //true
System.out.println("ac".matches(re3)); //false
System.out.println("abcc".matches(re3)); //false
//匹配数字
System.out.println("匹配数字:");
String re4 = "00\\d";
System.out.println("005".matches(re4));//true
System.out.println("00A".matches(re4));//false
System.out.println("0088".matches(re4));//false
//匹配常用字符
//用\w可以匹配一个字母、数字或下划线,w的意思是word。
System.out.println("匹配常用字符:");
String re5 = "java\\w";
System.out.println("javac".matches(re5));//true
System.out.println("java9".matches(re5));//true
System.out.println("java_".matches(re5));//true
System.out.println("java#".matches(re5));//false
System.out.println("java ".matches(re5));//false
//匹配空格字符
System.out.println("匹配空格字符");
String re6 = "a\\sc";
System.out.println("a c".matches(re6)); //true
System.out.println("a\tc".matches(re6)); //true
System.out.println("a c".matches(re6)); //false,两个空格
System.out.println("abc".matches(re6)); //false
//匹配非数字
// \D匹配一个非数字
System.out.println("匹配非数字");
String re7 = "00\\D";
System.out.println("00A".matches(re7));//true
System.out.println("00&".matches(re7));//true
//重复匹配
System.out.println("重复匹配");
//修饰符*可以匹配任意字符,包括0个字符
String re8 = "A\\d*";
System.out.println("重复匹配:*");
System.out.println("A0".matches(re8)); //true
System.out.println("A01".matches(re8)); //true
System.out.println("A001".matches(re8)); //true
//修饰符+可以匹配至少一个字符
String re9 = "A\\d+";
System.out.println("重复匹配:+");
System.out.println("A".matches(re9)); //false
System.out.println("A0".matches(re9)); //true
System.out.println("A01".matches(re9)); //true
//修饰符?可以匹配0或一个字符
String re10 = "A\\d?";
System.out.println("重复匹配:?");
System.out.println("A".matches(re10)); //true
System.out.println("A0".matches(re10)); //true
System.out.println("A01".matches(re10)); //false
//修饰符{n}可以精确指定n个字符
String re11 = "A\\d{3}";
System.out.println("重复匹配:{n}");
System.out.println("A".matches(re11)); //false
System.out.println("A123".matches(re11)); //true
//修饰符{n,m}可以指定匹配n~m个字符
String re12 = "A\\d{3,5}";
System.out.println("重复匹配:{n,m}");
System.out.println("A123".matches(re12)); //true
System.out.println("A1235".matches(re12)); //true
System.out.println("A12345".matches(re12)); //true
//修饰符{n,}就可以匹配至少n个字符
String re13="A\\d{1,}";
System.out.println("重复匹配:{n,}");
System.out.println("A".matches(re13)); //false
System.out.println("A1".matches(re13)); //true
}
复杂匹配规则
- 匹配开头和结尾
用正则表达式进行多行匹配时,我们用^
表示开头,$
表示结尾。例如,^A\d{3}$
,可以匹配"A001"
、"A380"
。
- 匹配指定范围
使用[...]
可以匹配范围内的字符。
- 或规则匹配
用|连接的两个正则规则是或规则。
- 使用括号把公共部分提出来
现在我们想要匹配字符串learn java
、learn php
和learn go
,怎么办?一个最简单的规则是learn\sjava|learn\sphp|learn\sgo
,但是这个规则太复杂了,可以把公共部分提出来,然后用(...)
把子规则括起来表示成learn\\s(java|php|go)
。
public class RegexComplex {
public static void main(String[] args) {
//匹配开头和结尾
//用^表示开头,$表示结尾
String re = "^A\\d{3}$";
System.out.println("匹配开头和结尾");
System.out.println("A001".matches(re));//true
System.out.println("A01".matches(re)); //false
System.out.println("AB001".matches(re)); //false
//匹配指定范围
//使用[...]可以匹配范围内的字符
System.out.println("匹配指定范围");
String re1 = "[0-9a-fA-F]{6}";//匹配6位大小写不限的十六进制数
String re2 = "[^1-9]{3}";//不包含指定范围的字符
System.out.println("ABC".matches(re2)); //true
System.out.println("AB1".matches(re2)); //false
//或规则匹配
//用|连接的两个正则规则是或规则
System.out.println("或规则匹配");
String re3 = "java|php";
System.out.println("java".matches(re3));
System.out.println("php".matches(re3));
System.out.println("go".matches(re3)); //false
//使用括号把公共部分提出来
System.out.println("使用括号");
String re4 = "learn\\s(java|php|go)";
System.out.println("learn java".matches(re4));
System.out.println("learn Java".matches(re4));
System.out.println("learn php".matches(re4));
System.out.println("learn Go".matches(re4));
}
}
分组匹配
- 实际上
(...)
还有一个重要作用,就是分组匹配。 - 现在没办法用
String.matches()
这样简单的判断方法了,必须引入java.util.regex
包,用Pattern
对象匹配,匹配后获得一个Matcher
对象,如果匹配成功,就可以直接从Matcher.group(index)
返回子串。 - 我们在前面的代码中用到的正则表达式代码是
String.matches()
方法,而我们在分组提取的代码中用的是java.util.regex
包里面的Pattern
类和Matcher
类。实际上这两种代码本质上是一样的,因为String.matches()
方法内部调用的就是Pattern
和Matcher
类的方法。
但是反复使用String.matches()
对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern
对象。完全可以先创建出一个Pattern
对象,然后反复使用,就可以实现编译一次,多次匹配。
public class GroupMatch {
public static void main(String[] args) {
Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
Matcher m = p.matcher("010-12345678");
//匹配成功才可以提取group子串
if (m.matches()) {
String g = m.group(0); //提取整个字符串
String g1 = m.group(1); //提取第一个子串
String g2 = m.group(2); //提取第二个子串
System.out.println(g);
System.out.println(g1);
System.out.println(g2);
} else {
System.out.println("匹配失败!");
}
}
}
非贪婪匹配
1.正则表达式匹配默认使用贪婪匹配(任何一个规则,它总是尽可能多地向后匹配),可以使用?
表示对某一规则进行非贪婪匹配。
public class NonGreedyMatch {
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)); // ""
}
//非贪婪匹配
Pattern pattern1 = Pattern.compile("(\\d+?)(0*)");
Matcher matcher1 = pattern1.matcher("1230000");
if (matcher1.matches()) {
System.out.println("group1=" + matcher1.group(1)); // "123"
System.out.println("group2=" + matcher1.group(2)); // "0000"
}
Pattern pattern2 = Pattern.compile("(\\d??)(9*)");
Matcher matcher2 = pattern2.matcher("19999");
if (matcher2.matches()) {
System.out.println("group1=" + matcher2.group(1)); // "1"
System.out.println("group2=" + matcher2.group(2)); // "9999"
}
}
}
搜索和替换
- 分割字符串
String.split()
方法传入的正是正则表达式。如果我们想让用户输入一组标签,然后把标签提取出来,因为用户的输入往往是不规范的,这时,使用合适的正则表达式,就可以消除多个空格、混合,
和;
这些不规范的输入,直接提取出规范的字符串。
- 搜索字符串
我们获取到Matcher
对象后,不需要调用matches()
方法(因为匹配整个串肯定返回false),而是反复调用find()
方法,在整个串中搜索能匹配上\\wo\\w
规则的子串,并打印出来。这种方式比String.indexOf()
要灵活得多,因为我们搜索的规则是3个字符:中间必须是o
,前后两个必须是字符[A-Za-z0-9_]
。
- 替换字符串
使用正则表达式替换字符串可以直接调用String.replaceAll()
,它的第一个参数是正则表达式,第二个参数是待替换的字符串。
- 反向引用
如果我们要把搜索到的指定字符串按规则替换,比如前后各加一个<b>xxxx</b>
,这个时候,使用replaceAll()
的时候,我们传入的第二个参数可以使用$1
、$2
来反向引用匹配到的子串。
它实际上把任何4字符单词的前后用<b>xxxx</b>
括起来。实现替换的关键就在于" <b>$1</b> "
,它用匹配的分组子串([a-z]{4})
替换了$1
。
public class RegApplicationDemo {
public static void main(String[] args) {
//分割字符串
System.out.println("分割字符串");
String[] a = "a b c".split("\\s"); // { "a", "b", "c" }
System.out.println(Arrays.toString(a));
String[] b = "a b c".split("\\s");// { "a", "b", "", "c" }
System.out.println(Arrays.toString(b));
String[] c = "a, b ;; c".split("[\\,\\;\\s]+"); // { "a", "b", "c" }
System.out.println(Arrays.toString(c));
//搜索字符串
System.out.println("搜索字符串");
String s = "the quick brown fox jumps over the lazy dog.";
//中间必须是o,前后两个必须是字符[A-Za-z0-9_]。
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);
}
//替换字符串
System.out.println("替换字符串");
String s1 = "The quick\t\t brown fox jumps over the lazy dog.";
System.out.println(s1);
String r = s1.replaceAll("\\s+", " ");
System.out.println(r); // "The quick brown fox jumps over the lazy dog."
//反向引用
System.out.println("反向引用");
String s2 = "the quick brown fox jumps over the lazy dog.";
String r2 = s2.replaceAll("\\s([a-z]{4})\\s", " <b>$1</b> ");
System.out.println(r2); //the quick brown fox jumps <b>over</b> the <b>lazy</b> dog.
}
}