正则表达式中量词贪婪型和勉强型的讨论(Java语言描述)

在正则表达式的书写量词时,具有贪婪型号、勉强型2种常见类型(Java中还有用于完全匹配的占有型)。刚开始学习时,并没有意识到他们之间的区别。但是在匹配一个例子时,调了好长时间的BUG才意识到原来是匹配模式方面思考不严谨。下面就将几点经验大家共享:

首先是贪婪型和勉强型的区别,贪婪与非贪婪模式影响的是被量词修饰的子表达式的匹配行为,贪婪模式在整个表达式匹配成功的前提下,尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果是贪婪模式,那么它将继续向下进行匹配。而勉强模式在整个表达式匹配成功的前提下,尽可能少的匹配。这个量词匹配满足模式所需的最少字符数。
属于贪婪模式的量词,也叫做匹配优先量词,包括:
X? 一个或零个X

X* 零个或多个X

X+ 一个或多个X

X{n} 恰好n个X

X{n,} 至少n个X

X{n,m} X至少n次,且不超过m次

在匹配优先量词后加上“?”,即变成属于勉强模式的量词,也叫做忽略优先量词,和上面意义对应分别为:

X?? 一个或零个X

X*? 零个或多个X

X+? 一个或多个X

X{n}? 恰好n个X

X{n,}? 至少n个X

X{n,m}? X至少n次,且不超过m次


从正则语法的角度来讲,被匹配优先量词修饰的子表达式使用的就是贪婪模式,如“(Expression)+”;被忽略优先量词修饰的子表达式使用的就是非贪婪模式,如“(Expression)+?”。
对于他们区别的重要性,我们通过一个我在练习的过程中碰到的例子来进行区别。


题目要求是,通过输入输出流读取一段Java代码,通过正则表达式提取出代码中所有的注释。

我们将前面算法类博客的bag01.java放入E盘下,用作实验。看到这个题目,首先想到注释包含两种不同的类型,一种是在程序开头出现的可能出现换行的/*.....*/的注释,另外一种是在代码书写过程中的// 注释。对于后者,匹配比较容易,直接通过正则表达式“//.*”将其提取出来即可。对于前一种,我们先看一个貌似可行的方案:

package chapter13;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class exe13_17 {
	public static void main(String[] args)throws Exception{
		StringBuilder sb = new StringBuilder();
		String line;
		String regex = "/\\*.*\\*/";
		Pattern p = Pattern.compile(regex);
		BufferedReader bufr = new BufferedReader(new FileReader("E:\\bag01.java"));
		while((line=bufr.readLine())!=null){
			sb.append(line);
//			sb.append("\r\n");
		}
		
		String article = sb.toString();
		Matcher m = p.matcher(article);
		while(m.find())
			System.out.println(m.group());
		bufr.close();
	}
}

通过代码的执行可以发现,我们将读取到的每一行数据都存入到一个StringBuilder中去,且不进行换行。在正则表达式中,运用了贪婪型的模式。可以解读为以/*开头,中间有任意多的非换行的任意字符,并且以*/结尾的都可以完成匹配。在读取文件不换行的情况下,我们获得了这样的结果:


我们能够取出位于开头的注释,但是全文没有换行符,可读性极差。

再将上述代码的换行符的注释去掉后,再用该正则表达式进行匹配,会发现没有匹配成功的字符串。原因就在于正则表达式中的“.”只是对于非换行符的任意字符有效,当每一行最后加入换行符后,必然会导致上述匹配失败。所以,当时我的思路就是将中间任意多个字符的表述变为“.|\\s”,\s表示空白符(空格,tab,换行,换页,回车)。所以正则表达式就变为"/\\*(.|\\s)*\\*/*"。代码为:

package chapter13;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class exe13_17 {
	public static void main(String[] args)throws Exception{
		StringBuilder sb = new StringBuilder();
		String line;
		String regex = "/\\*(.|\\s)*\\*/";
		//String regex = "//.*";
		Pattern p = Pattern.compile(regex);
		BufferedReader bufr = new BufferedReader(new FileReader("E:\\bag01.java"));
		while((line=bufr.readLine())!=null){
			sb.append(line);
			sb.append("\r\n");
		}
		
		String article = sb.toString();
		Matcher m = p.matcher(article);
		while(m.find())
			System.out.println(m.group());
		bufr.close();
	}
}
运行结果很不可思议,出现了堆栈溢出的错误:

为什么用正则表达式进行匹配会出现堆栈溢出呢?原因就在于这里使用了贪婪型的匹配模式,对于中间的“(.|\\s)*” 任意字符包括换行符都能够符合条件,所以这篇文档中的所有字符都能够符合这半部分的要求,因为是贪婪模式,所以后面的\\*/ 根本读不到。中间的那个部分能够读完内存中所有的字符都满足条件,最终发生堆栈溢出的情况。而如果将匹配的正则表达式改为勉强型:"/\\*(.|\\s)*?\\*/",及找出最短的符合匹配模式的字符串,那么当匹配到开头注释的结尾*/时,整个字符串就已经满足了匹配模式,而后面又不再有满足条件的字符串,所以匹配会返回找到的这个符合条件的字符串并且返回。最终的代码变为:

package chapter13;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class exe13_17 {
	public static void main(String[] args)throws Exception{
		StringBuilder sb = new StringBuilder();
		String line;
		String regex = "(/\\*(.|\\s)*?\\*/)|//.*";    //加个问号改为勉强型!!不能用贪婪型!!
		Pattern p = Pattern.compile(regex);
		BufferedReader bufr = new BufferedReader(new FileReader("E:\\bag01.java"));
		while((line=bufr.readLine())!=null){
			sb.append(line);
			sb.append("\r\n");
		}
		
		String article = sb.toString();
		Matcher m = p.matcher(article);
		while(m.find())
			System.out.println(m.group());
		bufr.close();
	}
}
运行结果为:

可以看到匹配完全成功。可见,深入理解贪婪型和勉强型的区别在使用中是非常重要的。里面的逻辑关系一定要理顺弄清。




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值