阅读 12

快速掌握正则表达式

关键词 正则 状态机 环视匹配 等价转换 限定符 内联模式

一.DFA引擎 or NFA引擎

不同类型的正则引擎对同样的正则表达式执行的结果是不同的。搞清楚引擎是必要的。

概念

DFA(Deterministic Finite Automaton)为确定性有限自动机,NFA(Non-deterministic Finite Automaton)为非确定性有限自动机。

  • DFA 引擎在线性时状态下执行,因为它们不要求回溯(并因此它们永远不测试相同的字符两次)
  • NFA包含回溯算法也叫试探法,从一条路往前走,能进则进,不能进则退回来,换一条路再试。回溯算法说白了就是穷举法。因此,在最坏情况下,它的执行速度可能非常慢。

可以用如下的正则表达式测试当前编程语言采用的引擎是否 NFA:nfa|nfa not

js:"nfa,nfa not".match(/nfa|nfa not/)  结果 ["nfa", index: 0, input: "nfa,nfa not", groups: undefined]
js:/nfa|nfa not/.test('nfa')  结果 true 
复制代码

用上面的正则表达式来测试字符串 nfa not,NFA 引擎在检测满足 nfa 就返回匹配成功的结果了,而 DFA 则会尝试继续查找,也就是说会得到“最长的匹配结果”。

知识点:自动机、自动机

如/\d\w+/这个正则生成的状态机图:

关于正则引擎如何工作这里有一个不错的说明,先说明了DFA工作的方法,然后又引出了“回溯”

正则引擎语言分类

DFA类型 语言
DFA awk (大多数版本)、egrep(大多数版本)、flex、lex、MySQL、Procmail
传统型NFA GNU Emacs、Java、grep(大多数版本)、less、more、.NET语言、PCRE library、Perl、PHP(所有三套正则库)、Python、Ruby、sed(大多数版本)、vi ,JavaScript
POSIX NFA mawk、Mortice Kern Systems’ utilities、GNU Emacs (明确指定时使用)
DFA/NFA 混合 GNU awk、GNU grep/egrep、Tcl

不同的语言对于正则表达式符号的支持情况也是不同的。

二.正则表达式

常用符号

