Java正则表达式
1.介绍
正则表达式英文全称Regular Expression。一个正则表达式就是用某种模式去匹配字符串的一个公式,默认贪婪匹配。正则表达式不只是java语言独有的,许多编程语言都支持正则表达式对字符串的操作。
2.底层实现
要了解正则表达式的底层实现,那就要了解到两个类:Pattern和Matcher。
2.1 一个简单案例
下面通过一个简单案例演示如何使用正则表达式完成匹配,找出符合要求的子串。该案例实现从一个字符串中找出所有的四位数。
代码:
public class RegExp_1 {
public static void main(String[] args) {
//匹配的字符串
String content = "1999abx-#***2000ahj2001&2002";
//正则表达式,\\d代表一个任意0-9的数字,四个\\d代表4个连在一起的数字
String regex = "\\d\\d\\d\\d";
//获得一个模式
Pattern pattern = Pattern.compile(regex);
//匹配
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
//打印匹配到的值
System.out.println(matcher.group(0));
}
}
}
控制台输出:
2.2 案例解析
注意一下解析会搬出jdk源码,不需要去深究其中具体实现,只需要了解该方法有什么用?怎么使用?返回的值是什么?就可以了!
1.通过Pattern.compile(regex)方法传入正则表达式返回一个Pattern对象。下面这个代码块是jdk1.8中Pattern类的compile方法的源码,该方法是一个静态方法,返回一个Pattern对象。
public static Pattern compile(String regex) {
return new Pattern(regex, 0);
}
2.match意为匹配,调用Pattern类的matcher方法,传入需要匹配的字符串返回一个Mather对象。下面这个代码块是jdk1.8中Pattern类的matcher方法的源码。
public Matcher matcher(CharSequence input) {
if (!compiled) {
synchronized(this) {
if (!compiled)
compile();
}
}
Matcher m = new Matcher(this, input);
return m;
}
3.find意为找到,也就是找到了符合正则表达式的子串。通过查看Matcher类下该方法的源码,我们知道返回值是一个boolean类型的值。找到返回true,未找到返回false,因此我们在案例中调用并将其放在while循环中,使匹配直到再也找不到的时候才停止。
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);
}
4.每找到一个匹配的子串,就打印一下matcher.group(0),而打印出来的东西刚好就是我们所需的结果。在展示的案例中有四个符合的子串,因此打印了4次。可以看出这个group()是重点,要解释清楚还是挺不容易的,在接下来的篇章中会重点解析。
2.3 group()
先上源码:
public String group(int group) {
if (first < 0)
throw new IllegalStateException("No match found");
if (group < 0 || group > groupCount())
throw new IndexOutOfBoundsException("No group " + group);
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
return null;
return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
}
1.首先group(int)返回的是一个字符串,案例中group(0)返回的是我们所需要的结果,说明这个方法返回的是一个字符串也是匹配成功的子串。这个int型的参数为什么取0先别管。
2.返回的是getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString(),也就是调用了getSubSequence这个方法然后将方法的返回值转成了字符串。所以我们查看getSubSequence的源码:
CharSequence getSubSequence(int beginIndex, int endIndex) {
return text.subSequence(beginIndex, endIndex);
}
3.显然getSubSequence是截取了text字符串的部分获得一个子串,那这个text究竟是什么,看看jdk源码怎么说:
text意为匹配的原始字符串,也就是案例中的content。
4.接下来我们只要搞清楚beginIndex和endIndex是怎么产生的,也就是弄清楚groups[ ]这个数组到底有什么用。
为了了解groups[ ]里面有些什么,是怎么变化的,直接断点调试案例中的代码,在while(matcher.find())打断点:
(上图)可以看到matcher里有groups数组,初始化大小为20,全是-1;
(上图)往下走一步后,groups[0]=0,groups[l]=4
(上图)第二轮,groups[0]=12,groups[l]=16
(上图)第三轮,groups[0]=19,groups[l]=23
(上图)第四轮,groups[0]=24,groups[l]=28
四轮过后结束了。为什么会有4轮,因为案例中有四个匹配的子串,每一轮匹配到一个子串。通过观察会发现:groups只有下标0和1在变化。下标为0储存的值:匹配成功子串的开始下标,下标为1储存的值:匹配成功子串的终止下标+1!!!
5.所以第一轮的beginIndex=0,endIndex=4,截取了1999,以此类推…
2.4 group()的参数
这个参数与分组有关,先了解一下分组。
在正则表达式中,用()小括号括起来的就是一个分组,
现在修改案例代码如下:
public class RegExp_1 {
public static void main(String[] args) {
//匹配的字符串
String content = "1999abx-#***2000ahj2001&2002";
//正则表达式,\\d代表一个任意0-9的数字,四个\\d代表4个连在一起的数字
// String regex = "\\d\\d\\d\\d";
String regex2 = "(\\d\\d)(\\d\\d)";
//获得一个模式
Pattern pattern = Pattern.compile(regex2);
//匹配
Matcher matcher = pattern.matcher(content);
int n = 0;
while(matcher.find()){
//打印匹配到的值
System.out.println("匹配到的第"+(++n)+"个子串:"+matcher.group(0));
System.out.println("第1个分组:"+matcher.group(1));
System.out.println("第2个分组:"+matcher.group(2));
}
}
}
改进的代码中,正则表达式变为了两个分组,输出的多了group(1)和group(2)。
控制台输出:
可以很明显知道group(0)是打印整个子串,group(1)是打印子串的第1个分组,group(2)是打印子串的第二个分组。
为了说清楚这个group的参数,那追根到底就是要知道groups数组到底是怎么变化的,那再来debug一波:
(上图)第一轮,可以看到下标0-5都有了数值。推测一下不难得出0-1对应整个子串,2-3对应第一个分组,4-5对应第二个分组。不信可以再看以下几轮:
(上图)第二轮
(上图)第三轮
(上图)第四轮
四轮过后结束,不难看出groups数组记录的是整个子串以及分组在原字符串的开始下标与结束下标+1的数值。group(1)用到的就是groups数组下标为2-3的数值,group(2)用到的就是groups数组下标为4-5的数值,以此类推。
3.元字符
想要很好地使用正则表达式,必须了解各种元字符的功能,按功能分为以下六大类
3.1 限定符
用于指定其前面的字符和组合项连续出现多少次
符号 | 含义 |
---|---|
* | 指定字符重复0次或n次 |
+ | 指定字符重复1次或n次(最少1次) |
? | 指定字符重复0次或1次(最多1次) |
{n} | 长度为n |
{n,} | 指定至少n个字符匹配 |
{n} | 指定至少n个字符,至多m个字符匹配 |
? | 在其它限定符后加上?变为非贪婪匹配 |
3.2 选择匹配符
符号 | 含义 |
---|---|
l | 匹配 l 之前或者之后的表达式 |
3.3 分组组合和反向引用符
3.3.1 捕获分组
常用分组构造形式 | 说明 |
---|---|
(pattern) | 非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。 |
?<name>(pattern) | 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。 |
3.3.2 非捕获分组(特别分组)
常用分组构造形式 | 说明 |
---|---|
(?:pattern) | 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符组合模式部件的情况很有用。例如,industr(?:y l ies)是industry或industrie更经济的表达式。 |
(?=pattern) | 它是一个非捕获匹配。例如,“Windows (?=95[98[NT[2000)’匹配"Windows 2000"中的Windows”,但不匹配"Windows 3.1"中的"windows"。 |
(?!pattern) | 该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,“Windows (?I95]98[NT[2000)”匹配"Windows 3.1"中的“Windows”,但不匹配"Windows 2000"中的"Windows。. |
3.3.3 反向引用
圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\分组号,外部反向引用$分组号。
3.4 特殊字符
在使用正则表达式去匹配某些特殊符号时,需要用到转移符号(//),否则匹配不到结果甚至报错。
在java的正则表达式中,\代表其它语言中的\ !!!
需要用到转义字符的符号有:. * + ( ) & / \ ? [ ] ^ { }
3.5 字符匹配符
符号 | 含义 |
---|---|
[ ] | [abcd12] 匹配中括号中的任意一个字符 |
[^ ] | [^abcd12] 匹配除了中括号中的任意一个字符 |
- | A-Z 匹配任意单个大写字母 |
. | 匹配除换行符(\n)以外的任意字符 |
\\d | 匹配单个数字字符 相当于[0-9] |
\\D | 匹配单个非数字字符 相当于[^0-9] |
\\w | 匹配单个数字、大小写字母字符 相当于[0-9a-zA-Z] |
\\W | 匹配非单个数字、大小写字母字符 相当于[^0-9a-zA-Z] |
3.6 定位符
规定要匹配的字符串出现的位置
符号 | 含义 |
---|---|
^ | 指定开始字符 |
$ | 指定结束字符 |
\\b | 匹配目标字符串的边界 |
\\B | 匹配目标字符串的非边界 |
4.正则表达式的三个常用类
java.util.regex包主要包括以下三个类Pattern类、Matcher类和PatternSyntaxException
4.1 Pattern
其中有两个常用方法:
1.静态方法compile(String regex),返回一个Pattern对象。
2.静态方法matches(String regex,CharSequence input)用于整体匹配,返回一个boolean值。
上代码:
正则表达式只匹配部分,返回了false。
正则表达式在原先的基础上加上了至少匹配一个数字,结果返回true。
4.2 Matcher
Matcher类中方法众多,在这里放一个总览图,就不一一展示了。
4.3 PatternSyntaxException
PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
5 实战演练
以下答案不唯一,仅供参考
T1.验证电子邮件格式是否合法
规定电子邮件规则为
1.只能有一个@
2.@前面是用户名,可以是a-z A-Z0-9_-字符
3.@后面是域名,并且域名只能是英文字母,比如sohu.com或者tsinghua.org.cn
4.写出对应的正则表达式,验证输入的字符串是否为满足规则
String regex1 = "^[a-zA-Z0-9_]+@([a-z]+\\.)+[a-zA-Z]+$";
String regex2 = "^[\\w_]+@([a-z]+\\.)+[a-z]+$";
T2.要求验证是不是整数或者小数
提示:这个题要考虑正数和负数
比如:123 -345 34.89 -87.9 -0.01 0.45等
String regex = "^-?(?:[1-9]\\d*|0)(\\.\\d+)*$";
T3.对一个url进行解析
https://www.sohu.com:8080/abc/index.html
a.要求得到协议是什么?
http
b.域名是什么?
www.sohu.com
c.端口是什么?
8080
d.文件名是什么?
index.html
public static void main(String[] args) {
String content = "https://www.sohu.com:8080/abc/index.html";
String regex = "^([a-zA-Z]+)://([a-zA-Z.]+):([1-9]\\d*)[\\w/]*/([\\w]+\\.[a-z]+)$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
System.out.println("整体匹配:"+matcher.group(0));
System.out.println("协议:"+matcher.group(1));
System.out.println("域名:"+matcher.group(2));
System.out.println("端口号:"+matcher.group(3));
System.out.println("文件名:"+matcher.group(4));
}
}
结果:
T4.结巴去重
将字符串"java…是…是是.是…世界界…界上…最好的的.语语言"通过正则表达式转换为"java是世界上最好的语言"。
public static void main(String[] args) {
String content = "java...是..是是.是.....世界界..界上..最好的的.语语言";
String regex = "(.)";
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(content);
content = matcher.replaceAll("");
System.out.println("去掉省略号后:"+ content);
Pattern pattern1 = Pattern.compile("(.)\\1+");
Matcher matcher1 = pattern1.matcher(content);
content = matcher1.replaceAll("$1");
System.out.println(("去重后:"+ content));
}
结果:
到这里文章就结束了,欢迎各位批评指正,也别忘了点赞+评论+收藏