深入理解正则表达式

Jackey PHP 3,960 次浏览 , 没有评论

正则表达式基础

  • 完整的正则表达式是由两种字符组成。特殊字符(元字符)和普通字符(文本)
  • 示例:/^\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函数分别用单引号和双引号的表达式来匹配上面的\$,怎么写?
答案:
表达式需要的正则是\\\$
用单引号表示上面的字符串\\\\\$
用双引号表示上面的字符串\\\\\\\$
  1. <?php
  2. $uids = '33333\$333\3333';
  3. $uids = preg_match_all("/\\\\\\\$/", $uids, $matchs);
  4. // 双引号中 \\ 转义输出一个 \, \$ 转义输出 $
  5. $uids = preg_match_all('/\\\\\$/', $uids, $matchs);
  6. // \s单引号内不会转义,输出 \$,\\会转义,输出一个 \
  7. print_r($matchs);

多选结构

  • 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的倍数
答案:
  1. $string = '5345678986';
  2. $pattern = '/(?<=\d)(?=(\d{3})+$)/';
  3. $replacement = ',';
  4. echo preg_replace($pattern, $replacement, $string);

贪婪与懒惰(匹配优先与忽略优先)

  • 当正则表达式中包含能接受重复的量词(制定数量的代码,例如+,*,{3,12}等)时,通常的行为是匹配尽可能多的字符。
  • 表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,他会匹配整个字符串aabab。这被称为贪婪匹配。
  • 我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的量词都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就以为着匹配任意数量的重复,但是能使整个匹配成功的前提下使用最少的重复。
  • a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab。
示例:
  1. <?php
  2. $string = 'aaabaab';
  3. $pattern1 = '/a.{1,10}b/';
  4. $pattern2 = '/a.{1,10}?b/';
  5. preg_match_all($pattern1, $string, $matchs1);
  6. preg_match_all($pattern2, $string, $matchs2);
  7. var_dump($matchs1);
  8. var_dump($matchs2);

正则的引擎

正则引擎主要可以分为两大类:一种是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
  • 忽略优先还是匹配有限,具体情况具体分析
    同样情况下,匹配优先速度更快

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Go