一、正则表达式是什么?
正则表达式就是一个用于匹配字符串的字符串模板,可以匹配一批字符串。在实际开发中,会遇到很多处理字符串的情景,当然String类已经提供了很多方法方便我们处理字符串,但是有的时候需要处理的规则比较复杂,往往在这些时候,使用正则表达式可以让问题迎刃而解。可以说正则表达式就是处理字符串的神器。还有就是,精通正则可以装B,因为大部分程序猿对正则的使用还是比较少的,如果你精通了正则,就可以在朋友面前秀一把。
二、怎么创建正则表达式?要创建正则表达式需要懂得正则的语法,语法如下
1.正则表达式支持的合法字符如下表所示
使用最多的是第一行的x
字符 | 解释 |
---|---|
x | 代表一个字符,比如1、2、a、#等,在这里x代表的是任何合法字符 |
\0xxx | 代表一个八进制数,x代表 0 ~ 7 ,如0271 |
\xhhh | 代表一个十六进制数,h代表 0 ~ 9和A ~ F,如0xAC9D |
\uhhhh | 代表一个十六进制数字0xhhhh所表示的Unicode字符 |
\t | 代表制表符(\u0009) |
\n | 代表一个换行符(\u000A) |
\r | 代表一个回车符(\u000C) |
\f | 代表一个换页符(\u000C) |
2.特殊字符
正则表达式中有一些特殊字符,在正则语法里面有它自身的含义,如果要匹配特殊字符本身需要使用其对应转义字符,如下表所示。
特殊字符 | 解释 |
---|---|
$ | 匹配一行的结尾。如果要匹配$本身,使用 $ |
^ | 匹配一行的开头。如果要匹配^本身,使用 \^ |
( ) | 标记子表达式的开始和结束为止。要匹配他们,使用 \( 和 \) |
[ ] | 用于确定中括号表达式开始和结束的位置。要匹配他们,使用 \[ 和 \] |
{ } | 用于标记前面的子表达式出现的频度。要匹配他们,使用 \{ 和 \} |
* | 指定前面子表达式可以出现0次或多次。 要匹配*本身,使用 \* |
+ | 指定前面子表达式可以出现1次或多次。要匹配+本身,使用 \+ |
? | 指定前面子表达式可以出现0次或1次。要匹配?本身,使用 \? |
. | 匹配出换行符\n之外的任何单词字符。要匹配 . 本身,使用 \. |
/ | 用于转义下一个字符,或指定八进制、十六进制字符。如果需要匹配\本身,使用 \\ |
| | 正定在两项之间任选一项。如果要匹配 | 本身,使用 | |
注意:
在Java里,反斜杠 \ 本身具有特殊含义,如果要用正则匹配反斜杠本身,那么对应的正则表达式是4个反斜杠,即 ‘\\\\’。因为在正则语法里匹配反斜杠本身需要进行一次转义,即 ‘\\’ 。到了java里面,还需要还需要对 ‘\\’ 中的两个反斜杠再进行一次转义,因此变成了4个斜杠 ‘\\\\’。如果要匹配 ‘(’,在java里面就是 ‘\\(’。
3. 通配符
通配符是可以匹配一类字符的特殊字符,也被叫做预定义字符,如下表所示。
通配符 | 解释 |
---|---|
. | 匹配任何字符 |
\d | 匹配 0 ~ 9 的所有数字 |
\D | 匹配非数字 |
\s | 匹配所有空白字符,包括空客、制表符、回车符、换页符、换行符等 |
\S | 匹配所有非空白字符 |
\w | 匹配所有单词字符,包括 0 ~ 9 所有数字、26个英文字母(不区分大小写)和下划线 |
\W | 匹配所有非单词字符 |
举例:
‘\d\d\d - \d\d\d\d\d\d\d\d’ 这个正则可以匹配 028 - 24619271这种电话号码。注意哦,在java里的话也需要对这里的反斜杠进行转义,转义后对应的正则是 ‘\\d\\d\\d - \\d\\d\\d\\d\\d\\d\\d\\d’ 。你是不是觉得这里写了这么多个 \d 太啰嗦了,没错,继续往后面看,后面会有解决的。
4.方括号表达式
在有些特殊的情况下,指向匹配 a ~ h 的字母、或者匹配出ab之外的所有小写字母、或者匹配所有的中文字符。怎么办,上面介绍的这些东西就无能为力了,好在我们还有方括号表达式,如下表所示。
方括号表达式 | 解释 |
---|---|
表示枚举 | 例如 [abc], 表示a、b、c其中任意一个字符; [12Gh],表示1、2、G、h其中的任一字符; |
表示范围 | 例如 [a-f],表示a ~ f范围内的任意字符; [\u0041-\u0056],表示十六进制字符\u0041到\u0056范围的字符; 范围可以和枚举结合使用,如[a-dh-z]表示ad、hz范围内的任一字符; |
表示取非:^ | 例如 [abc]表示非a、b、c的任意字符;[h-z],表示非h~z范围内的任意字符; |
表示 '与' 运算:&& | 例如 [a-z&&[def]],表示求az与d、e、f的交集,即d、e、f;<br/>[a-z&&[^bc]],表示az范围内除了b、c的字符,与[ad-z]等价; [a-z&&[^m-p]],表示az范围内除了mp范围内的任一字符,与[a-lq-z]等价; |
表示 '并' 运算 | 并运算和前面的枚举类似,如 [a-d[m-p]],表示[a-dm-p] |
5.圆括号表达式
正则表达式还支持圆括号表达式,用于将多个表达式组成一个表达式,圆括号表达式还可以使用或运算符 ‘|’,语法很简单。
例如: ((private)|(protected)|(public))用于匹配Java的访问控制符之一
6.边界匹配符
边界匹配符可以匹配一些元素的边界,其中 ^ 和 $ 用的最多,如下表所示
边界匹配符 | 解释 |
---|---|
^ | 行的开头 |
$ | 行的结尾 |
\b | 单词的边界 |
\B | 非单词的边界 |
\A | 输入的开头 |
\G | 前一个匹配的结尾 |
\Z | 输入的结尾,进用于最后的结束符 |
\z | 输入的结尾 |
7.量词
前面写过一个正则表达式是 ‘\d\d\d - \d\d\d\d\d\d\d\d’,这看起十分繁琐,好在正则语法提供了量词来解决这个问题,量词说明如下表所示
量词 | 说明 |
---|---|
X? | X表达式出现0次或1次 |
X* | X表达式出现0次或多次 |
X+ | X表达式至少出现1次 |
X{n} | X表达式出现n次 |
X{n,} | X表达式至少出现n次 |
X{n,m} | X表达式至少出现n次,至多出现m次 |
有了量词后,我们可以把 ‘\d\d\d - \d\d\d\d\d\d\d\d’ 改成 ‘\d{3}-\d{8}’
注意:量词匹配模式
在java中,量词的匹配模式有三种:
- 贪婪模式:默认的就是贪婪模式,贪婪模式会一直匹配下去直到无法匹配。比如 ‘\w{2, 10}’ ,表示最少匹配2次,但是默认是贪婪匹配,匹配到2次后不会停止,会一直匹配下去,直到匹配达到10次或者不能再匹配到字符,只要匹配到2-10次都算成功。
- 勉强模式:用 ?作为量词的后缀,就像 ‘\w{2,10}?’ 这样,勉强模式会按照最少次数去匹配。比如 ‘\w{2,10}’ 表示对\w匹配2次或10次,但是如果在后面加上?变成‘\w{2,10}?’,变成了勉强模式,就会按照最少匹配,即匹配到2次就算成功。
- 占有模式:用 + 作为量词的后缀,就像 ‘\w*{2,10}+’ 这样,和贪婪模式一样,占有模式会尽可能多的去匹配,但是贪婪模式匹配不到字符后会有回溯操作,而占有模式没有回溯操作,这个回溯可以再写一篇文章了,为了篇幅原因,这里不过多介绍,不过下面会提供参考文章链接。
关于贪婪模式、勉强模式、占有模式这里做了简单介绍,平时使用的最多的还是贪婪模式,但是贪婪模式会有回溯陷阱,具体可以参考以下文章,作者写的很好,他介绍了正则原理和回溯机制,可以帮助我们更好的理解这三种模式,写出性能更好的正则表达式。
正则表达式三种模式:贪婪模式、懒惰模式、独占模式
三、上面介绍了正则表达式的语法,下面就说在java里面怎么使用正则表达式吧
java中与正则表达式相关的类主要是Pattern和Matcher,Pattern类是不可变类,可以让多个线程并发安全使用。Pattern对象是正则表达式编译之后在内存中的存在形式,因此正则表达式字符串必须先被编译为Pattern对象,然后再利用Pattern对象创建对应的Matcher对象。执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可共享一个Pattern对象。
典型的使用流程如下:
// 将一个正则表达式编译成Pattern对象
Pattern pattern = Pattern.compile("ab{1,3}V");
// 使用Pattern对象创建Matcher对象
Matcher matcher = pattern.matcher("abbV");
// 尝试将整个字符串与正则表达式进行匹配,
boolean matches = matcher.matches();
上面代码中定义的Pattern对象可以重复多次使用,如果某个正则表达式仅需使用一次,则可以使用Pattern类的静态方法matches(),如下:
boolean matches = Pattern.matches("ab{1,3}V", "abbV");
需要注意的是,如果正则表达式需要重复使用,建议提前将它编译好,作为一个成员对象保存在内存中,否则每次重新编译正则表达式,效率不高。
Matcher类提供常用方法如下:
- find():返回目标字符串是否包含了Pattern匹配的子串
- group():返回上一次与Pattern匹配的子串
- start():返回上一次与Pattern匹配的自传在目标字符串中的开始位置
- end():返回上一次与Pattern匹配的自传在目标字符串中的结束为止加1
- matches():返回整个目标字符串与Pattern事发后匹配
- lookingAt():和matches()方法类似,matches方法是将整个目标字符串进行匹配,而该方法是将目标字符串前缀部分与Pattern匹配
- reset():将现有的Matcher对象应用于一个新的字符序列
下面写个小demo来展示这些方法的使用
@Test
public void regexDemo() {
// webCotent是从网上截取的一小段内容,我们用正则匹配出里面所有的电话号码,实现一个爬虫小demo
String webContent = "\n" + "深圳移动\n" + "移动选号\n" + "情侣号码\n" + "家庭号码\n" + "移动资费\n" + "移动营业厅\n" + "深圳联通\n" + "联通选号\n" + "情侣号码\n" + "家庭号码\n" +
"联通资费\n" + "联通营业厅\n" + "深圳电信\n" + "电信选号\n" + "情侣号码\n" + "家庭号码\n" + "电信资费\n" + "电信营业厅\n" + "特色服务\n" + "回收号码\n" + "订制靓号\n" +
"情侣号匹配\n" + "风水靓号\n" + "寄卖号码\n" + "号码测吉凶\n" + "\n" + " 1 2 3 4 \n" + "\n" + " 优惠快报\n" + "\n" + " 工信部:电话用户真实身份信息登...\n" +
" 北京号码订制特价-联通185靓号最...\n" + " 冰激凌手机号码来啦,快抢购吧\n" + " 手机靓号的价值意义\n" + " 手机号码测吉凶到底靠谱吗?\n" + " 投资手机号码的好处\n" +
" 手机靓号的发展历程\n" + " 哪些人最喜欢手机靓号?\n" + " 139手机号码靓号有什么特别的意...\n" + " 经典手机靓号的小故事\n" + "\n" + "会员登录\n" + "会员注册\n" +
"\n" + "特价靓号限时抢购\n" + "距离结束0:50:56\n" + "\n" + " 13699877999\n" + " 深圳移动\n" + " 卡费:¥34203 话费:¥0\n" + " 13823138888\n" + " 深圳移动\n" +
" 卡费:议价 话费:¥100\n" + " 18107556789\n" + " 深圳电信\n" + " 卡费:¥35000 话费:¥100\n" + " 13823335000\n" + " 深圳移动\n" + " 卡费:¥11319 话费:¥0\n" +
" 13802591000\n" + " 深圳移动\n" + " 卡费:¥13985 话费:¥55\n" + "\n" + " 18566666630\n" + " 深圳联通\n" + " 卡费:¥24385 话费:¥55\n" +
" 13823621666\n" + " 深圳移动\n" + " 卡费:¥37385 话费:¥55\n" + " 13631650000\n" + " 深圳移动\n" + " 卡费:¥41545 话费:¥55\n" + " 13828888815\n" +
" 深圳移动\n" + " 卡费:¥49345 话费:¥55\n" + " 13823260888\n" + " 深圳移动\n" + " 卡费:¥62345 话费:¥55\n" + "\n" + " 13823291888\n" +
" 深圳移动\n" + " 卡费:¥62345 话费:¥55\n" + " 13809881899\n" + " 深圳移动\n" + " 卡费:¥11200 话费:¥0\n" +
" 13808808006\n" + " 深圳移动\n" + " 卡费:¥11200 话费:¥0\n" + " 13556699336\n" + " 深圳移动\n" + " 卡费:¥13499 话费:¥10\n" +
" 15602318888\n" + " 深圳联通\n" + " 卡费:¥60840 话费:¥0\n" + "\n" + " 13265629999\n" + " 深圳联通\n" + " 卡费:¥49140 话费:¥0\n" +
" 13538088885\n" + " 深圳移动\n" + " 卡费:¥20800 话费:¥0\n" + " 15012991299\n" + " 深圳移动\n" + " 卡费:¥23400 话费:¥0\n" + " 18823331391\n" +
" 深圳移动\n" + " 卡费:¥11880 话费:¥0\n" + " 13686888885\n" + " 深圳移动\n" + " 卡费:¥71500 话费:¥0\n" + "\n" + " 13823333328\n" +
" 深圳移动\n" + " 卡费:¥52000 话费:¥0\n" + " 13662222220\n" + " 深圳移动\n" + " 卡费:¥71500 话费:¥0\n" + " 13686868681\n" + " 深圳移动\n" +
" 卡费:¥65000 话费:¥0\n" + " 13603060306\n" + " 深圳移动\n" + " 卡费:¥58500 话费:¥0\n" + " 13902989889\n" + " 深圳移动\n" + " 卡费:¥58500 话费:¥0\n" +
"\n" + " 13922862286\n" + " 深圳移动\n" + " 卡费:¥58500 话费:¥0\n" + " 18822822282\n" + " 深圳移动\n" + " 卡费:¥52000 话费:¥0\n" +
" 13828888339\n" + " 深圳移动\n" + " 卡费:¥49400 话费:¥0\n" + " 13889918991\n" + " 深圳移动\n" + " 卡费:¥45500 话费:¥0\n" + " 18816881868\n" +
" 深圳移动\n" + " 卡费:¥36400 话费:¥0\n" + "\n";
Pattern pattern = Pattern.compile("\\d{11}");
Matcher matcher = pattern.matcher(webContent);
while (matcher.find()) {
System.out.println("起始位置:" + matcher.start() + ",结束位置:" + matcher.end() + ",号码:" + matcher.group());
}
}
结果如下:
起始位置:376,结束位置:387,号码:13699877999
起始位置:422,结束位置:433,号码:13823138888
起始位置:466,结束位置:477,号码:18107556789
起始位置:514,结束位置:525,号码:13823335000
起始位置:560,结束位置:571,号码:13802591000
起始位置:608,结束位置:619,号码:18566666630
起始位置:655,结束位置:666,号码:13823621666
起始位置:702,结束位置:713,号码:13631650000
起始位置:749,结束位置:760,号码:13828888815
起始位置:796,结束位置:807,号码:13823260888
起始位置:844,结束位置:855,号码:13823291888
起始位置:891,结束位置:902,号码:13809881899
起始位置:937,结束位置:948,号码:13808808006
起始位置:983,结束位置:994,号码:13556699336
起始位置:1030,结束位置:1041,号码:15602318888
起始位置:1077,结束位置:1088,号码:13265629999
起始位置:1123,结束位置:1134,号码:13538088885
起始位置:1169,结束位置:1180,号码:15012991299
起始位置:1215,结束位置:1226,号码:18823331391
起始位置:1261,结束位置:1272,号码:13686888885
起始位置:1308,结束位置:1319,号码:13823333328
起始位置:1354,结束位置:1365,号码:13662222220
起始位置:1400,结束位置:1411,号码:13686868681
起始位置:1446,结束位置:1457,号码:13603060306
起始位置:1492,结束位置:1503,号码:13902989889
起始位置:1539,结束位置:1550,号码:13922862286
起始位置:1585,结束位置:1596,号码:18822822282
起始位置:1631,结束位置:1642,号码:13828888339
起始位置:1677,结束位置:1688,号码:13889918991
起始位置:1723,结束位置:1734,号码:18816881868