读《Mastering Regular Expressions》

      以前一直觉得正则表达式不过是programming中的一个辅助工具而已,但是最近花了几天时间阅读了Jeffrey的《Mastering Regular Expressions》,读完以后才感觉到正则表达式如此的充满魅力,就像Alice从兔子洞看到了神奇的地底世界一样,我也看到了一个神奇的正则表达式的世界。


      说到正则表达式的历史,书中有介绍到,它最初的构想源自20世纪40年代的两位神经学学家,而真正使这个构想成为现实的是若干年后一个叫做Stephen Kleene的数学家,正则在计算机领域的应用首先是出现在UNIX的编译器里。这样看来,正则确实有着很长的发展历史,而正是因为经过了这么多年的发展,才使正则从最初的形态发展成了现在这样一个成熟,拥有各种各样的流派(Flavor)的形态。


      如果要了解正则,那首先就要了解正则的引擎,它是正则的动力系统。正则的引擎最初有两种,DFA和NFA,即确定型有穷自动机和非确定型有穷自动机,看到这两个概念应该会很熟悉,它们曾出现过在我们大学的编译原理里,说起编译原理,以前学的都有点忘了,现在看到很多大牛们都在研究它,我也要好好补补了,毕竟它是program最重要的基础之一。

 

      好了,继续说正则引擎,这两种正则的引擎处理字符序列的方式和能力是有很大的不同的,因此了解好它们的特点和差异就很重要了,另外,前面提到了正则的发展经过了很长的历史,所以演变出各式各样的流派,这样就变得很不统一,于是就产生了一个POSIX标准,用于统一正则的引擎,所以,不严格的说,现在存在三种正则引擎,即DFA,传统型NFA和POSIX NFA。现在的各种语言是采用不同标准的引擎的,因此你需要了解你用的语言采用了哪种引擎,不过现在大多数的语言采用的都是传统型NFA,可以看到下面的列表,摘抄自书里,不是完整的,但包括了大多数语言和工具:


  DFA - egrep(大多数版本), awk(大多数版本), lex, flex, MySQL, Procmail, GNU awk(DFA和NFA混合), GNU grep/egrep(DFA和NFA混合), Tcl(DFA和NFA混合)

  传统型NFA - JAVA, .Net, PHP, Ruby, Perl, Python, GUN Emacs, ed, sed(大多数版本), less, more, vi, grep(大多数版本)

  POSIX NFA - mwak, GNU Emacs(明确指定时使用)


      先来说说DFA,这种引擎是以文本为导向的,意思就是以要检测的文本去匹配正则表达式。

  例如要检测"abc"这个文本,正则表达式是"acb|abc|cba",引擎会拿文本中的"a"去匹配正则表达式,这样就排除了"cba",引擎记录下"acb"和"abc",接着再拿"b"去匹配,这样就把"acb"也排除了,这样就只剩下"abc"了。

  这种引擎的特点是快,因为文本中的同一个字符只检查一次,不会回溯,而且效率和表达式无关,只和要检测的文本有关。但是这种引擎支持的模式很少,它不支持捕获括号,反向引用,环视,非匹配优先量词,这无疑就失去了很多强大的功能。 


      接着说说传统型NFA,这是很多语言使用的引擎,很重要,需要好好了解。一般来说这种引擎速度没有DFA快,但是功能很强大。这种引擎是表达式主导的,意思就是它是拿着表达式去匹配要检测的文本,等于说就是和DFA正好反过来了,因此它就需要回溯,因为它会先拿一种可能的表达式去匹配文本,匹配不了的话就回溯,接着拿另外一种可能的表达式再去匹配,直到匹配成功或者是尝试完所有可能,因此如果不存在匹配的话,可能会花较长的时间。

  还是上面那个例子,首先引擎会拿表达式中"acb"这个分支去匹配文本,匹配不成功,回溯,再拿"abc"这个分支去匹配文本,匹配成功,停止匹配。

  传统型NFA中的回溯是很重要的一个机制,它其实就是先尝试一种可能,如果失败的话就退回前面引擎记录的备用状态,继续尝试,直到尝试成功或者完全的失败,回溯在分支选择中有一些原则:

  (1)就是对于匹配优先的量词(?,*,+,{min,max}),会先尝试最大的匹配可能,因此也有叫它贪婪模式的

  (2)而对于忽略优先的量词(??,*?,+?,{min,max}?),就会尝试最少的匹配

  (3)还有一种是占有优先的量词(?+,++,*+,{min,max}+),即匹配成功的话舍弃引擎的保存的备用状态,样即使后面的没匹配上也不会回溯

  这样,可以看出来,传统型NFA的效率和表达式的书写有着直接的关系,因此想要对这种引擎进行优化,就要在表达式的书写上花一些功夫,比如减少不必要的分支,避免出现指数级的回溯等等。

  传统型NFA中还有一些重要的特性,比如捕获,即根据是否在匹配结束之后还要引用匹配的部分来决定是否要捕获,捕获用的是括号,非捕获用的是(?:);还有环视,即在当前位置观察左边或者是右边是否存在或是不存在某个表达式,观察结束不改变观察位置,也叫零长度,环视一般用:(?=)肯定正序环视,(?!)否定正序环视,(?<=)肯定逆序环视,否定逆序环视(?<!)来表示。

  总之,传统型NFA功能很强大,但需要正确的使用表达式,不然很容易出现错误或是性能问题。


      最后是POSIX NFA,这种引擎和传统型NFA基本一样,唯一不同是这种引擎支持查找最长匹配,即会查找所有分支,找出匹配的目标里最长的出来,因此一般来说会比上面两种引擎慢。


      上面就是一些关于正则引擎的,了解完正则引擎以后就需要了解一下各语言提供的API和实现的具体细节了,因为各种流派之间是存在一些细节差异的,比如支持的元字符不一样,对各种编码的支持有差别等等,就像在JAVA中"\d","\w","\s"等字符组是只支持ASCII的,如果用它们去匹配UNICODE编码的字符就会出现错误。还有在语法上各语言也是有差异的,像javascript我感觉用起来就有点别扭,比如我想匹配一段文本中的所有某个字符串,每次想用起来总是忘记该则么用,索性记录一下吧:

  var regex = /xx/g;

  var str = xx;

  while(regex.test(str)){

    alert(RegExp.$x);

  }


      啰哩八嗦总结了这么一些,只是个人的学习总结,仅供参考

转载于:https://www.cnblogs.com/EvanHuang/archive/2010/12/26/1917439.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值