正则表达式基础
- 完整的正则表达式是由两种字符组成。特殊字符(元字符)和普通字符(文本)
- 示例:/^\d+hello.*/
- ^\d + .* 都是元字符
- hello是文本字符
元字符
常见的元字符
- . 匹配除换行符意外的任意字符
- \w 匹配字母或数字
- \s 匹配任意的空白字符
- \d 匹配数字
- \b 匹配单词的开始或结束
- ^ 匹配字符串的开始
- $ 匹配字符串的结束
- \xxx 查找以八进制数xxx规定的字符
- \xdd 查找以十六进制数dd规定的字符
- \uxxxx 查找以十六进制数 xxxx规定的Unicode字符
字符转义
- tesh\.php匹配tesh.php
- c:\\windows匹配c:\windows
- 2\^8匹配2^8(通常这是2的8次方的书写方式)。
思考
333333\$33\33333
要匹配上面字符串的“\$”正则应该怎么写?
如果在PHP中preg_match函数分别用单引号和双引号的表达式来匹配上面的\$,怎么写?
答案:
表达式需要的正则是\\\$
用单引号表示上面的字符串\\\\\$
用双引号表示上面的字符串\\\\\\\$
- <?php
- $uids = '33333\$333\3333';
- // 双引号中 \\ 转义输出一个 \, \$ 转义输出 $
- // \s单引号内不会转义,输出 \$,\\会转义,输出一个 \
多选结构
- Windows98|Windows2000|WindowsXP
- ^Windows98|Windows2000|WindowsXP$
- Windows(98|2000|XP)
上面三个正则表达的含义一样么?
多选结构可以包含很多字符,但不能超越括号的界限。
分组
我们已经了解怎么重复单个字符;
但如果想要重复一个字符串该怎么办?你可以用小括号来指定子表达式(也叫做分组)。
(\d{1,3}\.){3}\d{1,3} 简单的IP地址匹配表达式
但是它也将匹配256.300.888.999这种不可能存在的IP地址。你能写一个更准确的正则?
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
后向引用
- 使用小括号指定一个子表达式(分组)后,匹配这个子表达式的文本可以被捕获,从而在表达式或其他程序中作进一步的处理。
- 默认情况下,每个分组会自动拥有一个租号,规则是:以分组的左括号为标志,从左向右,第一个分组的标号为1,第二个为2,以此类推。
示例:
\b(\w+)\b\s+\1\b 可以用来匹配重复的单词
匹配诸如:where where go, tom tom happy
环视(零宽断言)
- 环视不匹配任何字符,只匹配文本中的特定位置。类似于\b,^,$那样。环视不会占用字符。
- 环视分为顺序和逆序两种
顺序(?=exp)位置的后面能配置exp。例如:(?=\d)当前位置右边是数字。(?!exp)位置的后面不能匹配exp。例如:(?!\d)当前位置右边不能是数字。逆序(?<=exp)位置的前面能匹配exp。例如:(?<=\d)当前位置左边是数字。(?<!=exp)位置的前面不能匹配exp。例如:(?!\d)当前位置左边不是数字。
环视示例:
(?=jeffrey)jeff
jeff(?=rey)
上面两个表达式是等价的么?
答案:是完全等价的。
环视为数值添加逗号
5345678986
5,345,678,986
分析:
我们需要找到一个位置,并将‘位置’替换为‘,’。
这个位置需要符合下面几个条件。
①左边必须有数字
②右边的数字是3的倍数
答案:
- $string = '5345678986';
- $pattern = '/(?<=\d)(?=(\d{3})+$)/';
- $replacement = ',';
贪婪与懒惰(匹配优先与忽略优先)
- 当正则表达式中包含能接受重复的量词(制定数量的代码,例如+,*,{3,12}等)时,通常的行为是匹配尽可能多的字符。
- 表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,他会匹配整个字符串aabab。这被称为贪婪匹配。
- 我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的量词都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就以为着匹配任意数量的重复,但是能使整个匹配成功的前提下使用最少的重复。
- a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab。
示例:
- <?php
- $string = 'aaabaab';
- $pattern1 = '/a.{1,10}b/';
- $pattern2 = '/a.{1,10}?b/';
正则的引擎
正则引擎主要可以分为两大类:一种是DFA,一种是NFA。
- NFA表达式主导(以表达式为主,顺序去匹配文字)
- DFA文本主导(以文字稳住,顺序去匹配表达式)
- 解释正则表达式/pe(nciil|mcil|ncil)/尝试匹配“this is pencil”两种引擎的不同。
一般而论,DFA引擎则搜索更快一些。但是NFA以表达式为主导,更容易操纵,因此一般程序员更偏爱NFA引擎!
如何判断当前程序使用的引擎?
是否支持忽略优先量词和分组捕获?
支持 NFA
不支持 DFA
使用DFA引擎的程序主要有:awk,egrep,flex,lex,mysql,procmail等;
使用传统型NFA引擎的程序主要有:
GNU Emacs,java,ergp,less,more,.NET,PCRE library,perl,php,python,ruby,sed,vi;
回溯
- NFA引擎,最重要的性质就是它依次处理各个自表达式,遇到需要在两个能成功的可能中进行选择的时候,他会选择其一同时记住另外一个,以备可能得需要。
- 回溯就像是在道路的每个分岔路口留下一小堆面包屑。
- 如果需要再“进行尝试”和“跳过尝试”之间选择,对于匹配优先量词,引擎会优先选择“进行尝试”,而对于忽略优先量词,会选择“跳过尝试”。
- 距离当前最近存储的选项就是当本地失败强制回溯时返回的。使用的原则是LIFO。
- 回溯时NFA的灵魂。
下面匹配的结果是?
".*"与".*?"
分别匹配
This names "Mcdonald's" is said "makudonarudo" in Japanese.
回溯示例
a.*t 匹配 abcdefghijklmnopqrst
a.*?t 匹配 abcdefghijklmnopqrst
测试两者的效率(避免回溯,提高表达式的效率)
PHP常用的模式修饰符
- i 大小写不敏感匹配
- m 增强的行锚点模式
- x 空白和#注释将被忽略
- s 点号元字符匹配所有字符,包含换行符
- e 将替换后的字符串作为php代码评估制定
下面的写法正确么?
preg_match_all('!a.{1,10}b!', 'aaabaab', $matchs);
preg_match_all('+a.{1,10}b+', 'aaabaab', $matchs);
答案:正确
PCRE的分隔符
PCRE需要由分隔符闭合包裹。
分隔符可以使任意非字母数字、非反斜线、非空白字符。
- /foo bar/
- #^[^0-9]$#
- +php+
- %[a-zA-Z0-9_-]%
PCRE的主要函数
- preg_grep 发挥匹配模式的数组条目
- preg_match 执行一个正则表达式匹配
- preg_match_all 执行一个全局正则表达式匹配
- preg_filter 执行一个正则表达式搜索和替换
- preg_replace 执行一个正则表达式的搜索和替换
- preg_replace_callback 执行搜索并且使用一个回调进行替换
- preg_split 通过一个正则表达式分割字符串
- preg_quote 转义正则表达式字符
- preg_last_error 返回最后一个PCRE正则执行产生的错误代码
表达式的优化
- 化简量词
.*与(?:.)*在逻辑上是相等的,但是前者速度要更快,引擎内部做了优化。
- 尽量使用非捕获的括号(非捕获的:(?:))
- 消除不必要的字符组
[.]与\.在逻辑上是相等的,但是后者更快
- 字符组优于多选结构
[abc]优于a|b|c
- 忽略优先还是匹配有限,具体情况具体分析
同样情况下,匹配优先速度更快