简洁版

  • ?表示匹配0个或1个
  • +表示匹配1-无穷
  • *表示匹配0-无穷
  • .表示除\n之外的任意字符
  • ^为匹配输入字符串的开始位置
  • $匹配结束位置
  • \d就是[0-9]。表示是一位数字。记忆方式:其英文是digit(数字)。
  • \D就是[^0-9]。表示除数字外的任意字符。
  • \w就是[0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。
  • \W是[^0-9a-zA-Z_]。非单词字符。
  • \s是[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。
  • \S是[^ \t\v\n\r\f]。 非空白符。.就是[^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。记忆方式:想想省略号...中的每个点,都可以理解成占位符,表示任何类似的东西。

wsb 和 WSB 是相反的

等价 \w = [A-Za-z0-9_], 展开看好理解

分解: 无法一步就完成的需求可以分解,如获取网页中的图片地址, 先获取img标签,然后再从标签中获取src值

特殊字符

参考版

特别字符 描述
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则$ 也匹配 '\\n' 或 '\\r'。要匹配 $ 字符本身,请使用 \\$
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。
[ 标记一个中括号表达式的开始。要匹配 [,请使用 \[。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。
{ 标记限定符表达式的开始。要匹配 {,请使用 \{。
| 指明两项之间的一个选择。要匹配 |,请使用 \|。

限定符

限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。

正则表达式组成

正则表达式可以包含子表达式、小括号(子表达式部分)、中括号(主干部分)、大括号(限定部分)。这些括号组合是比较灵活的,可以相互包含(但大括号能不能包含其他括号)。

源字符串:<div>a test</div>
正则表达式:(?<=<div>)[^<]+(?=</div>)
复制代码

环视匹配

Lookaround, 也叫零宽断言

  • 环视只进行子表达式的匹配,不占有字符,匹配到的内容不保存到最终的匹配结果,是零宽度的。环视匹配的最终结果就是一个位置(确定位置的过程)。
  • 环视的作用相当于对所在位置加了一个附加条件,只有满足这个条件,环视子表达式才能匹配成功。

示例1:

金额匹配: `(^0\.[0-9]{1,2}$)|(^[1-9]\d+\.[0-9]{1,2}$)|(^[1-9]\d+$)`
金额匹配(环视匹配): `(?!^0\d+|\D*0$)^[0-9]{1,7}(\.[0-9]{1,2})?$`
复制代码

这里常规金额匹配由三个子表达式组成,要排除整数部分为0开头的情况,整个过程比较冗长。换成环视匹配后就简单多了,环视匹配表达式的含义为 匹配除0开头的整数部分和非数字,整数部分长度1-7位。

优化金额 前:/^\d+(\.\d{1,2})?$/ 问题:12.校验通过; 优化后:/^\d+[.]\d{1,2}$|^\d+$/ 有“.” 小数部分必填。

示例2:

源字符串:aa<p>one</p>bb<div>two</div>cc
正则表达式:<(?!/?p\b)[^>]+>
复制代码

这个正则的意义就是匹配除了<p···>或

之外的所有标签 按图所示,这个正则中<就匹配它本身,(?!/?p\b)是一个顺序否定表达式,子表达式是</?p\b,意思是这个表达式的右边不能是字符【/p】或者【p】,问号是代表匹配一次或者不匹配,大家应该还记得。然后[^>]+中的[^···]是排除型字符组,表示的是除【>】以外的字符都可以匹配,匹配的数量是最少一次,最多不限制。最后一个表达式>的意义是匹配它本身。 上面整个表达式的含义就是:匹配【<(<右边不能是p或/p)(除>的字符N个)>】这样一个文本。由此例子,我们也可以看出,表达式通常可以拆分成一个个的子表达式,最后把它们连起来形成一个完整的表达式。

正序否定环视匹配

(?=Expression)
顺序肯定环视,表示所在位置右侧能够匹配Expression
(?!Expression)
顺序否定环视,表示所在位置右侧不能匹配Expression
复制代码

逆序环视匹配

(?<=Expression)
逆序肯定环视,表示所在位置左侧能够匹配Expression
(?<!Expression)
逆序否定环视,表示所在位置左侧不能匹配Expression
复制代码

开头都是 "?"

都包含 “=”或“!”; 肯定否定。(都包含肯定”=”或 否定“!”)

逆序包含"<"

全局匹配模式和内联匹配模式

内联匹配模式特征:正则前面的(?i)(?s)(?m)(?is)(?im)

称为内联匹配模式,通常用内联匹配模式代替使用枚举值RegexOptions指定的全局匹配模式,写起来更简洁。

  • (?i)表示所在位置右侧的表达式开启忽略大小写模式
  • (?s)表示所在位置右侧的表达式开启单行模式。
  • 更改句点字符(.)的含义,以使它与每个字符(而不是除\n之外的所有字符)匹配。
  • 注意:(?s)通常在匹配有换行的文本时使用
  • (?m)表示所在位置右侧的表示式开启指定多行模式。
  • 更改^和$的含义,以使它们分别与任何行的开头和结尾匹配,
  • 而不只是与整个字符串的开头和结尾匹配。
  • 注意:(?m)只有在正则表达式中涉及到多行的“^”和“$”的匹配时,才使用Multiline模式。
  • 上面的匹配模式可以组合使用,比如(?is),(?im)。
  • 另外,还可以用(?i:exp)或者(?i)exp(?-i)来指定匹配的有效范围。

常用正则表达式练习

- 金额校验包含最小最大值 ` ^0(?!\d+$)\.[0-9]?[1-9]{1}$|^2[0]{5}$|(?!^[0,2-9]\d+|\D*0$)^[0-9]{1,6}(\.[0-9]{1,2})?$`
- 手机号` ^1[3|5|8|6|9]\d{9}$ `  
- 身份证` err:\d{15}$|\d{17}[\d{1}|X|x]$; right:^\d{17}[\d|X|x]{1}$|^\d{15}$`
- 一般信息填写:` ^([^\x00-\xff]|[A-Za-z0-9_-]|[()()]|[\\s])*$ `; //assic0-255除数字、大小写字母、以外的字符、这个正则杀马特字符是可以输入的
- 中文\英文\数字\下划线 `^([\u4e00-\u9fa5]|[A-Za-z0-9_-])*$`  
- 各种电话号码匹配,允许格式: 13988888888、010-88888888、010-88888888-88(转接号)、400888888:`(^1[3|4|5|8|6|9]\d{9}$)|(^\d{3,4}-\d{7,8}(-\d{1,3})?$)|(^400\d{6}$)`
- 日期  `^[1,2]+[0-9]{3}-(0[1-9]|1[0,1,2])-(0[1-9]|([1,2][0-9]{1})|3[0,1]{1})$`
- 大于0的数 `/^[0-9]\d*(\.\d+)$/` => `/^[0-9]\d*(\.\d+)$/.test('1.0')`

1.验证用户名和密码:("^[a-zA-Z]\w{5,15}$")正确格式:"[A-Z][a-z]_[0-9]"组成,并且第一个字必须为字母6~16位;
2.验证电话号码:("^(\d{3,4}-)\d{7,8}$")正确格式:xxx/xxxx-xxxxxxx/xxxxxxxx;
3.验证身份证号(15位或18位数字):("^\d{15}|\d{18}$");
4.验证Email地址:("^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
5.只能输入由数字和26个英文字母组成的字符串:("^[A-Za-z0-9]+$") ;
6.整数或者小数:^[0-9]+\.{0,1}[0-9]{0,2}$
7.只能输入数字:"^[0-9]*$"。
8.只能输入n位的数字:"^\d{n}$"。
9.只能输入至少n位的数字:"^\d{n,}$"。
10.只能输入m~n位的数字:。"^\d{m,n}$"
11.只能输入零和非零开头的数字:"^(0|[1-9][0-9]*)$"。
12.只能输入有两位小数的正实数:"^[0-9]+(.[0-9]{2})?$"。 => "^[0-9]+(\.[0-9]{2})?$"
13.只能输入有1~3位小数的正实数:"^[0-9]+(.[0-9]{1,3})?$"。=> "^[0-9]+(\.[0-9]{1,3})?$"。
14.只能输入非零的正整数:"^\+?[1-9][0-9]*$"。
15.只能输入非零的负整数:"^\-[1-9][]0-9"*$。
16.只能输入长度为3的字符:"^.{3}$"。
17.只能输入由26个英文字母组成的字符串:"^[A-Za-z]+$"。
18.只能输入由26个大写英文字母组成的字符串:"^[A-Z]+$"。
19.只能输入由26个小写英文字母组成的字符串:"^[a-z]+$"。
20.验证是否含有^%&',;=?$\"等字符:"[^%&',;=?$\x22]+"。
21.只能输入汉字:"^[\u4e00-\u9fa5]{0,}$"
22.验证URL:"^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$"。
23.验证一年的12个月:"^(0?[1-9]|1[0-2])$"正确格式为:"01"~"09"和"1"~"12"。
24.验证一个月的31天:"^((0?[1-9])|((1|2)[0-9])|30|31)$"正确格式为;"01"~"09"和"1"~"31"。
25.获取日期正则表达式:\d{4}[年|\-|\.]\d{\1-\12}[月|\-|\.]\d{\1-\31}日?
评注:可用来匹配大多数年月日信息。
26.匹配双字节字符(包括汉字在内):[^\x00-\xff]
评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)
27.匹配空白行的正则表达式:\n\s*\r
评注:可以用来删除空白行
28.匹配HTML标记的正则表达式:<(\S*?)[^>]*>.*?</>|<.*? />
网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力

var regexp =/^([\u4e00-\u9fa5]|[A-Za-z0-9_-])*$/g;
if(!regexp.test(content.trim())){
  // 不符合正则
}

- .*匹配除 \n 以外的任何字符。
- /[\u4E00-\u9FA5]/ 汉字
- /[\uFF00-\uFFFF]/ 全角符号
- /[\u0000-\u00FF]/ 半角符号
- Java不支持 ?、+、|、(),那应该怎么实现呢? 首先要知道这件事,之后在java平台写正则时要看java的文档
复制代码

基础手册

记住常用符号是必要的,长时间不用忘记也很正常,查询手册在这里

See Also

关注下面的标签,发现更多相似文章
评论