IT技术博客大学习 共学习 共进步

[正则优化] CSS属性选择符的匹配

Miller 2011-09-04 23:33:04 浏览 3,581 次

正则表达式如下

\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]

这个正则表达式的作用是用来匹配CSS属性选择符,例如: 

/*能匹配成功的*/
[class='abcdefg-abcdefg']
  
/*匹配失败的*/
[class='abcdefg-abcdefg'

成功匹配的过程如下:

2010-11-06_134439

短短的文本却需要匹配451步,效率是比较低的,总结一下大概有以下几个问题:

1. ((?:[\w\u00c0-\uFFFF-]|\\.)+) 这一部分表达式是用来匹配属性名称的,+号本身是贪婪匹配的,但是限定在一个分组之后就变成循环了,括号中的表达式会一次次的循环执行,因此效率也就比较低了。

2. (\S?=)部分是用来匹配属性关系符的,例如"="、"~="。当匹配的属性关系符的长度是2时,该表达式并没有问题,\S?能够成功的匹配第一个关系符,例如"~="中的"~"。但是在匹配"="这样只有一个字符的关系符时,\S?= 首先会用\S?来匹配"=",而且这个匹配是成功的,之后会再用表达式中的等号继续匹配,但匹配时失败的,这时候需要增加一次回溯来保证匹配成功,因此这里的回溯也是可以优化的。

3. 在匹配属性值时,使用了一个懒惰型的表达式,即(.*?),这也是按字符循环,速度较慢,可以进行优化。

根据以上的问题相应的做出了以下的改进:

1. ((?:[\w\u00c0-\uFFFF-]|\\.)+)  这一部分之所以要使用分组是因为属性名中可以包含\.这样的字符,在此可以将它视为特殊字符,因此使用(open normal(special normal+)* close)的格式对这部分进行重构,结果为:[\w\u00c0-\uFFFF-]*(\\.[\w\u00c0-\uFFFF-]*)*,这可以以最大的步伐匹配字符。

2. (\S?=)部分需要排除\S?对"="进行匹配的可能性,因此修改为 [^\s=]?

3. (['"]*)(.*?)\3|)匹配属性值的时候之所以使用懒惰匹配是因为使用了反向引用,这里可以使用分组来替换。

根据以上几点做出的修改后,结果如下:

\[\s*((?:[\w\u00c0-\uFFFF-]*(?:\\.[\w\u00c0-\uFFFF-]*)*))\s*(?:([^\s=]?=)
\s*('[^'\s\]]*'|"[^"\s\]]*"|[^'"\s\]]*))\s*\]

使用优化后的表达式匹配上文相同的文本得到的结果如下:

2010-11-06_174807

由原来的451步缩减为21步,是一个相当大的提升,这里匹配属性名和属性值时都是最大步伐的消耗掉文本,而且匹配关系符时不再有回溯。

而实际上浏览器的测试结果也验证了这一点:http://jsperf.com/regexp-test-1

结果显示,在匹配成功的情况下,IE、Firefox的性能提升在1倍以上,Chrome下也超过50%。

而在匹配失败的情况下,优化后的表达式也是回溯次数最少的,性能提升在以上case中也有体现,IE和Firefox下的提升高达4-5倍,Chrome下提升2倍左右。

建议继续学习

  1. 字符串匹配那些事(一) (阅读 7,104)
  2. grep 正则表达式选项要记得转义 (阅读 6,444)
  3. 统计最近用过的linux命令 (阅读 6,404)
  4. 正则表达式基础 (阅读 6,161)
  5. 正则表达式的与或非 (阅读 5,744)
  6. 学习Grep,Sed中的正则 (阅读 5,267)
  7. URL正则表达式 (阅读 4,663)
  8. 利用vim(gvim)的正则表达式实现代码自动匹配完成(等号两边数据交换) (阅读 4,524)
  9. 正则表达式简要入门 (阅读 4,364)
  10. 正则转义符汇总 (阅读 4,321)