> 正则表达式手册 > 正则表达式学习(八)位置匹配

PS:在所有例子中正则表达式匹配结果包含在源文本中的【和】之间,有的例子会使用java来实现,如果是java本身正则表达式的用法,会在相应的地方说明。所有java例子都在JDK1.6.0_13下测试通过。

 

一、问题引入

         如果想匹配一段文本中的某个单词(暂不考虑多行模式,将在后面介绍),我们可能会像下面这样:

文本:Yesterday is history, tomorrow is a mystery, but today is a gift.

正则表达式:is

结果:Yesterday 【is】 h【is】tory, tomorrow 【is】 a mystery, but today 【is】 a gift.

分析:本来只是要匹配单词is,但把其他单词中包含的is也匹配出来了。要解决这个问题,使用边界界定符,也就是在正则表达式里用一些元字符来表明我们想让匹配操作在什么位置(或边界)发生。

 

二、单词边界

         一种常用的边界是由限定符\b指定的单词边界,\b用来匹配单词的开始和结尾。更确切地说,它是匹配这样一个位置,这个位置位于一个能够用来构成单词的字符(字母、数字、下划线,也就是与\w相匹配的字符)和一个不能用来构成单词的字符(与\W相匹配的字符)之间。来看前面的例子:

文本:Yesterday is history, tomorrow is a mystery, but today is a gift.

正则表达式:\bis\b

结果:Yesterday 【is】 history, tomorrow 【is】 a mystery, but today 【is】 a gift.

分析:在原始文本中,单词is的前后都有一个空格,而这与模式\bis\b匹配(空格是用来分隔单词的字符之一)。而单词history中也包含了is,因为它的前后分别有一个字符h和t,这两个字符都不能与\b匹配。

         如果不匹配一个单词边界,则使用\B。如:

文本:Please enter the nine-digit id as it appears on your color - coded pass-key.

正则表达式:\B-\B

结果:Please enter the 【nine-digit】 id as it appears on your color - coded 【pass-key】.

分析:\B-\B将匹配一个前后都不是单词边界的连字符,nine-digit和pass-key中连字符前后都没有空格,所以能够匹配,而color - coded中连字符前后都有空格,所以不能匹配。

 

三、字符串边界

         单词边界可以用来进行与单词有关的位置匹配(单词开头、结束、整个单词等等)。而字符串边界也有着类似的用途,只不过是用来进行与字符串有关的位置匹配(字符串开头、结束、整个字符串等等)。用来定义字符串边界的元字符有两个:一个是用来定义字符串开头的^,另一个是用来定义字符串结尾的$。

         比如要检查一个XML文档的合法性,合法的XML文档都以<?xml…..?>这样形式开头:

文本:

<?xml version="1.0" encoding="UTF-8"?>

<project basedir="." default="ear">

</project>

正则表达式:^\s*<\?xml.*?\?>

结果:

<?xml version="1.0" encoding="UTF-8"?>

<project basedir="." default="ear">

</project>

分析:^匹配一个字符串的开头位置,所以^\s*将匹配一个字符串的开头位置和随后的零个或多个空白字符,因为<?xml>标签前面允许有空格、制表符、换行符等空白字符。

 

         $元字符符的用法除了位置上的差异外,与^用法完全一样。比如,检查一个HTML页面是否以</html>结尾,可以用模式:</[Hh][Tt][Mm][Ll]>\s*$

 

四、多行匹配模式

         正则表达式可以通过一些特殊的元字符来改变另外一些元字符的行为。可以通过(?m) 来启用多行匹配模式。多行匹配模式将使得正则表达式引擎把行分隔符当做一个字符串分隔符来对待。在多行匹配模式下,^不仅匹配正常的字符串开头,还将匹配行分隔符(换行符)后面的开始位置,$不仅匹配正常的字符串结尾,还将匹配行分隔符(换行符)后面的结束位置。

         在使用时,(?m)必须出现在整个模式的最前面。比如,通过正则表达式把一段java代码中的单行注释(以//开始)内容全部找出来。

文本:

         publicDownloadingDialog(Frame parent){
                   //Callsuper constructor, specifying that dialog box is modal.
                   super(parent,true);
                   //Setdialog box title.
                   setTitle("E-mailClient");
                   //Instructwindow not to close when the "X" is clicked.
                   setDefaultCloseoperation(DO_NOTHING_ON_CLOSE);
                   //Puta message with a nice border in this dialog box.
                   JPanelcontentPanel = new JPanel();
                   contentPanel.setBorder(BorderFactory.createEmptyBorder(5,5, 5, 5));
                   contentPanel.add(newJLabel("Downloading messages..."));
                   setContentPane(contentPanel);
                  
                   //Sizedialog box to components.
                   pack();
                   //Centerdialog box over application.
                   setLocationRelativeTo(parent);
         }

正则表达式:(?m)^\s*//.*$

结果:

         publicDownloadingDialog(Frame parent){

【              //Call superconstructor, specifying that dialog box is modal.】

                   super(parent,true);

【              //Set dialog boxtitle.】

                   setTitle("E-mailClient");

【              //Instruct windownot to close when the "X" is clicked.】

                   setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

【              //Put a messagewith a nice border in this dialog box.】

                   JPanelcontentPanel = new JPanel();

                   contentPanel.setBorder(BorderFactory.createEmptyBorder(5,5, 5, 5));

                   contentPanel.add(newJLabel("Downloading messages..."));

                   setContentPane(contentPanel);

                  

【              //Size dialog boxto components.】

                   pack();

【              //Center dialogbox over application.】

                   setLocationRelativeTo(parent);

         }

分析:^\s*//.*$将匹配一个字符串的开始,然后是任意多个空白字符,再后面是//,再往后是任意文本,最后是一个字符串的结束。不过这个模式只能找出第一条注释,加上(?m)前缀后,将把换行符视为一个字符串分隔符,这样就可以把每一行注释匹配出来了。

java代码实现如下(文本保存在text.txt文件中):

	public static String getTextFromFile(String path) throws Exception{
		BufferedReader br = new BufferedReader(new FileReader(new File(path)));
		StringBuilder sb = new StringBuilder();
		char[] cbuf = new char[1024];
		int len = 0;
		while(br.ready() && (len = br.read(cbuf)) > 0){
			br.read(cbuf);
			sb.append(cbuf, 0, len);
		}
        br.close();
		return sb.toString();
	}
	public static void multilineMatch() throws Exception{
		String text = getTextFromFile("E:/text.txt");
		String regex = "(?m)^\\s*//.*$";
		Matcher m = Pattern.compile(regex).matcher(text);
		while(m.find()){
			System.out.println(m.group());
		}
	}
输出结果如下:

//Call super constructor, specifying that dialog box is modal.
//Set dialog box title.
//Instruct window not to close when the "X" is clicked.
//Put a message with a nice border in this dialog box.

//Size dialog box to components.
//Center dialog box over application.

五、小结

正则表达式不仅可以用来匹配任意长度的文本块,还可以用来匹配出现在字符串中特定位置的文本。\b用来指定一个单词边界(\B刚好相反)。^和$用来指定单词边界。如果与(?m)配合使用,^和$还将匹配在一个换行符处开头或结尾的字符串。在下一篇中将介绍子表达式的使用。