字符串(4):正则表达式(中)

四、Pattern和Matcher

(1)Pattern和Matcher

    一般来说,比起功能有限的String类,我们更愿意构造功能强大的正则表达式对象。只需导入java.util.regex包,然后用static Pattern.compile()方法来编译你的正则表达式即可。它会根据你的String类型的正则表达式生成一个Pattern对象。接下来,把你想要检索的字符串传入Pattern对象的matcher()方法。matcher()方法会生成一个Matcher对象,它有很多功能可用(可以参考java.util.regext.Matcher的JDK文档)。例如,它的replaceAll()方法能将所有匹配的部分都替换成你传入的参数。

    作为第一个示例,下面的类可以用来测试正则表达式,看看它们能否匹配一个输入字符串。第一个控制台参数是将要用来搜索匹配的输入字符串,后面的一个或多个参数都是正则表达式,它们将被用来在输出的第一个字符串中查找匹配。在Unix/Linux上,命令行中的正则表达式必须用引号括起来。这个程序在测试正则表达式时很有用,特别是当你想验证它们是否具备你所期待的匹配功能的时候。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestRegularExpression {
	public static void main(String[] args) {
		args = new String[] { "abcabcabcdefabc", "abc+", "(abc)+", "(abc){2,}" };
		if (args.length < 2) {
			System.out.println("Usage:\njava TestRegularExpression characterSequence regularExpression+");
			System.exit(0);
		}
		System.out.println("Input:\"" + args[0] + "\"");
		for (String arg : args) {
			System.out.println("Regular expression:\"" + arg + "\"");
			Pattern p = Pattern.compile(arg);
			Matcher m = p.matcher(args[0]);
			while (m.find()) {
				System.out.println("Math \"" + m.group() + "\" at positions " + m.start() + "-" + (m.end() - 1));
			}
		}
	}
}

    Pattern对象表示编译后的正则表达式。从这个例子中可以看到,我们使用已编译的Pattern对象的matcher()方法,加上一个输入字符串,从而共同构造了一个Mathcer对象。同时,Pattern类还提供了static方法:

static boolean matchers(String regex, CharSequence input);

    该方法用以检查regex是否匹配整个CharSequence类型的input参数。编译后的Pattern对象还提供了split()方法,它从匹配了regex的地方分割输入字符串,返回分割后的子字符串String数组。

    通过调用Pattern.matcher()方法,并传入一个字符串参数,我们得到了一个Matcher对象。使用Matcher上的方法,我们将能够判断各种不同类型的匹配是否成功:

boolean matchers();
boolean lookingAt();
boolean find();
boolean find(int start);

    其中的matcher()方法用来判断整个输入字符串是否匹配正则表达式模式,而lookingAt()则用来判断该字符串(不必是整个字符串)的报告开头部分是否能够匹配模式。

(2)find()

    Match.find()方法可用来在CharSequece中查找多个匹配。例如:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Finding {
	public static void main(String[] args) {
		Matcher m = Pattern.compile("\\w+").matcher("Evening is full of the linnet's wings");
		while (m.find())
			System.out.print(m.group() + " ");
		System.out.println();
		int i = 0;
		while (m.find(i)) {
			System.out.print(m.group() + " ");
			i++;
		}
	}
}

    模式\\w+将字符串划分为单词。find()像迭代器那样向前遍历输入字符串。而第二个find()能够接收一个整数作为参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果中可以看出,后一个版本的find()方法能根据其参数的值,不断重新设定搜索的起始位置。

(3)组(Groups)

    组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个表达式,组号1表示被第一对括号括起来的组,以此类推。因此,在这个表达式:A(B(C))D。中有三个组:组0是ABCD,组1是BC,组2是C。

    Matcher对象提供了一系列方法,用以获取与组相关的信息:public int groupCount()返回该匹配器的模式中的分组数目,第0组不包括在内。public String group()返回前一次匹配操作(例如find())的第0组(整个匹配)。public String group(int i)返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则将返回null。public int start(int group)返回在前一次匹配操作中寻找到的组的起始索引。pubic int end(int group)返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。

    下面是正则表达式组的例子:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Groups {
	static public final String POEM = 
			"Twas brillig, and the slithy toves\n"
			+"Did gyre and gimble in the wabe.\n"
			+"All mimsy were the borogoves,\n"
			+"And the mome raths outgrabe.\n\n"
			+"Beware the Jabberwock, my son,\n"
			+"The jaws that bite, the claws that catch.\n"
			+"Beware the Jubjub bird, and shun\n"
			+"The frumious Bandersnatch.";
	
	public static void main(String[] args) {
		Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$").matcher(POEM);
		while(m.find()) {
			for (int i = 0; i <= m.groupCount(); i++) 
				System.out.print("[" + m.group(i) + "]");
			System.out.println();
		}
	}
}

    可以看到这个正则表达式模式有许多圆括号分组,由任意数目的非空格字符(\S+)及随后的任意数目的空格字符(\s+)所组成。目的是捕获每行的最后三个单词,每行最后以$结束。不过,在正常情况下是将$与整个输入序列的末端相匹配。所以我们一定要显式地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记(?m)来完成(模式标记马上就会介绍)。

