《精通正则表达式》笔记 --- 选择引号内的文字
这个例子出自《精通正则表达式》,做一下笔记帮助理解和记忆。
第一版
最简单的case就是考虑包含一对引号,那么写出来的表达式应该是这样的:
".*"
但是这个未免太简单了吧,会有啥问题呢?假如输入的字符串长这样结果就会出问题拉。see...
Input String: "Hello" and "World"
Regex: ".*"
Match: "Hello" and "World"
为什么会全部匹配到呢?这是因为 * 是一个greedy(匹配优先)的量词,我觉得英文的意思更容易帮助我们理解。这意味着它会首先会'贪婪'得把所有的字符匹配完,匹配到最后一个字符发现没有字符可以匹配了,于是开始匹配下一个引号,它会首先回朔到最后一个引号前,然后开始匹配引号匹配引号,发现可以匹配,然后就完成了。这就是为啥整个字符串都被匹配了。
第二版
既然是因为greedy的量词导致的这个问题,那我们将其给成lazy(忽略优先)的量词 ----- *? 。当一个量词是忽略优先的话,那在匹配的时候,引擎会选择忽略这个忽略优先量词修饰的字符去匹配下一个字符,如果匹配则继续,如果不匹配则返回来匹配这个忽略优先量词修饰的字符。
看一下这个例子,将表达式稍作修改,匹配结果就变鸟。
Input String: "Hello" and "World"
Regex: ".*?"
Match-1: "Hello"
Match-2: "World"
好了,似乎这个版本已经圆满完成我们的任务啦。
第三版
那我们将需求继续变化一下,在程序的世界里有一种东西叫做转移字符,比如有个字符串长这样子\"Hello, World!\"+\"
。 直接上最终正则表达式。
Input String: \"Hello, World!\"+\"
Regex: "(\\.|[^\\"])*"
Match-1: No Match
正则表达式中关键的是这个(\\.|[^\\"])
,这个括号在正则表达式里是起到一个多选结构的作用,表示匹配括号内任意一个子表达式即匹配。这个括号里面由两部分,第一部分是\\.
,第二部分是[^\\"]
。
\\.
\\.
能够匹配任何的转义字符,严格来说是匹配任意\以及其后面的字符,即使它们不是真正的转义字符。
[^\"]
这个输入字符串最后一个引号是转移的引号,所以这个正则表达式没有匹配到任何的结果。这个结果是对的,这个功劳就要归结到这个[^\\"]
,它的意思是匹配非\"的任意字符。当引擎匹配到字符串最后发现"
还未匹配,于是进行回溯,回溯到▴那个位置\"Hello, World!\"+ ▴ \"
。引擎会尝试去匹配[^\\"]
,发现匹配失败,于是失败!
假如我们将[^\\"]
换成[^"]
,那么在刚才那个位置的时候,[^"]
就会匹配\
字符,然后\
之后的"
会被外层的引号匹配,于是它的结果就是被匹配了。这显然是很奇怪的,当然如果你想要这么匹配也可以。
其他版本
还有一种方式是利用正则表达式里面的固化分组(Atomic grouping)或者占有优先量词(Possessive)。固化分组:"(?>(\\.|[^"])*)
,占有优先量词:"(\\.|[^"])*+"
。在固化分组或者占有优先量词的匹配过程中,在固化分组内或者占有优先量词修饰的组内,如果一个字符已经被匹配那么之前的状态将会被舍弃,就防止它进行回溯。
在这个例子中,\"Hello, World!\"+\"
,当匹配到这个位置的时候(\"Hello, World!\"+\" ▴
)发现没有办法继续匹配下去了,引擎会选择直接匹配失败,而不是回溯到之前的备用状态进行新的匹配。
关键的概念
例子虽然简单,但是出现了正则表达式中很多关键的概念,预知详情,请阅读《精通正则表达式》
- 匹配优先量词(Greedy quantifier)
- 忽略优先量词(Lazy quantifier)
- 占有优先量词(Possessive quantifier)
- 固化分组(Atomic grouping)
- 字符组(Character Classes)
- 多选结构(Alternation)
本文纯属个人读书笔记,如有错误概不负责哟~~~