- 一、环视基础
- 二、顺序环视匹配过程
- (一)顺序肯定环视匹配过程
- (二)顺序否定环视匹配过程
- 三、逆序环视匹配过程
- (一)逆序环视基础
- (二)逆序肯定环视匹配过程
- 1. 逆序表达式的长度固定,如何匹配
- 2. 逆序表达式的长度不固定,如何匹配
- (1)匹配开始位置不确定,匹配结束位置确定
- (2)匹配开始位置确定,匹配结束位置不确定
- (三)逆序否定环视匹配过程
- 1. 逆序表达式的长度固定,如何匹配
- (1)匹配起始位置不确定,匹配结束位置确定
- (2)匹配起始位置确定,匹配结束位置不确定
- 2. 逆序表达式的长度不固定,如何匹配
环视只进行子表达式的匹配,不占有字符,匹配到的内容不保存到最终的匹配结果,是零宽度的。环视匹配的最终结果就是一个位置。
环视的作用相当于对所在位置加了一个附加条件,只有满足这个条件,环视子表达式才能匹配成功。
环视按照方向划分有顺序和逆序两种,按照是否匹配有肯定和否定两种,组合起来就有四种环视。顺序环视相当于在当前位置右侧附加一个条件,而逆序环视相当于在当前位置左侧附加一个条件。
表达式说明(?逆序否定环视,表示所在位置左侧不能匹配 Expression(?=Expression)顺序肯定环视,表示所在位置右侧能够匹配 Expression(?!Expression)顺序否定环视,表示所在位置右侧不能匹配 Expression环视是正则中的一个难点,对于环视的理解,可以从应用和原理两个角度理解,如果想理解得更清晰、深入一些,还是从原理的角度理解好一些,正则匹配基本原理参考《NFA引擎匹配原理》。
上面提到环视相当于对“所在位置”附加了一个条件,环视的难点在于找到这个“位置”,这一点解决了,环视也就没什么秘密可言了。
对于顺序肯定环视(?=Expression)
来说,当子表达式Expression
匹配成功时,(?=Expression)
匹配成功,并报告(?=Expression)
匹配当前位置成功。
对于顺序否定环视(?!Expression)
来说,当子表达式Expression
匹配成功时,(?!Expression)
匹配失败;当子表达式Expression
匹配失败时,(?!Expression)
匹配成功,并报告(?!Expression)
匹配当前位置成功。
顺序肯定环视的例子已在《NFA引擎匹配原理》中讲解过了,请移步参考。
(二)顺序否定环视匹配过程源字符串:aa
one
bb正则表达式:]+>
这个正则的意义就是匹配除
或
之外的其余标签。/? 表示匹配正斜杠0
次或 1
次;\b 表示匹配字符边界。
首先由表达式的字符 。
对于逆序肯定环视 (?(? 匹配失败;当子表达式
Expression
匹配失败时,(?匹配成功,并报告
(?匹配当前位置成功;
顺序环视相当于在当前位置右侧附加一个条件,所以它的匹配尝试是从当前位置开始的,然后向右尝试匹配,直到某一位置使得匹配成功或失败为止。而逆序环视的特殊处在于,它相当于在当前位置左侧附加一个条件,所以它不是在当前位置开始尝试匹配的,而是从当前位置左侧某一位置开始,匹配到当前位置为止,报告匹配成功或失败。
顺序环视尝试匹配的起点是确定的,就是当前位置,而匹配的终点是不确定的。逆序环视匹配的起点是不确定的,是当前位置左侧某一位置,而匹配的终点是确定的,就是当前位置。
所以顺序环视相对是简单的,而逆序环视相对是复杂的。这也就是为什么大多数语言和工具都提供了对顺序环视的支持,而只有少数语言提供了对逆序环视支持的原因。
JavaScript 中只支持顺序环视,不支持逆序环视。
Java 中虽然顺序环视和逆序环视都支持,但是逆序环视只支持长度确定的表达式,逆序环视中量词只支持“?”,不支持其它长度不定的量词。长度确定时,引擎可以向左查找固定长度的位置作为起点开始尝试匹配,而如果长度不确定时,就要从当前位置向左逐个位置开始尝试匹配,不成功则回溯,再向左侧位置进行尝试匹配,然后重复以上过程,直到匹配成功,或是尝试到位置0处以后,报告匹配失败,处理的复杂度是显而易见的。
目前只有.NET
中支持不确定长度的逆序环视。
源字符串:
正则表达式:(?(1)匹配起始位置不确定,匹配结束位置确定
当前位置是匹配终点,匹配起点在当前位置的左侧,最终的匹配起点是不确定的,初始的匹配起点可以根据逆序表达式的长度来查找。
注:我认为这样的匹配逻辑是错误的,不认可
首先由“(?)”的子表达式“”取得控制权,由位置 0
开始尝匹配,由于“”的长度固定为 3
,所以会从当前位置向左查找 3
个字符,但是由于此时位于位置 0
处,前面没有任何字符,所以直接匹配失败,“”匹配失败,那么整个逆序否定环视表达式“(?)”则匹配成功,所以位置 0
满足逆序否定环视表达式“(?)”,那么控制权就传给了“B”,由“B”从位置 0
开始向右匹配字符,于是“B”就去匹配字符串中的“a”,结果匹配失败,那么第 1
次迭代匹配失败。
正则引擎传动装置向右传动,你可以理解为有个指针的东西向右移动,此时指针来到位置 1
处,由位置 1
处向左查找 3
个字符,但是前面只有 1
个字符 a,所以同样和“”匹配失败,则整个逆序否定环视表达式“(?)”匹配成功,控制权传给“B”,由“B”从位置 1
开始向右匹配字符,于是“B”就去匹配字符串中的“d”,结果匹配失败,那么第 2
次迭代匹配失败。
直到位置 3
处,向左查找到 3
个字符串“abc”,字符数满足条件,此时“(?)”中的子表达式“”获得控制权。“”取得控制权后,由位置 0
处开始向右逐个字符匹配字符串“abc”,既然是逐字符进行匹配的,所以这时会把控制权交给“”中的“B”,由“B”从位置 3
开始向右匹配字符,于是“B”就去匹配字符串中的“”,由“B”从位置 4
开始向右匹配字符,于是“B”就去匹配位置 4
后面的“B”,结果匹配成功。
重复上述的过程直到正则引擎的指针移到字符串结尾才结束迭代匹配。
最后匹配到的“B”,如下所示(高亮部分):
当前位置是匹配起点,逆序环视是从当前位置向左开始匹配的,匹配终点在当前位置的左侧。不少人认为应该是这样的匹配规则,因为更符合逆序的概念。我也支持这个匹配逻辑。
注:需要明确的一点,无论是什么样的正则表达式,都是要从字符串的位置 0 处开始尝试匹配的,这点没有变。
逆序否定环视表达式“(?)B”中的“”先获得控制权,因为匹配从右到左,所以子表达式“”中的“>”会先获得控制权,去匹配字符串当前位置左边的第 1
个字符,不过当前位置是 0
,所以左侧没有字符,固然匹配失败,既然“”匹配失败,那么整个逆序否定环视表达式“(?)B”就匹配成功,也就是说位置 0
是匹配成功的,位置 0
是满足逆序否定环视表达式的,于是控制权交给“B”,由“B”从字符串位置 0
开始向右匹配字符,显然“B”匹配“a”是失败的,因此整个正则表达式的第 1
次迭代匹配失败。
重复上述的过程,直到位置 4
,“”从位置 4
开始向左逐个字符匹配,首先由“>”匹配位置 4
左边的第 1
个字符“”匹配成功,也就是说位置 4
匹配成功,控制权交个了“B”,由“B”从位置 4
开始向右匹配字符,显示“B”与字符“B”匹配成功。
重复上述过程,直到正则引擎的指针移到位置 6
时,“”逐个字符匹配位置 6
左侧的字符,首先“”中的“>”先去匹配位置 6
左边的第 1
个字符“>”,匹配成功;接着“”中的“B”去匹配位置 6
左边的第 2
个字符“B”,也匹配成功;接着“”中的“
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?