目录
引言:
由于正则表达式写起来给人看上去极其不友好(乱,杂),以至于很多人对这个语法是陌生的,但是,如果我们能够正确灵活的运用正则表达式,将会极大的提高我们的开发效率。
一.案例引入
我们先引入一个案例来看看。
String context = "a 11c 8a__sda% $&*@...abcaBc";
String regStr = "a(?i)bc"; //b不区分大小写
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(context);
while(matcher.find()){
System.out.println("匹配到: " + matcher.group(0));
}
结果:
简单看一下这段代码(一些看不懂的地方不要紧,因为还没有介绍正则的内容):
- 然后我们通过调用Pattern的compile方法返回一个比较器对象。
- 调用比较器的方法macher进行匹配比较。
- 通过循环找到这段字符串里满足要求的子串。
二.正则表达式的底层实现
由于作者的水平有限,这里只是简单对底层的实现进行一个介绍。
继续拿上面的案例进行介绍。
在这块,我们主要涉及到源码里的两个类: Pattern和Matcher
Pattern的compile()源码
我们可以看到,确实是返回了一个new的Pattern对象。
参数介绍:
regex是我们传进来的正则表达式;
flags是一个匹配标志(源码里的介绍):
Match flags, a bit mask that may include {@link #CASE_INSENSITIVE}, {@link #MULTILINE}, {@link #DOTALL}, {@link #UNICODE_CASE}, {@link #CANON_EQ}, {@link #UNIX_LINES}, {@link #LITERAL}, {@link #UNICODE_CHARACTER_CLASS} and {@link #COMMENTS}举个例子:
CASE_INSENSITIVE : 这个标志就代表匹配时不区分大小写。
Pattern里的match() (匹配的方法):
可以看到,在这里最终调用的其实是Matcher类的Matcher()方法。这里关于源码,其实我们也没有必要完全看懂,这个东西的完全实现还是有一点复杂的。
fina()和gruop()
- find()方法
find()方法就是用来循环查找下一个符合正则表达式的子串的
find()方法做的事情:
find()方法会将成功找到的子串的始末位置记录到groups[]数组中,把开始索引的位置记录到groups[0],结束索引的位置记录到groups[1];下次执行时,直接从groups[1]的位置开始find()。
- group[]数组
group[]数组用来存放匹配到的字符串。但并不是将字符串存放到数组中(这明显是一个int[]),而是将匹配的子串的开始和结束索引位置存放到数组中。
但是还是有一个关于group[]疑问:
关于这一个,我们再来写一个小例子:
匹配一个子串(前五个是字母,字母不区分大小写,后三个是数字)。
肉眼我们可以观察到:"hello123" , "lskda215" , "hello123"是符合要求的。
public static void main(String[] args) {
String str = "hello123abc w;orld ;a'lb'c lskda2154p;]=hello123asdx";
//匹配前五个是字母,后面跟三个数字;
String regStr = "([a-zA-Z]{5})([0-9]{3})";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(str);
while (matcher.find()){
System.out.println("匹配到的完整子串:" + matcher.group(0));
System.out.println("匹配到的第一组子串:" + matcher.group(1));
System.out.println("匹配到的第二组子串:" + matcher.group(2));
}
}
看一下运行结果:
这里我提到了分组的概念,同时我们也看到groups数组里的数字好像也跟这个分组有点关系。我们来介绍一下分组的概念
正则表达式的分组概念:
在我们的正则表达式里,一个小括号就代表一个分组(从1开始累加),0代表整个串的分组。
这是最初的写法:
public static void main(String[] args) {
String str = "hello123abc w;orld ;a'lb'c lskda2154p;]=hello123asdx";
//匹配前五个是字母,后面跟三个数字;
String regStr = "[a-zA-Z]{5}[0-9]{3}";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(str);
while (matcher.find()){
System.out.println("匹配到的完整子串:" + matcher.group(0));
}
}
groups[]的简单分析:
由上图可知:groups数组依次存放的就是相应的索引位置。
三.正则表达式的基本语法(重要)
说了那么多,正则表达式的语法才是实干的重要部分,如果你能够熟练运用各种元字符,底层上有一些没有搞懂又有什么关系呢?
元字符的分类
- 限定符
- 选择匹配符
- 分组组合和反向引用符
- 特殊字符
- 字符匹配符
- 定位符
限定符:
作用:用于指定其前面的字符和组合连续出现多少次
符号 | 说明 |
{n,} | 指定至少n个匹配 |
{n,m} | 指定至少n个但不多余m个匹配 |
* | 指定字符重复0次或n次 |
+ | 指定字符重复1次或n次(至少1次) |
? | 指定字符重复0次或1次 |
{n} | 只能输入n个字符 |
示例:
[abcd]{3,} | “abcd“中任意长度不小于3的字符串 : abc ,abd ,bcd ,aabbcd , dbc |
[abcd]{3,5} | "abcd"字母组成的任意长度不小于3,且不大于5的子串 : aaaaa, abcda , badac |
(abc)* | 包含任意个abc(可以是0): abc , abcabc , abcabcabc.... |
x+(abc)* | 以至少一个x开头,且任意个abc结尾: x , xabc , xxabcabc , xxxxabc |
x+(abc)? | 以至少一个x开头,且最多一个abc结尾 : xxx , xxxabc , xxxxabc |
(abcd){3} | 由abcd中任意组成的长度为3的组合: abc ,abd ,bcd , bca , bda |
选择匹配符
符号 | 说明 | 示例 |
| | 匹配“|”之前或之后的表达式 | ab|cd : ab或者cd |
分组组合和反向引用符
注:这里的"pattern"是你在实际的场景中写出的正则表达式
分组组合符:
符号 | 说明 |
(pattern) | 捕获pattern到默认分配的组里去,编号为零的是整个正则表达式匹配的内容 |
(?<name>pattern) | 捕获pattern到命名为"name"的组里去 |
(?:pattern) | 非捕获匹配,只匹配pattern,但不捕获该匹配的子字符串。 |
(?=pattern) | 非捕获匹配 ,只匹配pattern之前的位置 |
(?!pattern) | 非捕获匹配,只匹配非pattern之前的位置 |
反向
反向引用符号
符号 | 说明 |
\\ | 内部反向引用使用 |
$ | 外部反向引用使用 |
当圆括号的内容被捕获后(分组的内容),可以在这个括号后被使用,从而快速便捷的达到一些匹配的目的。
案例演示:匹配电话号码
需求:前五个是任意数字,然后- , 后面九位数,每三位都一样
String str = "hello jerry13545-111222333 1221 tom11111 jerry";
String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(str);
while (matcher.find()){
System.out.println("匹配到:" + matcher.group(0));
}
}
\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}
"(\\d)"被捕获后,由于这是我们的第一个分组,“\\1”就是引用第一组的内容;
后面的{2},代表除了已经出现的一次,再出现两次,一共三次。
关于外部反向引用的$,我们在后面的一个例子中举例说明。
定位符
符号 | 说明 |
^ | 指定起始字符 |
$ | 指定结束字符 |
\\b | 匹配目标字符串的边界(一般是挨着空格) |
\\B | 匹配目标字符串的非边界(和\\b相反) |
字符匹配符
符号 | 说明 |
. | 匹配除\n以外的任何字符 |
\\d | 匹配单个数字字符 |
\\D | 匹配单个非数字字符 |
\\w | 匹配任意英文字母,数字还有下划线 |
\\W | 匹配特殊字符,与\\w相反 |
[] | 可接受的字符列表 |
[^] | 不接受的字符列表 |
- | 连字符 |
四.正则表达式练习
外部反向引用的实例。
题目:将 “我我我...要...学学学学学..编...程程.程....Ja..v..a” 恢复正常 “我要学编程Java!”
public static void main(String[] args) {
String str = "我我...要要学学学学....编..程Java!";
//第一步,先去除"."
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(str);
while (matcher.find()){
str = matcher.replaceAll("");
}
System.out.println("去除.后: " + str);
//第二步,去除重复的字
pattern = pattern.compile("(.)\\1+");
matcher = pattern.matcher(str);
while (matcher.find()){
str = matcher.replaceAll("$1");
}
System.out.println("净化完成后的: "+ str);
}
关于正则表达式,需要大家多多熟悉,多多练习,本文也只是为大家介绍了一部分。