网上很好的一篇文章: 正则表达式30分钟入门教程
以及一本经典正则表达式权威 master regular expression
其中提到了 很少用到的 零宽断言正则表达式 (Zero-Width Assertions),javascript作为脚本语言虽然没有全部实现,但是对于预测式的零宽断言其实也是实现了的。
(?=exp)也叫零宽度正预测先行断言(positive lookahead):
它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。
如:
var x=/\b\w+(?=ing\b)/g; alert("I'm singing while you're dancing.".match(x));
(?<=exp)也叫零宽度正回顾后发断言(positive lookbehind 不支持) :
//not support by javascript from master regular expression alert("yiminghes book".replace(/(?=s)(?<=yiminghe)/g,"'") == "yiminghe's book");
其实可以用分组回避:
//其实可以用分组捕获来回避的 alert("yiminghes book".replace(/(yiminghe)(?=s)/g,"$1'") == "yiminghe's book");
Java 支持 :
public class Test { public static void main(String[] args){ //java support lookahead and lookbehind System.out.println("yiminghes book".replaceAll("(?=s)(?<=yiminghe)","'") .equals("yiminghe's book")); } }
零宽度负预测先行断言 (?!exp negative lookahead) :
断言此位置的后面不能匹配表达式exp 。例如:\d{3}(?!\d) 匹配三位数字,而且这三位数字的后面不能是数字 ;\b((?!abc)\w)+\b 匹配不包含连续字符串abc的单词 。
如:
var x=/\b((?!abc)\w)+\b/g; alert('xyz abc zuc zabc 11'.match(x));
也可用来模拟 字符类集合操作 :
匹配 a-z 除去 acd
alert("z".match(/(?![acd])[a-z]/));
举例:匹配页面全部html的例子 (From high performance javascript)
最开始想法:
/<html>[\s\S]*?<head>[\s\S]*?<title>[\s\S]*?<\/title>[\s\S]*?<\/head> [\s\S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>/
但是当页面html闭合出问题时,由于回朔(backtracking) 性能会不可接受,利用 (?!)可修改为
/<html>(?:(?!<head>)[\s\S])*<head>(?:(?!<title>)[\s\S])*<title> (?:(?!<\/title>)[\s\S])*<\/title>(?:(?!<\/head>)[\s\S])*<\/head> (?:(?!<body>)[\s\S])*<body>(?:(?!<\/body>)[\s\S])*<\/body> (?:(?!<\/html>)[\s\S])*<\/html>/
大大减少了backtracking,但是毕竟?!性能不高,再加上量词*,这个解决方案仍然不是最好,最好的方案见下文原子分组部分。
java 直接支持类操作:
System.out.println("a".matches("[[a-z]&&[^acd]]"));
(?<!exp) ,零宽度负回顾后发断言(不支持 negative lookbehind)
零宽度负预测先行断言在javascript中的一个很好的应用例子:
/** * 组合两个正则表达式。 * @param[RegExp] r 要组合的一个字符串 * @param[String] attr attributes of the final new return RegExp * @return[RegExp] */ RegExp.prototype.concat = function(r, attr){ //零宽度负预测先行断言 ,所有捕捉分组个数 ,()不包括 (?:) var i=(this.source.match(/\((?!\?:)/g) || "").length; //最终的regexp的第二个正则表达式部分的后向引用要考虑本身regexp已有的后向引用,重新编号 return new RegExp(this.source+r.source.replace(/\\(\d)/g, function($0, $1){ return "\\" + (i+($1 | 0)); }), attr); };
使用:
//匹配2个重复数字后跟一个字母 var x=/(\d)\1(?:\w)/g; //然后必须紧跟两个重复字母 x=x.concat(/(\w)\1/,'g'); //x == /(\d)\1(?:\w)(\w)\2/g alert('11aww 22xzy'.match(x));
原子分组应用 (?>) 不支持 (atomic group)固化分组
一旦匹配则消除由当前原子分组内的所有保存状态
保留小数2-3位,如果第三位不为0保留三位,否则保留两位
例子:
0.55555 -> 0.555
0.550005 -> 0.55
0.50002 -> 0.50
可以用正则式
"0.625".replace(/(\.\d\d[1-9]?)\d*/,"$1");
但是其实 0.625 可以完全不必被匹配(效率问题),于是 [1-9]? 一旦匹配就不要再放弃了,可得
"0.625".replace(/(\.\d\d(?>[1-9]?))\d+/,"$1");
但是 js 不支持哦,但是可以用 positive lookhead 结合 backreference 来取代一下。利用 subexpression 来丢失状态以及捕获分组
/(\.\d\d(?=([1-9]?))\2)\d+/.test("0.625"); /(\.\d\d(?=([1-9]?))\2)\d+/.test("0.6250981");
lookahead 其实就是一个原子分组,但是如前所述它的匹配不消耗字符,因此可以通过将一个lookahead包裹在一个捕捉分组中,并在其后跟上backreference,而形成了以下模式:
(?=(pattern to make atomic))\1
多个的话只要注意backreference的计数即可。
最好的例子:接?!部分的匹配页面html例子:
利用原子分组彻底消除回朔可得:
/<html>(?=([\s\S]*?<head>))\1(?=([\s\S]*?<title>))\2(?=([\s\S]*? <\/title>))\3(?=([\s\S]*?<\/head>))\4(?=([\s\S]*?<body>))\5 (?=([\s\S]*?<\/body>))\6[\s\S]*?<\/html>/
JAVA 丢失状态例子2 :离开原子分组后 | 保存的状态没了:
String str = "^(?>a+|.+)$"; String s = "aaaaaac"; Pattern p = Pattern.compile(str); Matcher m = p.matcher(s); System.out.println(m.replaceAll("!"));
possessive match (+) 不支持
同固化分组,一旦匹配就删除用于回朔的保存状态,一般和量词结合
"0.625".replace(/(\.\d\d[1-9]?+)\d+/,"$1");
js 不支持 ,java.util.regexp 支持
System.out.println(".625".matches("(\\.\\d\\d[1-9]?+)\\d+")); System.out.println(".625".matches("(\\.\\d\\d[1-9]?)\\d+"));
DOTALL 模式 (?s) 不支持
不支持 java 中的 Pattern.DOTALL 以及 (?s) ,以及 perl 中的 //s ,s modifier : .可以匹配回车换行
使用alternation以及character class弥补:
/.|[\n\r]/.test("\r");
条件判断 :(?if then | else) 不支持
java ,javascript 均不支持
Pattern p = Pattern.compile("(<a>)?<img/>(?(1)\\s*</a>)"); String s="<img/>"; Matcher m=p.matcher(s); System.out.println(m.group());
/(<a>)?<img/>(?(1)\s*</a>)/.test("<img/>");
注意 if 中的正则表达式为零宽向前(?=,?!)或向后预测(?<=,><!)正则表达式。如果不写则为零宽正向向前预测。
详细解释:
正则表达式 (<a>)?<img/>(?(1)\s*</a>) 相当于: (<a>)?<img/>(?(1)(\s*</a>)|\s*),可匹配的字符串包括 <a><img/></a> 与 <img/>,而不匹配 <a><img/> 或 <img/></a>。
其中 if (1) 为先前匹配到的分组 (<a>),如果先前出现了 <a> 则接着匹配结束标记 </a>,否则直接匹配成功。
后果是代码极其不易读,实际中尽量不要使用!可分成多个正则联合检测
\G anchor 不支持
还包括 \A,\Z 等锚点都不支持!\G 在 perl,java 中表示当前匹配开始的位置在上次匹配结束的位置,如果失败则不会从字符串的下一个位置重新匹配。
1.在零匹配成功后,\G 位置为当前位置,而下一次匹配位置被强制为当前位置的下一位置(防止死循环),所以\G后只能一次成功零匹配
2.匹配成功后,如果从当前匹配结束位置不能匹配正则,则整体失败,不会从当前匹配结束位置的下一位置进行下一次匹配。
如:
String[] ps = {"x*", "\\Gx*", "ab", "\\Gab","abc","\\Gabc"}; String s = "abcabc"; System.out.println(s + " : "); for (String str : ps) { System.out.print(str + " : "); Pattern p = Pattern.compile(str); Matcher m = p.matcher(s); System.out.println(m.replaceAll("!")); }
另外命名捕获,正则表达式内模式切换(Pattern.compile("(?i)T(?-i)Est")),注释,unicode 属性区块也不支持。
? lazy 描述符 支持
可修饰 ? 以及 + ,* 等,不加都是贪婪模式,加了则为懒模式了(能不匹配就不匹配):
var reg=/.+?/g; reg.exec("12"); console.log(reg.lastIndex); var reg=/.??/g; reg.exec("12"); console.log(reg.lastIndex); var reg=/.*?/g; reg.exec("12"); console.log(reg.lastIndex);
Bonus :
String.prototype.replace 的 javascript 实现比较
updated 2010-11-24
发现一个关于正则式的专业网站 ,详细比较了各种语言的正则引擎,本文内容都已经被他包含.....
updated 2011-01-11
发现一个很了解各个浏览器正则引擎的blog,原来浏览器也不是完全按照规范来的,各有不同(bug?):
non-participate capture group 也有问题