正则表达式的匹配有两种概念:

  • 匹配字符
  • 匹配位置。例如:^匹配输入字行首,$匹配输入字行尾

零宽断言是一种零宽度的匹配,它匹配的内容不会保存到匹配结果中,也不会占用index宽度,它用于查找在某些内容之前或之后的东西。也就是说他们像\b, ^, $这样的锚定作用一样,用于匹配一个位置,这个位置应该满足一定条件(即断言)。

所谓零宽断言分为先行断言和后行断言:

  1. 先行断言
    第一步,按照正则表达式顺序去匹配。
    第二步,遇到先行断言,就会向前看,预搜索判断是否满足先行断言。
  2. 后行断言
    第一步,按照正则表达式顺序去匹配。
    第二步,遇到后行代言,就会向后看,回溯判断是否满足后行断言。

零宽断言一共有一下四种形式:

  1. 零宽正向先行断言(zero-width positive lookahead assertion)
    (?=pattern) 代表字符串中的一个位置,紧接该位置之后的字符序列能够匹配pattern。
\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分)
  1. 零宽负向先行断言(zero-width negative lookahead assertion)
    (?!pattern) 代表字符串中的一个位置,紧接该位置之后的字符序列不能匹配pattern。
\d{3}(?!\d),匹配三位数字,而且这三位数字的后面不能是数字
  1. 零宽正向后行断言(zero-width positive lookbehind assertion)
    (?<=pattern) 代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配pattern。
(?<=<(\w+)>).*(?=<\/\1>),匹配不包含属性的简单HTML标签内里的内容
(<?(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>),
然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。
注意后缀里的\/,它用到了前面提过的字符转义;
\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容
这样如果前缀实际上是<b>的话,后缀就是</b>了。
整个表达式匹配的是<b>和</b>之间的内容
  1. 零宽负向后行断言(zero-width negative lookbehind assertion)
    (?<!pattern) 代表字符串中的一个位置,紧接该位置之前的字符序列不能匹配pattern。

(?<!\\)=只匹配前面是反斜线的等号。

String tmp = grabNextValue(args, name, "for its key");
// only match = to escape use "\="
Pattern p = Pattern.compile("(?<!\\\\)=");
String[] parts = p.split(tmp);

例子:

public class regrexAssert {
	public static void main(String[] args) {
		String content = "I'm singing while you're dancing.";
        run(content, "\\b\\w+(?=ing\\b)");
        System.out.println(".................................................");
        content = "12345dgasdf23r5dga478kfa3445gdgd434ddfg";
        run(content, "\\d{3}(?!\\d)");
        System.out.println(".................................................");
        content = "src: local('Open Sans Light'), local('OpenSans-Light'), url(http://fonts.gstatic.com) format('woff2')";
        run(content, "(?<=\\()[^\\)]+");
        System.out.println(".................................................");
        content = "a\\=b=c\\=d=e\\=f=g\\=h";
        Pattern p = Pattern.compile("(?<!\\\\)=");
        String[] parts = p.split(content);
        System.out.println(Arrays.toString(parts));
	}
	public static void run(String content, String regrex) {
		Pattern pattern = Pattern.compile(regrex);
		Matcher matcher = pattern.matcher(content);
		while(matcher.find()){
			System.out.println(matcher.group());	
		}
	}
}

终端输出如下结果:

sing
danc
.................................................
345
478
445
434
.................................................
'Open Sans Light'
'OpenSans-Light'
http://fonts.gstatic.com
'woff2'
.................................................
[a\=b, c\=d, e\=f, g\=h]

注意:
在后行断言中,这个表达式必须是定长(fixed length)的,即不能使用*、+、?等元字符,如(?<=abc)没有问题,但(?<=a*bc)是不被支持的,特别是当表达式中含有|连接的分支时,各个分支的长度必须相同。之所以不支持变长表达式,是因为当引擎检查后行断言时,无法确定要回溯多少步。