文章目录
- 1.概述
- 2.前瞻断言
- 3.否定的前瞻断言
- 4.后瞻断言
- 5. 捕获组
- 6.在密码强度规则中的应用
- 7.总结
1.概述
正则表达式之前瞻断言与后瞻断言
有时我们只需要为一个模式找到那些在另一个模式之后或之前的匹配项。
有一种特殊的语法,称为“前瞻断言(lookahead)”和“后瞻断言(lookbehind)”。
首先,让我们从字符串中查找价格,例如 1 turkey costs 30€。即:一个数字,后跟€符号。
2.前瞻断言
语法为:x(?=y),它表示“仅在后面是 Y 时匹配 X”。这里的 X 和 Y 可以是任何模式。
那么对于一个后面跟着 € 的整数,正则表达式应该为:\d+(?=€)。
let str = "1 turkey costs 30€"; alert( str.match(/\d+(?=€)/) ); //
30,数字 1 被忽略了,因为它后面没有 €
请注意:前瞻断言只是一个测试,括号 (?=…) 中的内容不包含在匹配结果 30 中。
当我们查找 X(?=Y) 时,正则表达式引擎会找到 X,然后检查其后是否有 Y。如果没有,则跳过潜在匹配,并继续搜索。
更复杂的测试也是可能的,例如 X(?=Y)(?=Z) 表示:
寻找 X。
检查 Y 是否紧跟在 X 之后(如果不是则跳过)。
检查 Z 是否也在 X 之后(如果不是则跳过)。
如果两个测试都通过了,那么 X 是匹配的,否则继续搜索。
换句话说,这样的模式意味着我们同时在寻找 X 后跟 Y 和 Z。
这只有在模式 Y 和 Z 不是互斥的情况下才可行。
例如,\d+(?=\s)(?=.*30) 查找后跟着空格 (?=\s) 的 \d+,并且有 30 在它之后的某个地方 (?=.*30):
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1
在我们给出的字符串中,与数字 1 完全匹配。
3.否定的前瞻断言
假设我们想要一个数量,而不是来自同一字符串的价格。那是一个数字 \d+,后面不是 €。
为此,我们可以使用否定的前瞻断言。
语法是:X(?!Y),意思是“搜索 X,但前提是后面没有 Y”。
let str = "2 turkeys cost 60€";
alert( str.match(/\d+\b(?!€)/g) ); // 2(价格不匹配)
4.后瞻断言
⚠️ 后瞻断言的浏览器兼容情况
请注意:非 V8 引擎的浏览器不支持后瞻断言,例如 Safari、Internet Explorer。
前瞻断言允许添加一个“后面要跟着什么”的条件判断。
后瞻断言也类似,只不过它是在相反的方向上进行条件判断。也就是说,它只允许匹配前面有特定字符串的模式。
语法为如下:
- 肯定的后瞻断言:(?<=Y)X,匹配 X,仅在前面是 Y 的情况下。
- 否定的后瞻断言:(?<!Y)X,匹配 X,仅在前面不是 Y 的情况下。
例如,让我们把价格换成美元。美元符号通常在数字前面,所以要查找 $30 我们将使用 (?<=$)\d+ —— 一个前面带 $ 的数值:
let str = "1 turkey costs $30";
// 美元符号被转义 \$
alert( str.match(/(?<=\$)\d+/) ); // 30(跳过了仅仅是数字的值)
如果我们需要找到量词 —— 一个前面不带 $ 的数字,我们可以使用否定的后瞻断言:(?<!$)\d+
let str = "2 turkeys cost $60";
alert( str.match(/(?<!\$)\b\d+/g) ); // 2(价格不匹配)
5. 捕获组
一般来说,前瞻断言和后瞻断言括号中的内容不会成为结果的一部分。
例如,在模式 \d+(?!€) 中,€ 符号就不会出现在匹配结果中。这是很自然的事:我们寻找一个数字 \d+,而 (?=€) 只是一个测试,表示要匹配的数字后面应该紧跟着 € 字符。
但在某些情况下,我们可能还想捕获前瞻断言和后瞻断言所匹配的内容,或者部分内容。这也是可行的。只需要将该部分包装在额外的括号中。
在下面的示例中,货币符号 (€|kr) 和金额一起被捕获了:
let str = "1 turkey costs 30€";
let regexp = /\d+(?=(€|kr))/; // €|kr 两侧有额外的括号
alert( str.match(regexp) ); // 30, €
后瞻断言也一样:
let str = "1 turkey costs $30";
let regexp = /(?<=(\$|£))\d+/;
alert( str.match(regexp) ); // 30, $
6.在密码强度规则中的应用
java正则表达式校验密码必须是包含大小写字母、数字、特殊符号的8位以上组合
一、需求:密码必须是包含大写字母、小写字母、数字、特殊符号(不是字母,数字,下划线,汉字的字符)的8位以上组合
二、方案:利用正则表达式来校验
三、思路:排除法
1、排除大写字母、小写字母、数字、特殊符号中1种组合、2种组合、3种组合,那么就只剩下4种都包含的组合了
2、表达式为:^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,}$
3、拆分解释:其中(2)-(6)运用了零宽断言、环视等正则功能
(1)^匹配开头
(2)(?![A-Za-z0-9]+$)匹配后面不全是(大写字母或小写字母或数字)的位置,排除了(大写字母、小写字母、数字)的1种2种3种组合
(3)(?![a-z0-9\W]+$)同理,排除了(小写字母、数字、特殊符号)的1种2种3种组合
(4)(?![A-Za-z\W]+$)同理,排除了(大写字母、小写字母、特殊符号)的1种2种3种组合
(5)(?![A-Z0-9\W]+$)同理,排除了(大写字母、数组、特殊符号)的1种2种3种组合
(6)[a-zA-Z0-9\W]匹配(小写字母或大写字母或数字或特殊符号)因为排除了上面的组合,所以就只剩下了4种都包含的组合了
(7){8,}8位以上
(8)$匹配字符串结尾
四、代码:
public class RegexTest {
public static final String PW_PATTERN = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,}$";
public static void main(String[] args) {
String pw1 = "ABCDEFGHIG";
String pw2 = "abcdefghig";
String pw3 = "0123456789";
String pw4 = "!@#$%^&*()";
String pw5 = "ABCDEabcde";
String pw6 = "ABCDE01234";
String pw7 = "ABCDE!@#$%";
String pw8 = "abcde01234";
String pw9 = "abcde!@#$%";
String pw10 = "01234!@#$%";
String pw11 = "abcde01234!@#$%";
String pw12 = "ABCDE01234!@#$%";
String pw13 = "ABCDEabcde!@#$%";
String pw14 = "ABCDEabcde01234";
String pw15 = "Aa0!";
//符合要求密码
String pw16="ABCabc012!@#";
System.out.println(pw1.matches(PW_PATTERN));
System.out.println(pw2.matches(PW_PATTERN));
System.out.println(pw3.matches(PW_PATTERN));
System.out.println(pw4.matches(PW_PATTERN));
System.out.println(pw5.matches(PW_PATTERN));
System.out.println(pw6.matches(PW_PATTERN));
System.out.println(pw7.matches(PW_PATTERN));
System.out.println(pw8.matches(PW_PATTERN));
System.out.println(pw9.matches(PW_PATTERN));
System.out.println(pw10.matches(PW_PATTERN));
System.out.println(pw11.matches(PW_PATTERN));
System.out.println(pw12.matches(PW_PATTERN));
System.out.println(pw13.matches(PW_PATTERN));
System.out.println(pw14.matches(PW_PATTERN));
System.out.println(pw15.matches(PW_PATTERN));
System.out.println(pw16.matches(PW_PATTERN));
}
}
输出结果除最后一行外全是false,即只有最后一个密码是符合要求的
7.总结
当我们想根据前面/后面的上下文匹配某些内容的时候,前瞻断言和后瞻断言(通常被称为“环视断言”)很有用。
对于简单的正则表达式,我们可以手动执行类似的操作。即:不管上下文,匹配所有可匹配的内容,然后在循环中根据上下文进行过滤。
请记住,str.match(没有修饰符 g)和 str.matchAll(总是)将匹配项作为具有 index 属性的数组返回,因此我们知道它在文本中的确切位置,并且可以检查上下文。
但通常环视断言更方便。
环视断言类型:
模式 | 类型 | 匹配 |
X(?=Y) | 肯定的前瞻断言 | X 后紧跟着 Y |
X(?!Y) | 否定的前瞻断言 | X 后没紧跟着 Y |
(?<=Y)X | 肯定的后瞻断言 | X 紧跟在 Y 后面 |
(?<!Y)X | 否定的后瞻断言 | X 没紧跟在 Y 后面 |