正则表达式是一种对文本进行搜索和操作的强大工具。
java.util.regex包提供了Java对于正则表达式功能的支持,其功能主要由Pattern和Matcher两个类完成。
Pattern类:
Pattern类用于编译和解析正则表达式
1、获取Pattern对象
由于没有公共的构造函数,只能通过Pattern的compile静态方法获取一个Pattern对象。
public static Pattern compile(String regex) {
return new Pattern(regex, 0);
}
public static Pattern compile(String regex, int flags) {
return new Pattern(regex, flags);
}
上面是两个获取Pattern对象的静态方法,可以看出第一个方法是通过调用第二个方法来实现的。
compile(String regex,int flags)方法需要传入两个参数,第一个参数regex是需要编译和解析的正则表达式字符串,第二个参数flags表示正则表达式匹配过程中的规则标志的和(这些标志是Pattern的常量。例如CASE_INSENSITIVE的值为0x02,表示匹配过程中不区分大小写,COMMENTS的值为0x04,表示正则表达式中包含的空白符不会与测试字符序列的空白符匹配。如果需要设置这两种规则,flags传入他们的和6就行了。),如没有特殊要求,传入0就行。
/**
* 获取Pattern对象
*/
Pattern pattern = Pattern.compile("\\d{3}");
2、Pattern的主要方法(方法名前带“*”的为静态方法)
(1)matcher(CharSequence input):该方法用于获取Matcher对象(Matcher类后面会讲到),需要传入待测试的字符序列(实现了CharSequence接口的类型都可以,例如:StringBuffer、String)。
public static void main(String[] args){
// 获取Pattern对象
Pattern pattern = Pattern.compile("\\d{3}");
//获取Matcher对象
Matcher matcher = pattern.matcher("1234567");
}
(2)*matches(String regex, CharSequence input):该方法用于判断字符序列是否完全匹配正则表达式。第一个参数为表示正式表达式的字符串,第二个参数同matcher(CharSequence input)方法中的参数。
/**
* 可以看到该方法先后创建了Pattern和Matcher对象,并最后调用了Matcher对象的matches()方法
* 这个方法一般用于只需要进行一次判断的需求
* Matcher的matches()方法后面会讲到,现在只需要知道一点:
* 待测试的字符序列必须完全匹配正则表达式,不能多也不能少
* ,
*/
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
现在做一个测试:
public static void main(String[] args){
System.out.println(Pattern.matches("\\d{3}", "123"));
System.out.println(Pattern.matches("\\d{3}", "a123"));
System.out.println(Pattern.matches("\\d{3}", "1233"));
}
打印的结果分别是true、false、false。
(3)public String[] split(CharSequence input, int limit):该方法用于根据正则表达式将传入的字符序列input分割为字符串数组,limit表示字符串数组的最大长度。(String类的split()方法最后也是通过调用该方式来实现分割功能的)现在来做个测试:
/**
* 打印结果:
* array1:[a, 4bc, def, gh]
* array2:[a1234bc567def890gh]
* array3:[a, 4bc, def890gh]
*
*/
public static void main(String[] args) {
// 获取Pattern对象
Pattern pattern = Pattern.compile("\\d{3}");
// 对传入的字符串序列进行分割操作
String[] array1 = pattern.split("a1234bc567def890gh", 0);
String[] array2 = pattern.split("a1234bc567def890gh", 1);
String[] array3 = pattern.split("a1234bc567def890gh", 3);
System.out.println("array1:" + Arrays.toString(array1));
System.out.println("array2:" + Arrays.toString(array2));
System.out.println("array3:" + Arrays.toString(array3));
}
(4)*String quote(String s):将传入的字符串转变为它的字面量,不带有任何特殊含义(比如元字符、转义字符、)。类似于xml中的CDATA,用CDATA定义的文本不会被xml解析器解析,而被当作普通文本。
/**
* 打印结果:
* \Q\d\E
* \Qa*\E
* 可以看到调用quote方法后,原正则表达式被“\Q”和“\E”包裹了起来,而且转义字符\也被去掉了
* 如果我们需要精确匹配的时候,可以用到quote方法
*/
public static void main(String[] args) {
System.out.println(Pattern.quote("\\d"));
System.out.println(Pattern.quote("a*"));
}
2、Matcher类:
Matcher类主要用于完成字符序列的匹配操作。
1、获取Matcher对象
由于Matcher类也没有公共的构造器,只能通过Pattern对象的matcher()方法获取
// 获取Pattern对象
Pattern pattern = Pattern.compile("\\d{3}");
//获取Matcher对象
Matcher matcher = pattern.matcher("1234567");
2、Matcher的主要方法
讲Matcher类的方法之前,先谈谈Matcher两种功能(姑且取名功能吧):
1、正则表达式匹配字符序列:对应方法match(int from, int anchor),匹配返回true,不匹配返回false,其中包括两种模式(anchor):
(1)正则表达式完全匹配字符序列:对应Matcher类中的常量ENDANCHOR ,即字符序列不能有多余的字符,实现的公共方法方法为matches()
(2)正则表达式不必完全匹配字符序列:对应Matcher类中的常量NOANCHOR,即字符序列可以有多余的字符,但是多余的字符必须在匹配字符序列的后面,实现的公共方法为lookingAt()
2、在字符序列中搜索匹配正则表达式的字符序列:对应方法search(int from),搜索到后返回false,没有搜索到返回false,默认使用的NOANCHOR模式。实现的公共方法为find()。
由于match和search方法只有包访问权限,我们可以通过matches()、lookingAt()、find()这三个方法来测试。
先测试matches()、lookingAt()方法:
/**
* 打印结果:
* matcher1 Match(ENDANCHOR): true
* matcher2 Match(ENDANCHOR): false
* matcher3 Match(ENDANCHOR): false
* ---------------------------------------
* matcher1 Match(NOANCHOR): true
* matcher2 Match(NOANCHOR): false
* matcher3 Match(NOANCHOR): true
*
* 分析:
* 1、ENDANCHOR模式下,只有matcher1能匹配成功
* 2、NOANCHOR模式下,匹配的序列前面有其他字符时返回false,而后面有其他字符时返回true
* 3、相同Matcher对象相同模式下匹配多次,返回结果相同
* 结论:
* 1、match()方法每次都是从字符序列的第一个字符开始匹配
* 2、ENDANCHOR模式下,正则字符串和字符序列必须完全匹配,不能有多的字符
* 3、NOANCHOR模式下,正则字符串和字符序列不必须完全匹配,可以有其他的字符,但是其他字符必须位于匹配字符序列的后面
*/
public static void main(String[] args) {
Pattern pattern = Pattern.compile("123");
Matcher matcher1 = pattern.matcher("123");
Matcher matcher2 = pattern.matcher("abc123");
Matcher matcher3 = pattern.matcher("123abc");
System.out.println("matcher1 Match(ENDANCHOR): " + matcher1.matches());
System.out.println("matcher2 Match(ENDANCHOR): " + matcher2.matches());
System.out.println("matcher3 Match(ENDANCHOR): " + matcher3.matches());
System.out.println("---------------------------------------");
System.out.println("matcher1 Match(NOANCHOR): " + matcher1.lookingAt());
System.out.println("matcher2 Match(NOANCHOR): " + matcher2.lookingAt());
System.out.println("matcher3 Match(NOANCHOR): " + matcher3.lookingAt());
}
接下来测试find()方法:
/**
*打印结果:
* 第1次搜索匹配成功!
* 第2次搜索匹配成功!
* 第3次搜索匹配成功!
*
*结果分析:
* 搜索匹配成功三次,测试字符序列也正好有三个“123”
*
*结论:
* 1、find()方法时只会对字符序列进行一次从头到尾的搜索匹配工作
* 2、搜索过程中匹配成功一次后会停止搜索,直到下一次调用find()
* 3、搜索到结尾时停止搜索,如果没有匹配到,返回false
*/
private static void testFind() {
Pattern pattern = Pattern.compile("123");
Matcher matcher = pattern.matcher("abc123def123ghi123jklmn");
int number = 0;
while (matcher.find()) {
number++;
System.out.println("第" + number + "次搜索匹配成功!");
}
}
在工作中,我们需要根据实际的情况来分别使用这几个方法。比如在验证客户登陆输入的验证码时,需要完全匹配才能验证通过,这是可以选择matches()方法(即match方法的ENDANCHOR模式)。在需要知道某段文本中符合正则表达式的序列有多少个,并且需要得到这些序列具体信息时,可以使用find()方法(获取具体信息的方法后面会讲到)。
为了更好的了解match和search方法,介绍下Matcher类中的两个属性first和last
/**
* The range of string that last matched the pattern. If the last
* match failed then first is -1; last initially holds 0 then it
* holds the index of the end of the last match (which is where the
* next search starts).
*/
int first = -1, last = 0;
first:搜索和匹配过程中,存储匹配的序列的第一个字符的索引,初始化和搜索匹配失败时,first=-1
last:搜索和匹配过程中,存储匹配的序列的最后一个字符的索引+1(也是下一次搜索和匹配过程的起点)
下面我们通过反射技术来查看这两个属性变化过程
/**
* 打印first、last属性的值
*
* @param m
* @throws Throwable
*/
private static void printFieldValue(String status, Matcher m) throws Throwable {
System.out.println("\n---" + status + "BEGIN------------------------------------------------------");
for (Field field : m.getClass().getDeclaredFields()) {
if (!field.getName().equals("first") && !field.getName().equals("last")) {
continue;
}
field.setAccessible(true);// 取消访问权限检查,保证能够访问到属性
System.out.println(field.getName() + ": " + field.get(m));
}
System.out.println("---" + status + "END------------------------------------------------------");
}
public static void main(String[] args) throws Throwable {
Matcher matcher = Pattern.compile("123").matcher("123abc123defg123hijk123lmn");
printFieldValue("初始化", matcher);
int number = 0;
while (matcher.find()) {
number++;
printFieldValue("第" + number + "次find()", matcher);
}
printFieldValue("结束", matcher);
}
打印结果如下:
---初始化BEGIN------------------------------------------------------
first: -1
last: 0
---初始化END------------------------------------------------------
---第1次find()BEGIN------------------------------------------------------
first: 0
last: 3
---第1次find()END------------------------------------------------------
---第2次find()BEGIN------------------------------------------------------
first: 6
last: 9
---第2次find()END------------------------------------------------------
---第3次find()BEGIN------------------------------------------------------
first: 13
last: 16
---第3次find()END------------------------------------------------------
---第4次find()BEGIN------------------------------------------------------
first: 20
last: 23
---第4次find()END------------------------------------------------------
---结束BEGIN------------------------------------------------------
first: -1
last: 23
---结束END------------------------------------------------------
从结果结合find()原码可以看出find()的过程:
/*
* 方法的第一行就将last值赋值给了nextSearchIndex,nextSearchIndex即下一次搜索的起点
*/
public boolean find() {
int nextSearchIndex = last;
if (nextSearchIndex == first)
nextSearchIndex++;
// If next search starts before region, start it at region
if (nextSearchIndex < from)
nextSearchIndex = from;
// If next search starts beyond region then it fails
if (nextSearchIndex > to) {
for (int i = 0; i < groups.length; i++)
groups[i] = -1;
return false;
}
return search(nextSearchIndex);
}
find()过程:
以last(即上一次搜索结束时的最后一个字符的索引+1)的索引位置作为起点开始搜索,当匹配到一个"123"时,会停止搜索,并更新first为“1”的索引,last为“3”的索引+1
其他的方法这里就不测试了,有兴趣的可以自己试试。
到这里,大家对Matcher类有了一定的了解了,下面开始其他的方法:
先看下面的一段代码
public static void main(String[] args) {
String regex = "(?<group1>t)o(?<group2>o?)\\b";
String str = "Nice to meet you, my name is HanMeimei.My name is LiLei. Nice to meet you, too. Welcome!";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
System.out.println("groupCount:" + matcher.groupCount());
StringBuffer sb = new StringBuffer();
int number = 0;
while (matcher.find()) {
number++;
System.out.println("\n--第" + number + "次BEGIN:");
System.out.println("group():" + matcher.group() + " group(1):" + matcher.group(1)
+ " group(\"group1\"):" + matcher.group("group1"));
System.out.println("start():" + matcher.start() + " start(1):" + matcher.start(1)
+ " start(\"group1\"):" + matcher.start("group1"));
System.out.println("end():" + matcher.end() + " end(1):" + matcher.end(1) + " end(\"group1\"):"
+ matcher.end("group1"));
matcher.appendReplacement(sb, "2222");
System.out.println("appendReplacement:" + sb.toString() + "\n--第" + number
+ "次END--------------------------------------------------------------------\n");
}
matcher.appendTail(sb);
System.out.println("appendTail: " + sb);
System.out.println("matchCount:" + number);
}
打印结果:
groupCount:2
--第1次BEGIN:
group():to group(1):t group("group1"):t
start():5 start(1):5 start("group1"):5
end():7 end(1):6 end("group1"):6
appendReplacement:Nice 2222
--第1次END--------------------------------------------------------------------
--第2次BEGIN:
group():to group(1):t group("group1"):t
start():62 start(1):62 start("group1"):62
end():64 end(1):63 end("group1"):63
appendReplacement:Nice 2222 meet you, my name is HanMeimei.My name is LiLei. Nice 2222
--第2次END--------------------------------------------------------------------
--第3次BEGIN:
group():too group(1):t group("group1"):t
start():75 start(1):75 start("group1"):75
end():78 end(1):76 end("group1"):76
appendReplacement:Nice 2222 meet you, my name is HanMeimei.My name is LiLei. Nice 2222 meet you, 2222
--第3次END--------------------------------------------------------------------
appendTail: Nice 2222 meet you, my name is HanMeimei.My name is LiLei. Nice 2222 meet you, 2222. Welcome!
matchCount:3
先解释下这个正则表达式
\b(?<group1>t)o(?<group2>o?)\b:
1、\b:单词或者行的边界
2、(?<group1>t):捕获组,"t"是捕获组的正则表达式,“group1”是这个捕获组的名称,这个是1.8新增的功能,其用法为(?<groupName>)
3、该正则表达式匹配单词to或者too
1、groupCount():返回正则表达式中捕获组的数量
\b(?<group1>t)o(?<group2>o?)\b 有两个捕获组(“group1”和“group2”),所以返回2
2、group():返回上一次匹配的字符串
第一次匹配的是第一个“to”,第三次捕获的是“too”
3、group(int group):返回上一次匹配中,第“group”个捕获组捕获的字符串
第一次匹配的是“to”,其中第一个捕获组捕获了“t”,返回“t”
4、group(String name):返回上一次匹配中,名称为“name”的捕获组捕获的字符串
第一次匹配的是“to”,其中名称为“group1”的捕获组捕获了“t”,返回“t”
5、int start():返回上一次匹配中,匹配的字符串的第一个字符的索引(即first的值)
第一次匹配的是“to”,第一个字符“t”的索引是5,返回5
6、start(int group):返回上一次匹配中,第“group”个捕获组捕获的字符串的第一个字符的索引
第一次匹配的是“to”,其中第一个捕获组捕获了“t”,第一个字符“t”的索引是5,返回5
7、start(String name):返回上一次匹配中,名称为“name”的捕获组捕获的字符串的第一个字符的索引
第一次匹配的是“to”,其中名称为“group1”的捕获组捕获了“t”,第一个字符“t”的索引是5,返回5
8、int end():返回上一次匹配中,匹配的字符串的最后一个字符的索引+1(即last的值)
第三次匹配的是“too”,最后一个字符“o”的索引是77,返回78
9、end(int group):返回上一次匹配中,第“group”个捕获组捕获的字符串的最后一个字符的索引+1
第三次匹配的是“too”,其中第一个捕获组捕获了“t”,最后一个字符“t”的索引是75,返回76
10、end(String name):返回上一次匹配中,名称为“name”的捕获组捕获的字符串
第三次匹配的是“too”,其中名称为“group1”的捕获组捕获了“t”,最后一个字符“t”的索引是75,返回76
11、appendReplacement(StringBuffer sb, String replacement):将上一次匹配过程中,搜索的字符串(即起点和结束位置之间的字符串)拼接到“sb”中,并用“replacement”替换匹配到的字符串
12、appendTail(StringBuffer sb):将剩下的未搜索的字符串拼接到“sb”中
这里介绍了一部分Matcher类的方法。
上面只提到了first和last两个属性,还有其他的一些属性,比如oldLast、lastAppendPosition等等,如果有兴趣可以使用反射技术查看这些属性在match()和search()两个方法调用过程中的变化,这些属性值的变化对于理解Matcher类的方法很重要。