全网最通俗易懂的正则表达式讲解(java)
正则表达式语法
在其他语言中,\\ 表示:我想要在正则表达式中插入一个普通的(字面上的)反斜杠,请不要给它任何特殊的意义。
在 Java 中,\\ 表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。
所以,在其他的语言中(如 Perl),一个反斜杠 \ 就足以具有转义的作用,而在 Java 中正则表达式中则需要有两个反斜杠才能被解析为其他语言中的转义作用。也可以简单的理解在 Java 的正则表达式中,两个 \ 代表其他语言中的一个 \,这也就是为什么表示一位数字的正则表达式是 \\d,而表示一个普通的反斜杠是 \\。
System.out.print("\\"); // 输出为 \
System.out.print("\\\\"); // 输出为 \\
语法表
限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 ***** 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。
以下正则表达式匹配一个正整数,[1-9]设置第一个数字不是 0,[0-9]* 表示任意多个数字:
表达式 :[1-9][0-9]*
* 和 + 限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个 ? 就可以实现非贪婪或最小匹配。
贪婪:下面的表达式匹配从开始小于符号 (<) 到关闭 h1 标记的大于符号 (>) 之间的所有内容。
总结:满足条件下的最大匹配 ({0,}也是一样的情况)
非贪婪:如果您只需要匹配开始和结束 h1 标签,下面的非贪婪表达式只匹配 <h1>。
总结:满足条件下的最小匹配
定位符
定位符使您能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。
定位符用来描述字符串或单词的边界,^ 和 $ 分别指字符串的开始与结束,\b 描述单词的前或后边界,\B 表示非单词边界。
定位符表示的只是一个位置,不匹配任何内容
正则表达式的定位符有:
注意:不能将限定符与定位符一起使用。由于在紧靠换行或者单词边界的前面或后面不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。
若要匹配一行文本开始处的文本,请在正则表达式的开始使用 ^ 字符。不要将 ^ 的这种用法与中括号表达式内的用法混淆。
若要匹配一行文本的结束处的文本,请在正则表达式的结束处使用 $ 字符。字符串从右开始匹配。
示例:
1、 匹配由26个英文字母组成的字符串
^[A-Za-z]+$
重点:^匹配的是开始位置,$匹配的是截至位置,所以上面的规则只能匹配大小写英文字母
2、匹配除26个英文字母外组成的字符串
^[^A-Za-z]+$
2、匹配全为数字的字符串
^[0-9]+$
3、 只能输入由数字、26个英文字母或者下划线组成的字符串
^\w+$
4、 匹配首尾空格的正则表达式
(^\s+)|(\s+$)
重点:这里的"|"表示 或的意思,用小括号包裹起来的内容表示一个子表达式
5、 匹配中文字符的正则表达式 (根据 utf-8中文编码匹配 )
^[\u4e00-\u9fa5]+$
匹配单词边界稍有不同,但向正则表达式添加了很重要的能力。单词边界是单词和空格之间的位置。非单词边界是任何其他位置。下面的表达式匹配单词 Chapter 的开头三个字符,因为这三个字符出现在单词边界后面:
\bCha //匹配以Cha开头的单词
\b 字符的位置是非常重要的。如果它位于要匹配的字符串的开始,它在单词的开始处查找匹配项。如果它位于字符串的结尾,它在单词的结尾处查找匹配项。
例如,下面的表达式匹配单词 Chapter 中的字符串 ter,因为它出现在单词边界的前面:
ter\b //匹配以ter结尾的单词
单词边界就是单词和符号之间的边界,不只是空格
这里的单词可以是中文字符,英文字符,数字;符号可以是中文符号,英文符号,空格,制表符,换行
下面的表达式匹配 Chapter 中的字符串 apt,但不匹配 aptitude 中的字符串 apt:
\Bapt //匹配单词中包含apt的单词,不可以匹配单词的开头或结尾
字符串 apt 出现在单词 Chapter 中的非单词边界处,但出现在单词 aptitude 中的单词边界处。
分组和零宽断言
分组
public class Test01 {
private static final Pattern compile = Pattern.compile("https://www\\.bilibili\\.com/video/(\\w+).*");
public static void main(String[] args) {
Matcher matcher = compile.matcher("https://www.bilibili.com/video/BV1rq4y177C4/");
if (matcher.find()) {
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
}
}
}
但用圆括号会有一个副作用,使相关的匹配会被缓存,使用零宽断言来消除这种副作用。
零宽断言
零宽断言的意思是(匹配宽度为零,满足一定的条件/断言)
零宽断言用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。
断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配
它匹配到的内容不会保存到匹配结果中去,最终匹配结果只是一个位置而已。
作用是给指定位置添加一个限定条件,用来规定此位置之前或者之后的字符必须满足限定条件才能使正则中的字表达式匹配成功。
注意:这里所说的子表达式并非只有用小括号括起来的表达式,而是正则表达式中的任意匹配单元。
零宽断言组成部分:
( ?
括号问号,开启零宽断言(?<
小于号,代表限制断言左←边的字符,不加,则默认代表限制右边的字符。
(1)先行(lookahead) & 后行(后发)(lookbehind)
先行断言:是当扫描指针位于某处时,引擎会尝试匹配指针还未扫过的字符,先于指针到达该字符,故称为先行。
后行断言:引擎会尝试匹配指针已扫过的字符,后于指针到达该字符,故称为后行。
(2)正向(positive)和负向(negative)
正向就表示匹配括号中的表达式,负向表示不匹配。
零宽度正预测先行断言
(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。
比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找*I’m singing while you’re dancing.*时,它会匹配sing和danc。
示例:
- 当 exp1放在 exp2的后面。 exp2(?=exp1)
- 作用:用来筛选出runoob的后面为数字的runoob
- 当exp1放在ex2前面。(?=exp1)ex2
匹配过程如下:
- 首先由正则表达式中的"^"获取控制权,首先由位置0开始进行匹配,它匹配开始位置0,匹配成功
- 然后控制权转交给"
(?=<)
“,由于”^“是零宽的,所以”(?=<)“也是从位置0处开始匹配,它要求所在的位置右侧必须是字符”<“,位置0的右侧恰好是字符”<",匹配成功,然 - 后控制权转交个"<“,由于”(?=<)"也是零宽的,所以它也是从位置0处开始匹配,于是匹配成功,后面的匹配过程就不介绍了。
零宽度正回顾后发断言
(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
-
当exp1放在ex2前面:(?<=exp1)exp2
作用:用来筛选出runoob的前面是数字的runoob
- 当exp1放在ex2后面:exp2 (?<=exp1)
- 作用:用来获取以23结尾的3位数字
注意:匹配当前位置前面的所有字符串,不只是exp2的结果数据
- 作用:用来获取以23结尾的3位数字
零宽度负预测先行断言
(?!exp)也叫零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
- 当exp1放在ex2后面:exp2(?!exp1)
- 作用:用来筛选出runoob的后面不为数字的runoob
- 当exp1放在ex2前面:(?!exp1)exp2
- 作用:用来筛选出ab的后面不为大小写字母的ab
匹配过程如下:
- 首先由正则表达式的字符"a"获取控制权,从位置0处开始匹配,匹配字符"a"成功,然后控制权转交给"b",从位置1处开始匹配,配字符"b"成功,
- 然后控制权转交给"
(?![A-Z])
“,它从位置2处开始匹配,它要求所在位置的右边不能够是任意一个大写字母,而位置的右边是大写字母"Z”,匹配失败,
- 然后控制权又重新交给字符"a",并从位置1处开始尝试,匹配失败,
- 然后控制权再次交给字符"a",从位置2处开始尝试匹配,依然失败,
- 如此往复尝试,直到从位置7处开始尝试匹配成功,然后将控制权转交给"b",然后从位置8处开始尝试匹配,匹配成功,然后再将控制权转交给"
(?![A-Z])
“,它从位置9处开始尝试匹配,它规定它所在的位置右边不能够是大写字母,匹配成功,但是它并不会真正匹配字符,所以最终匹配结果是"ab”。
零宽度负回顾后发断言
(?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
- 当exp1放在ex2前面:(?<!exp1)exp2
- 作用:用来获取前面不为字符a的三位数字
- 当exp1放在ex2后面:exp2(?<!exp1)
- 作用:用来获取不为23结尾的3位数字
- \d,\W,\s,\B,.,*,+,?分别是什么?
\d表示0-9的数字。
\W表示非英文数字下划线的任意字符。
\s表示任意空白字符。
\B表示非单词开始或结尾的位置。
.表示非换行符的任意字符。
*表示匹配前面子表达式0次或多次。
+表示匹配前面子表达式1次或多次。
?表示匹配前面子表达式0次或1次。
- (?=a),(?<!a),(?<=a),(?!a)分别是什么?
(?=a)表示匹配a前面的字符。
(?<!a)表示匹配前面不是a的字符。
(?<=a)表示匹配a后面的字符。
(?!a)表示匹配后面不是a的字符。
- 什么是贪婪匹配和懒惰匹配?
贪婪匹配:表示尽可能多的匹配,常见写法是.*
懒惰匹配:也叫非贪婪匹配,表示尽可能少的匹配,常见写法是.*?
- \d{1,2}*这样的写法对吗?请说明理由。
不正确,这是因为所有限定类元字符后只能紧跟?这个限定类元字符,如果
紧跟其他限定类元字符则会报错。正确的写法是(\d{1,2})*
- 怎么让正则表达式从字符串的右边开始匹配?
使用$则表示从右边开始匹配,比如\d{2}$表示从右开始匹配两个数字
6、写出验证用户名的正则表达式,用户名只能输入英文、数字和下划线。
^\w+$
- 写出验证用户名的正则表达式,用户名只能输入汉字、英文、数字和下划线。
^[\w\u4e00-\u9fa5]+$
- 验证用户密码,长度在6~18 之间,只能包含英文和数字。
^[A-Za-z0-9]{6,18}$
- 匹配QQ号。
^[1-9]\d{4,12}$
正则表达式在线测试:https://c.runoob.com/front-end/854/
关于正则的算法实现:leetcode上:https://leetcode.cn/problems/regular-expression-matching/