(4)start()与end()

    在匹配操作成功之后,start()返回先前匹配的起始位置的索引,而end()返回所匹配的最后字符的索引加一的值。匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用start()或end()将会产生IllegalStateException。下面的示例还同时展示了matches和lookingAt()的用法:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StartEnd {
	public static String input=
			"As long as there is injustice, whenever a\n"
			+"Targathian baby cries out. wherever a distress\n"
			+"signal sounds among the stars ... we'll be there.\n"
			+"This fine ship, and this fine crew ... \n"
			+"Never give up! Never surrender!";
	
	private static class Display {
		private boolean regexPrinted = false;
		private String regex;
		
		Display(String regex){
			this.regex = regex;
		}
		
		void display(String message) {
			if(!regexPrinted) {
				System.out.println(regex);
				regexPrinted = true;
			}
			System.out.println(message);
		}
	}
	
	static void examine(String s,String regex) {
		Display d = new Display(regex);
		Pattern p = Pattern.compile(regex);
		Matcher m = p.matcher(s);
		while(m.find())
			d.display("find() '" + m.group() + "' start = " + m.start() + " end = " + m.end()); 
		if(m.lookingAt())
			d.display("lookingAt() start = " + m.start() + " end = " + m.end()); 
		if(m.matches())	
			d.display("matches() start = " + m.start() + " end = " + m.end()); 
	}
	
	public static void main(String[] args) {
		for (String in : input.split("\n")) {
			System.out.println("input : " + in);
			for (String regex : new String[] {"\\w*ere\\w*", "\\w*ever", "T\\w+", "Never.*?!"}) 
				examine(in, regex);
		}
	}
}

    注意,find()可以在输入的任意位置定位正则表达式,而lookingAt()和matches()只有在正则表达式与输入的最开始处就开始匹配时才会成功。matches()只有在整个输入都匹配正则表达式时才会成功,而lookingAt()只要输入的第一部分匹配就会成功。

(5)Pattern标记

    Pattern类的compile()方法还有另一个版本,它接受一个标记参数,以调整匹配的行为:

Pattern Pattern.compile(String regex,int flags);

    其中的flag来自以下Pattern类中的常量:

编译标记效果

Pattern.CANON_EQ

    两个字符当且仅当它们的完全规范分解相匹配时,就认为它们是匹配的。例如,如果我们指定这个标记,表达式a\u030A就会匹配字符串?。在默认的情况下,匹配不考虑规范的等价性。
Pattern.CASE_INSENSITIVE(?i)    默认情况下,大小写不敏感的匹配假定只有US-ASCII字符集中的字符才能进行。这个标记允许模式匹配不必考虑大小写。通过指定UNICODE_CASE标记及结合此标记,基于Unicode的大小写不敏感的匹配就可以开启了。
Pattern.COMMENTS(?x)    这种模式下,空格符将被忽略掉,并且以#开始直到行末的注释也会被忽略掉。通过嵌入的标记表达式也可以开启Unix的行模式。
Pattern.DOTALL(?s)    在dotall模式中,表达式“.”匹配所有字符,包括行终结符。默认情况下,“.”表达式不匹配行终结符。
Pattern.MULTILINE(?m)    在多行模式下,表达式^和$分别匹配一行的开始和结束。^还匹配输入字符串的开始,而$还匹配输入字符串的结尾。默认情况下,这些表达式仅匹配输入的完整字符串的开始和结束。
Pattern.UNICODE_CASE(?u)    当指定这个标记,并且开启CASE_INSENSITIVE时,大小写不敏感的匹配将按照与Unicode标准相一致的方式进行。默认情况下,大小写不敏感的匹配假定只能在US-ASCII字符集中的字符才能进行。
Pattern.UNIX_LINES(?d)    在这种模式下,在. 、^和$行为中,只识别行终结符\n。

     这这些标记中,Pattern.CASE_INSENSITIVE、Pattern.MULTILINE以及Pattern.COMMENTS(对声明或文档)特别有用。请注意,你可以直接在正则表达式中使用其中的大多数标记,只需要将上表中括号括起来的字符插入到正则表达式中,你希望它起作用的位置即可。

    你还可以通过“或”(|)操作符组合多个标记功能:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Reflags {
	public static void main(String[] args) {
		Pattern p = Pattern.compile("^java", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
		Matcher m = p.matcher(
				"java has regex\nJava has regex\nJAVA has pretty good regular expressions\nRegular expressions are in Java");
		while (m.find())
			System.out.println(m.group());
	}
}

    在这个例子中,我们创建了一个模式,它将匹配所有以“java”、“Java”和“JAVA”等开头的行,并且是在设置了多行标记的状态下,对每一个行(从字符序列的第一个字符开始,至每一行终结符)都进行匹配。注意,group()方法只返回已匹配的部分。

未完待续。如果本文对您有很大的帮助,还请点赞关注一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游王子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值