做一个需求在解析 token 时用到 Java 里正则匹配,所以决定把 Java 里的常用正则的工具类学习一下。
在解析 token 的时候还碰到了之前学习过的贪婪模式与非贪婪模式的区别。
Matcher m = Pattern.compile("&data=(.*)").matcher(query); Matcher m = Pattern.compile("token=(.*?)&data").matcher(query);
2 个问题:(1)获取 data 数据为什么不加 ? ;(2)获取 token 数据为什么需要加 ?。
以前有写一篇博客,具体不多说了,看下图区别:
非贪婪模式:在修饰匹配次数的特殊符号后再加上一个 "?" 号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的 "不匹配"。 —— 所以下面加上 ? 号就匹配不上。
java.util.regex 是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包。它包括两个类:Pattern 和 Matcher。
一个 Pattern 就是一个正则表达式经编译后的表现模式。一个 Matcher 对象就是一个状态机器,它依据Pattern对象做为匹配模式对字符串展开匹配检查。 首先一个Pattern实例订制了一个所用语法与PERL的类似的正则表达式经编译后的模式,然后一个Matcher实例在这个给定的Pattern实例的模式控制下进行字符串的匹配工作。
一、捕获组的概念
捕获组可以通过从左到右计算其开括号来编号,编号是从1 开始的。例如,在表达式 ((A)(B(C)))中,存在四个这样的组:
((A)(B(C)))
(A)
(B(C))
(C)
捕获组分类:(1)普通捕获组(Expression);(2)命名捕获组(?<name>Expression)
1、普通捕获组 —— 从正则表达式左侧开始,每出现一个左括号“(”记做一个分组,分组编号从1开始。0代表整个表达式。
对于时间字符串:(\d{4})-((\d{2})-(\d{2})),
有4个左括号,所以有4个分组
编号 | 捕获组 | 匹配 |
---|---|---|
0 | (d{4})-((d{2})-(d{2})) | 2017-04-25 |
1 | (d{4}) | 2017 |
2 | ((d{2})-(d{2})) | 04-25 |
3 | (d{2}) | 04 |
4 | (d{2}) | 25 |
2、命名捕获组 —— 每个以左括号开始的捕获组,都紧跟着“?”,而后才是正则表达式。
如:(?<year>\d{4})-(?<md>(?<month>\d{2})-(?<date>\d{2}))
编号 | 名称 | 捕获组 | 匹配 |
---|---|---|---|
0 | 0 | (?d{4})-(?(?d{2})-(?d{2})) | 2017-04-25 |
1 | year | (?d{4})- | 2017 |
2 | md | (?(?d{2})-(?d{2})) | 04-25 |
3 | month | (?d{2}) | 04 |
4 | date | (?d{2}) | 25 |
命名的捕获组同样也可以使用编号获取相应值。
// 使用名称获取
System.out.printf("
matcher.group('year') value:%s", matcher.group("year"));
// 使用编号获取
System.out.printf("
matcher.group(1) value:%s", matcher.group(1));
3、非捕获组 —— 在左括号后紧跟 ?:,而后再加上正则表达式,构成非捕获组 (?:Expression)。
如:(?:\d{4})-((\d{2})-(\d{2})),这个正则表达式虽然有四个左括号,理论上有 4 个捕获组。但是第一组 (?:d{4}),其实是被忽略的。当使用 matcher.group(4) 时,系统会报错。
编号 | 捕获组 | 匹配 |
---|---|---|
0 | (d{4})-((d{2})-(d{2})) | 2017-04-25 |
1 | ((d{2})-(d{2})) | 04-25 |
2 | (d{2}) | 04 |
3 | (d{2}) | 25 |
4、总结 —— 普通捕获组使用方便;命名捕获组使用清晰;非捕获组目前在项目中还没有用武之地。
二、Pattern类
Pattern类用于创建一个正则表达式,也可以说创建一个匹配模式,它的构造方法是私有的,不可以直接创建,但可以通过 Pattern.complie(String regex) 简单工厂方法创建一个正则表达式
Pattern p = Pattern.compile("\w+");
p.pattern(); //返回 w+
pattern() 返回正则表达式的字符串形式,其实就是返回Pattern.complile(String regex)的regex参数。
1、Pattern.split(CharSequence input)
Pattern有一个 split(CharSequence input) 方法用于分隔字符串,并返回一个String[]。我猜 String.split(String regex) 就是通过Pattern.split(CharSequence input)来实现的
Pattern p=Pattern.compile("\d+");
String[] str=p.split("我的QQ是:456456我的电话是:0532214我的邮箱是:aaa@aaa.com");
// 结果:str[0]="我的QQ是:" str[1]="我的电话是:" str[2]="我的邮箱是:aaa@aaa.com"
2、Pattern.matcher(String regex,CharSequence input)是一个静态方法,用于快速匹配字符串,该方法适合用于只匹配一次,且匹配全部字符串
Pattern.matches("\d+","2223");//返回true
Pattern.matches("\d+","2223aa");//返回false,需要匹配到所有字符串才能返回true,这里aa不能匹配到
3、Pattern.matcher(CharSequence input)
Pattern.matcher(CharSequence input)返回一个Matcher对象。
Matcher类的构造方法也是私有的,不能随意创建,只能通过 Pattern.matcher(CharSequence input) 方法得到该类的实例。
Pattern类只能做一些简单的匹配操作,要想得到更强更便捷的正则匹配操作,那就需要将 Pattern与Matcher 一起合作。Matcher类提供了对正则表达式的分组支持,以及对正则表达式的多次匹配支持。
Pattern p=Pattern.compile("\d+");
Matcher m=p.matcher("22bb23");
m.pattern();//返回p 也就是返回该Matcher对象是由哪个Pattern对象的创建的
三、Matcher类
1、Matcher.matches()、 Matcher.lookingAt()、Matcher.find()
Matcher类提供三个匹配操作方法,三个方法均返回boolean类型,当匹配到时返回true,没匹配到则返回false 。
(1)matches()对整个字符串进行匹配,只有整个字符串都匹配了才返回true。
Pattern p=Pattern.compile("\d+");
Matcher m=p.matcher("22bb23");
m.matches();//返回false,因为bb不能被d+匹配,导致整个字符串匹配未成功.
Matcher m2=p.matcher("2223");
m2.matches();//返回true,因为d+匹配到了整个字符串
我们现在回头看一下Pattern.matcher(String regex,CharSequence input),它与下面这段代码等价 Pattern.compile(regex).matcher(input).matches()
(2)lookingAt() 对前面的字符串进行匹配,只有匹配到的字符串在最前面才返回true
Pattern p=Pattern.compile("\d+");
Matcher m=p.matcher("22bb23");
m.lookingAt();//返回true,因为d+匹配到了前面的22
Matcher m2=p.matcher("aa2223");
m2.lookingAt();//返回false,因为d+不能匹配前面的aa
(3)find()对字符串进行匹配,匹配到的字符串可以在任何位置
Pattern p=Pattern.compile("\d+");
Matcher m=p.matcher("22bb23");
m.find();//返回true
Matcher m2=p.matcher("aa2223");
m2.find();//返回true
Matcher m3=p.matcher("aa2223bb");
m3.find();//返回true
Matcher m4=p.matcher("aabb");
m4.find();//返回false
2、Mathcer.start()、 Matcher.end()、 Matcher.group()
当使用matches()、lookingAt()、find()执行匹配操作后,就可以利用以上三个方法得到更详细的信息。
(1)start()返回匹配到的子字符串在字符串中的索引位置
(2)end()返回匹配到的子字符串的最后一个字符在字符串中的索引位置
(3)group()返回匹配到的子字符串
Pattern p=Pattern.compile("\d+");
Matcher m=p.matcher("aaa2223bb");
m.find();//匹配2223
m.start();//返回3
m.end();//返回7,返回的是2223后的索引号
m.group();//返回2223
Mathcer m2=m.matcher("2223bb");
m.lookingAt(); //匹配2223
m.start(); //返回0,由于lookingAt()只能匹配前面的字符串,所以当使用lookingAt()匹配时,start()方法总是返回0
m.end(); //返回4
m.group(); //返回2223
Matcher m3=m.matcher("2223bb");
m.matches(); //匹配整个字符串
m.start(); //返回0,原因相信大家也清楚了
m.end(); //返回6,原因相信大家也清楚了,因为matches()需要匹配所有字符串
m.group(); //返回2223bb
四、分组在Java中如何使用
相信大家都明白了以上几个方法的使用,现在我们该说说正则表达式的分组在java中是怎么使用的。
start()、end()、group()均有一个重载方法,它们是start(int i)、end(int i)、group(int i)专用于分组操作,Mathcer类还有一个groupCount()用于返回有多少组。
Pattern p=Pattern.compile("([a-z]+)(\d+)");
Matcher m=p.matcher("aaa2223bb");
m.find(); //匹配aaa2223
m.groupCount(); //返回2,因为有2组
m.start(1); //返回0 返回第一组匹配到的子字符串在字符串中的索引号
m.start(2); //返回3
m.end(1); //返回3 返回第一组匹配到的子字符串的最后一个字符在字符串中的索引位置.
m.end(2); //返回7
m.group(1); //返回aaa,返回第一组匹配到的子字符串
m.group(2); //返回2223,返回第二组匹配到的子字符串
现在我们使用一下稍微高级点的正则匹配操作,例如有一段文本里面有很多数字,而且这些数字是分开的,我们现在要将文本中所有数字都取出来,利用java的正则操作是那么的简单。
Pattern p=Pattern.compile("\d+");
Matcher m=p.matcher("我的QQ是:456456 我的电话是:0532214 我的邮箱是:aaa123@aaa.com");
while(m.find()) {
System.out.println(m.group());
}
// 输出:
// 456456
// 0532214
// 123
// 如将以上while()循环替换成
while(m.find()) {
System.out.println(m.group());
System.out.print("start:"+m.start());
System.out.println(" end:"+m.end());
}
// 则输出:
456456
start:6 end:12
0532214
start:19 end:26
123
start:36 end:39
现在大家应该知道:每次执行匹配操作后start()、end()、group()三个方法的值都会改变成匹配到的子字符串的信息,以及它们的重载方法也会改变成相应的信息。
注意:只有当匹配操作成功才可以使用start()、end()、group()三个方法,否则会抛出java.lang.IllegalStateException,也就是当matches()、lookingAt()、find()其中任意一个方法返回true时才可以使用。