1.历史:正则表达式
引用算法
正则表达式萌芽于1940年代的神经生理学研究,由著名数学家Stephen Kleene第一个正式描述。具体地说,Kleene概括了前述的神经生理学研究,在一篇题为《正则集代数》的论文中定义了“正则集”,并在其上定义了一个代数系统,而且引入了一种记号系统来描述正则集,这种记号系统被他称为“正则表达式”。在理论数学的圈子里被研究了几十年以后,1968年,后来发明了UNIX系统的Ken Thompson第一个把正则表达式用于计算机领域,开发了qed和grep两个实用文本处理工具,取得了巨大成功。在此后十几年里,一大批一流计算机科学家和黑客对正则表达式进行了密集的研究和实践。在1980年代早期,UNIX运动的两个中心贝尔实验室和加州大学伯克利分校分别围绕grep工具对正则表达式引擎进行了研究和实现。与之同时,编译器“龙书”的做者Alfred Aho开发了Egrep工具,大大扩展和加强了正则表达式的功能。此后,他又与《C程序设计语言》的做者Brian Kernighan等三人一块儿发明了流行的awk文本编辑语言。到了1986年,正则表达式迎来了一次飞跃。先是C语言顶级黑客Henry Spencer以源代码形式发布了一个用C语言写成的正则表达式程序库(当时还不叫open source),从而把正则表达式的奥妙带入寻常百姓家,而后是技术怪杰Larry Wall横空出世,发布了Perl语言的第一个版本。自那之后,Perl一直是正则表达式的旗手,能够说,今天正则表达式的标准和地位是由Perl塑造的。Perl 5.x发布之后,正则表达式进入了稳定成熟期,其强大能力已经征服了几乎全部主流语言平台,成为每一个专业开发者都必须掌握的基本工具。less
2.DFA和NFA工具
引用性能
理解DFA和NFA
正则表达式引擎分红两类,一类称为DFA(肯定性有穷自动机),另外一类称为NFA(非肯定性有穷自动机)。两类引擎要顺利工做,都必须有一个正则式和一个文本串,一个捏在手里,一个吃下去。DFA捏着文本串去比较正则式,看到一个子正则式,就把可能的匹配串全标注出来,而后再看正则式的下一个部分,根据新的匹配结果更新标注。而NFA是捏着正则式去比文本,吃掉一个字符,就把它跟正则式比较,匹配就记下来:“某年某月某日在某处匹配上了!”,而后接着往下干。一旦不匹配,就把刚吃的这个字符吐出来,一个个的吐,直到回到上一次匹配的地方。
DFA与NFA机制上的不一样带来5个影响:
1. DFA对于文本串里的每个字符只需扫描一次,比较快,但特性较少;NFA要翻来覆去吃字符、吐字符,速度慢,可是特性丰富,因此反而应用普遍,当今主要的正则表达式引擎,如Perl、Ruby、Python的re模块、Java和.NET的regex库,都是NFA的。
2. 只有NFA才支持lazy和backreference等特性;
3. NFA急于邀功请赏,因此最左子正则式优先匹配成功,所以偶尔会错过最佳匹配结果;DFA则是“最长的左子正则式优先匹配成功”。
4. NFA缺省采用greedy量词(见item 4);
5. NFA可能会陷入递归调用的陷阱而表现得性能极差。
我这里举一个例子来讲明第3个影响。
例如用正则式/perl|perlman/来匹配文本 ‘perlman book’。若是是NFA,则以正则式为导向,手里捏着正则式,眼睛看着文本,一个字符一个字符的吃,吃完 ‘perl’ 之后,跟第一个子正则式/perl/已经匹配上了,因而记录在案,往下再看,吃进一个 ‘m’,这下糟了,跟子式/perl/不匹配了,因而把m吐出来,向上汇报说成功匹配 ‘perl’,再也不关心其余,也不尝试后面那个子正则式/perlman/,天然也就看不到那个更好的答案了。
若是是DFA,它是以文本为导向,手里捏着文本,眼睛看着正则式,一口一口的吃。吃到/p/,就在手里的 ‘p’ 上打一个钩,记上一笔,说这个字符已经匹配上了,而后往下吃。当看到 /perl/ 以后,DFA不会停,会尝试再吃一口。这时候,第一个子正则式已经山穷水尽了,没得吃了,因而就甩掉它,去吃第二个子正则式的/m/。这一吃好了,由于又匹配上了,因而接着往下吃。直到把正则式吃完,心满意足往上报告说成功匹配了 ‘perlman’。
由此可知,要让NFA正确工做,应该使用 /perlman|perl/ 模式。
经过以上例子,能够理解为何NFA是最左子式匹配,而DFA是最长左子式匹配。实际上,若是仔细分析,关于NFA和DFA的不一样之处,均可以找出道理。而明白这些道理,对于有效应用正则表达式是很是有意义的。flex
写道设计
正则表达式的形式定义故意很是精简,避免定义多余的量词 ? 和 +,它们能够被表达为: a+ = aa* 和 a? = (a|ε)。有时增长补算子 ~ ;~R 指示在 Σ* 上的不在 R 中的全部字符串的集合。补算子是多余的,由于它使用其余算子来表达(尽管计算这种表示的过程是复杂的,而结果可能指数性的增大)。
这种意义上的正则表达式能够表达正则语言,精确的是可被有限状态自动机接受的语言类。可是在简洁性上有重要区别。某类正则语言只能用大小指数增加的自动机来描述,而要求的正则表达式的长度只线性的增加。正则表达式对应于乔姆斯基层级的类型-3文法。在另外一方面,在正则表达式和不致使这种大小上的爆炸的非肯定有限状态自动机(NFA)之间有简单的映射;为此 NFA 常常被用做正则表达式的替表明示。
咱们还要在这种形式化中研究表达力。以下面例子所展现的,不一样的正则表达式能够表达一样的语言: 这种形式化中存在着冗余。
有可能对两个给定正则表达式写一个算法来断定它们所描述的语言是否本质上相等,简约每一个表达式到极小肯定有限自动机,肯定它们是否同构(等价)。
这种冗余能够消减到什么程度? 咱们能够找到仍有彻底表达力的正则表达式的有趣的子集吗? Kleene 星号和并集明显是须要的,可是咱们或许能够限制它们的使用。这提出了一个使人惊奇的困难问题。由于正则表达式如此简单,没有办法在语法上把它重写成某种规范形式。过去公理化的缺少致使了星号高度问题。最近 Dexter Kozen 用克莱尼代数公理化了正则表达式。
不少现实世界的“正则表达式”引擎实现了不能用正则表达式代数表达的特征。递归
目前正则引擎支持的语言种类:开发
引擎类型 | 程序 |
DFA | awk(大多数版本)、egrep(大多数版本)、flex、lex、MySQL、Procmail |
传统型 NFA | GNU Emacs、Java、grep(大多数版本)、less、more、.NET语言、PCRE library、Perl、PHP(全部三套正则库)、Python、Ruby、set(大多数版本)、vi |
POSIX NFA | mawk、Mortice Lern System's utilities、GUN Emacs(明确指定时使用) |
DFA/NFA混合 | GNU awk、 GNU grep/egrep、 Tcl |
引伸阅读:字符串
为何输入这种正则会致使假死?http://rdc.taobao.com/team/jm/archives/2432