在Sun的Java JDK 1.40版本中,Java自带了支持正则表达式的包,本文就抛砖引玉地介绍了如何使用java.util.regex包。html
可粗略估计一下,除了偶尔用Linux的外,其余Linu x用户都会遇到正则表达式。正则表达式是个极端强大工具,并且在字符串模式-匹配和字符串模式-替换方面富有弹性。在Unix世界里,正则表达式几乎没有什么限制,可确定的是,它应用很是之普遍。java
正则表达式的引擎已被许多普通的Unix工具所实现,包括grep,awk,vi和Emacs等。此外,许多使用比较普遍的脚本语言也支持正则表达式,好比Python,Tcl,JavaScript,以及最著名的Perl。程序员
我很早之前就是个Perl方面的黑客,若是你和我同样话,你也会很是依赖你手边的这些强大的text-munging工具。近几年来,像其余程序开发者同样,我也愈来愈关注Java的开发。正则表达式
Java做为一种开发语言,有许多值得推荐的地方,可是它一直以来没有自带对正则表达式的支持。直到最近,借助于第三方的类库,Java开始支持正则表达式,但这些第三方的类库都不一致、兼容性差,并且维护代码起来很糟糕。这个缺点,对我选择Java做为首要的开发工具来讲,一直是个巨大的顾虑之处。spring
你能够想象,当我知道Sun的Java JDK 1.40版本包含了java.util.regex(一个彻底开放、自带的正则表达式包)时,是多么的高兴!很搞笑的说,我花好些时间去挖掘这个被隐藏起来的宝石。我很是惊奇的是,Java这样的一个很大改进(自带了java.util.regex包)为何很少公开一点呢?!sql
最近,Java双脚都跳进了正则表达式的世界。java.util.regex包在支持正则表达也有它的过人之处,另外Java也提供详细的相关说明文档。使得朦朦胧胧的regex神秘景象也慢慢被拨开。有一些正则表达式的构成(可能最显著的是,在于糅合了字符类库)在Perl都找不到。数据库
在regex包中,包括了两个类,Pattern(模式类)和Matcher(匹配器类)。Pattern类是用来表达和陈述所要搜索模式的对象,Matcher类是真正影响搜索的对象。另加一个新的例外类,PatternSyntaxException,当遇到不合法的搜索模式时,会抛出例外。express
即便对正则表达式很熟悉,你会发现,经过java使用正则表达式也至关简单。要说明的一点是,对那些被Perl的单行匹配所宠坏的Perl狂热爱好者来讲,在使用java的regex包进行替换操做时,会比他们因此前经常使用的方法费事些。apache
本文的局限之处,它不是一篇正则表达式用法的彻底教程。若是读者要对正则表达进一步了解的话,推荐阅读Jeffrey Frieldl的Mastering Regular Expressions,该书由O’Reilly出版社出版。我下面就举一些例子来教读者如何使用正则表达式,以及如何更简单地去使用它。编程
设计一个简单的表达式来匹配任何电话号码数字多是比较复杂的事情,缘由在于电话号码格式有不少种状况。全部必须选择一个比较有效的模式。好比:(212) 555-1212, 212-555-1212和212 555 1212,某些人会认为它们都是等价的。
首先让咱们构成一个正则表达式。为简单起见,先构成一个正则表达式来识别下面格式的电话号码数字:(nnn)nnn-nnnn。
第一步,建立一个pattern对象来匹配上面的子字符串。一旦程序运行后,若是须要的话,可让这个对象通常化。匹配上面格式的正则表达能够这样构成:(/d{3})/s/d{3}-/d{4},其中/d单字符类型用来匹配从0到9的任何数字,另外{3}重复符号,是个简便的记号,用来表示有3个连续的数字位,也等效于(/d/d/d)。/s也另一个比较有用的单字符类型,用来匹配空格,好比Space键,tab键和换行符。
是否是很简单?可是,若是把这个正则表达式的模式用在java程序中,还要作两件事。对java的解释器来讲,在反斜线字符(/)前的字符有特殊的含义。在java中,与regex有关的包,并不都能理解和识别反斜线字符(/),尽管能够试试看。但为避免这一点,即为了让反斜线字符(/)在模式对象中被彻底地传递,应该用双反斜线字符(/)。此外圆括号在正则表达中两层含义,若是想让它解释为字面上意思(即圆括号),也须要在它前面用双反斜线字符(/)。也就是像下面的同样:
//(//d{3}//)//s//d{3}-//d{4}
如今介绍怎样在java代码中实现刚才所讲的正则表达式。要记住的事,在用正则表达式的包时,在你所定义的类前须要包含该包,也就是这样的一行:
import java.util.regex.*;
下面的一段代码实现的功能是,从一个文本文件逐行读入,并逐行搜索电话号码数字,一旦找到所匹配的,而后输出在控制台。
BufferedReader in;
Pattern pattern = Pattern.compile("//(//d{3}//)//s//d{3}-//d{4}");
in = new BufferedReader(new FileReader("phone"));
String s;
while ((s = in.readLine()) != null)
{
Matcher matcher = pattern.matcher(s);
if (matcher.find())
{
System.out.println(matcher.group());
}
}
in.close();
对那些熟悉用Python或Javascript来实现正则表达式的人来讲,这段代码很日常。在Python和Javascript这些语言中,或者其余的语言,这些正则表达式一旦明确地编译事后,你想用到哪里均可以。与Perl的单步匹配相比,看起来多多作了些工做,但这并不很费事。
find()方法,就像你所想象的,用来搜索与正则表达式相匹配的任何目标字符串,group()方法,用来返回包含了所匹配文本的字符串。应注意的是,上面的代码,仅用在每行只能含有一个匹配的电话号码数字字符串时。能够确定的说,java的正则表达式包能用在一行含有多个匹配目标时的搜索。本文的原意在于举一些简单的例子来激起读者进一步去学习java自带的正则表达式包,因此对此就没有进行深刻的探讨。
这至关漂亮吧! 可是很遗憾的是,这仅是个电话号码匹配器。很明显,还有两点能够改进。若是在电话号码的开头,即区位号和本地号码之间可能会有空格。咱们也可匹配这些状况,则经过在正则表达式中加入/s?来实现,其中?元字符表示在模式可能有0或1个空格符。
第二点是,在本地号码位的前三位和后四位数字间有多是空格符,而不是连字号,更有胜者,或根本就没有分隔符,就是7位数字连在一块儿。对这几种状况,咱们能够用(-|)?来解决。这个结构的正则表达式就是转换器,它能匹配上面所说的几种状况。在()能含有管道符|时,它能匹配是否含有空格符或连字符,而尾部的?元字符表示是否根本没有分隔符的状况。
最后,区位号也可能没有包含在圆括号内,对此能够简单地在圆括号后附上?元字符,但这不是一个很好的解决方法。由于它也包含了不配对的圆括号,好比"(555" 或 "555)"。相反,咱们能够经过另外一种转换器来强迫让电话号码是否带有有圆括号:(/(/d{3}/)|/d{3})。若是咱们把上面代码中的正则表达式用这些改进后的来替换的话,上面的代码就成了一个很是有用的电话号码数字匹配器:
Pattern pattern =
Pattern.compile("(//(//d{3}//)|//d{3})//s?//d{3}(-|)?//d{4}");
能够肯定的是,你能够本身试着进一步改进上面的代码。
如今看看第二个例子,它是从Friedl的中改编过来的。其功能是用来检查文本文件中是否有重复的单词,这在印刷排版中会常常遇到,一样也是个语法检查器的问题。
匹配单词,像其余的同样,也能够经过好几种的正则表达式来完成。可能最直接的是/b/w+/b,其优势在于只需用少许的regex元字符。其中/w元字符用来匹配从字母a到u的任何字符。+元字符表示匹配匹配一次或屡次字符,/b元字符是用来讲明匹配单词的边界,它能够是空格或任何一种不一样的标点符号(包括逗号,句号等)。
如今,咱们怎样来检查一个给定的单词是否被重复了三次?为完成这个任务,需充分利用正则表达式中的所熟知的向后扫描。如前面提到的,圆括号在正则表达式中有几种不一样的用法,一个就是能提供组合类型,组合类型用来保存所匹配的结果或部分匹配的结果(以便后面能用到),即便遇到有相同的模式。在一样的正则表达中,可能(也一般指望)不止有一个组合类型。在第n个组合类型中匹配结果能够经过向后扫描来获取到。向后扫描使得搜索重复的单词很是简单:/b(/w+)/s+/1/b。
圆括号造成了一个组合类型,在这个正则表示中它是第一组合类型(也是仅有的一个)。向后扫描/1,指的是任何被/w+所匹配的单词。咱们的正则表达式所以能匹配这样的单词,它有一个或多个空格符,后面还跟有一个与此相同的单词。注意的是,尾部的定位类型(/b)必不可少,它能够防止发生错误。若是咱们想匹配"Paris in the the spring",而不是匹配"Java's regex package is the theme of this article"。根据java如今的格式,则上面的正则表达式就是:Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b");
最后进一步的修改是让咱们的匹配器对大小写敏感。好比,下面的状况:"The the theme of this article is the Java's regex package.",这一点在regex中能很是简单地实现,即经过使用在Pattern类中预约义的静态标志CASE_INSENSITIVE :
Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b",
Pattern.CASE_INSENSITIVE);
有关正则表达式的话题是很是丰富,并且复杂的,用Java来实现也很是普遍,则须要对regex包进行的完全研究,咱们在这里所讲的只是冰山一角。即便你对正则表达式比较陌生,使用regex包后会很快发现它强大功能和可伸缩性。若是你是个来自Perl或其余语言王国的老练的正则表达式的黑客,使用过regex包后,你将会安心地投入到java的世界,而放弃其余的工具,并把java的regex包当作是手边必备的利器。
CharSequenceJDK 1.4定义了一个新的接口,叫CharSequence。它提供了String和StringBuffer这两个类的字符序列的抽象: interface CharSequence { charAt(int i); length(); subSequence(int start, int end); toString(); } 为了实现这个新的CharSequence接口,String,StringBuffer以及CharBuffer都做了修改。不少正则表达式的操做都要拿CharSequence做参数。 Pattern和Matcher先给一个例子。下面这段程序能够测试正则表达式是否匹配字符串。第一个参数是要匹配的字符串,后面是正则表达式。正则表达式能够有多个。在Unix/Linux环境下,命令行下的正则表达式还必须用引号。
Java的正则表达式是由java.util.regex的Pattern和Matcher类实现的。Pattern对象表示经编译的正则表达式。静态的compile( )方法负责将表示正则表达式的字符串编译成Pattern对象。正如上述例程所示的,只要给Pattern的matcher( )方法送一个字符串就能获取一个Matcher对象。此外,Pattern还有一个能快速判断可否在input里面找到regex的 staticboolean matches(?regex, ?input) 以及能返回String数组的split( )方法,它能用regex把字符串分割开来。 只要给Pattern.matcher( )方法传一个字符串就能得到Matcher对象了。接下来就能用Matcher的方法来查询匹配的结果了。 boolean matches()boolean lookingAt()boolean find()boolean find(int start) matches( )的前提是Pattern匹配整个字符串,而lookingAt( )的意思是Pattern匹配字符串的开头。 find( )Matcher.find( )的功能是发现CharSequence里的,与pattern相匹配的多个字符序列。例如:
"//w+"的意思是"一个或多个单词字符",所以它会将字符串直接分解成单词。find( )像一个迭代器,从头至尾扫描一遍字符串。第二个find( )是带int参数的,正如你所看到的,它会告诉方法从哪里开始找——即从参数位置开始查找。 GroupsGroup是指里用括号括起来的,能被后面的表达式调用的正则表达式。Group 0 表示整个表达式,group 1表示第一个被括起来的group,以此类推。因此; A(B(C))D 里面有三个group:group 0是ABCD, group 1是BC,group 2是C。 你能够用下述Matcher方法来使用group: public int groupCount( )返回matcher对象中的group的数目。不包括group0。 public String group( ) 返回上次匹配操做(比方说find( ))的group 0(整个匹配) public String group(int i)返回上次匹配操做的某个group。若是匹配成功,可是没能找到group,则返回null。 public int start(int group)返回上次匹配所找到的,group的开始位置。 public int end(int group)返回上次匹配所找到的,group的结束位置,最后一个字符的下标加一。
这首诗是Through the Looking Glass的,Lewis Carroll的"Jabberwocky"的第一部分。能够看到这个正则表达式里有不少用括号括起来的group,它是由任意多个连续的非空字符('/S+')和任意多个连续的空格字符('/s+')所组成的,其最终目的是要捕获每行的最后三个单词;'$'表示一行的结尾。可是'$'一般表示整个字符串的结尾,因此这里要明确地告诉正则表达式注意换行符。这一点是由'(?m)'标志完成的(模式标志会过一会讲解)。 start( )和end( )若是匹配成功,start( )会返回这次匹配的开始位置,end( )会返回这次匹配的结束位置,即最后一个字符的下标加一。若是以前的匹配不成功(或者没匹配),那么不管是调用start( )仍是end( ),都会引起一个IllegalStateException。下面这段程序还演示了matches( )和lookingAt( ):
注意,只要字符串里有这个模式,find( )就能把它给找出来,可是lookingAt( )和matches( ),只有在字符串与正则表达式一开始就相匹配的状况下才能返回true。matches( )成功的前提是正则表达式与字符串彻底匹配,而lookingAt( )成功的前提是,字符串的开始部分与正则表达式相匹配。 匹配的模式(Pattern flags)compile( )方法还有一个版本,它须要一个控制正则表达式的匹配行为的参数:
flag的取值范围以下:
在这些标志里面,Pattern.CASE_INSENSITIVE,Pattern.MULTILINE,以及Pattern.COMMENTS是最有用的(其中Pattern.COMMENTS还能帮咱们把思路理清楚,而且/或者作文档)。注意,你能够用在表达式里插记号的方式来启用绝大多数的模式。这些记号就在上面那张表的各个标志的下面。你但愿模式从哪里开始启动,就在哪里插记号。 能够用"OR" ('|')运算符把这些标志合使用:
这样建立出来的正则表达式就能匹配以"java","Java","JAVA"...开头的字符串了。此外,若是字符串分好几行,那它还会对每一行作匹配(匹配始于字符序列的开始,终于字符序列当中的行结束符)。注意,group( )方法仅返回匹配的部分。 split( )所谓分割是指将以正则表达式为界,将字符串分割成String数组。
这是一种既快又方便地将文本根据一些常见的边界标志分割开来的方法。
第二个split( )会限定分割的次数。 正则表达式是如此重要,以致于有些功能被加进了String类,其中包括split( )(已经看到了),matches( ),replaceFirst( )以及replaceAll( )。这些方法的功能同Pattern和Matcher的相同。 替换操做正则表达式在替换文本方面特别在行。下面就是一些方法: replaceFirst(String replacement)将字符串里,第一个与模式相匹配的子串替换成replacement。 replaceAll(String replacement),将输入字符串里全部与模式相匹配的子串所有替换成replacement。 appendReplacement(StringBuffer sbuf, String replacement)对sbuf进行逐次替换,而不是像replaceFirst( )或replaceAll( )那样,只替换第一个或所有子串。这是个很是重要的方法,由于它能够调用方法来生成replacement(replaceFirst( )和replaceAll( )只容许用固定的字符串来充当replacement)。有了这个方法,你就能够编程区分group,从而实现更强大的替换功能。 调用完appendReplacement( )以后,为了把剩余的字符串拷贝回去,必须调用appendTail(StringBuffer sbuf, String replacement)。 下面咱们来演示一下怎样使用这些替换方法。说明一下,这段程序所处理的字符串是它本身开头部分的注释,是用正则表达式提取出来并加以处理以后再传给替换方法的。
用TextFile.read( )方法来打开和读取文件。mInput的功能是匹配'/*!' 和 '!*/' 之间的文本(注意一下分组用的括号)。接下来,咱们将全部两个以上的连续空格全都替换成一个,而且将各行开头的空格全都去掉(为了让这个正则表达式能对全部的行,而不只仅是第一行起做用,必须启用多行模式)。这两个操做都用了String的replaceAll( )(这里用它更方便)。注意,因为每一个替换只作一次,所以除了预编译Pattern以外,程序没有额外的开销。 replaceFirst( )只替换第一个子串。此外,replaceFirst( )和replaceAll( )只能用常量(literal)来替换,因此若是每次替换的时候还要进行一些操做的话,它们是无能为力的。碰到这种状况,得用appendReplacement( ),它能在进行替换的时候想写多少代码就写多少。在上面那段程序里,建立sbuf的过程就是选group作处理,也就是用正则表达式把元音字母找出来,而后换成大写的过程。一般你得在完成所有的替换以后才调用appendTail( ),可是若是要模仿replaceFirst( )(或"replace n")的效果,你也能够只替换一次就调用appendTail( )。它会把剩下的东西全都放进sbuf。 你还能够在appendReplacement( )的replacement参数里用"$g"引用已捕获的group,其中'g' 表示group的号码。不过这是为一些比较简单的操做准备的,于是其效果没法与上述程序相比。 reset( )此外,还能够用reset( )方法给现有的Matcher对象配上个新的CharSequence。
若是不给参数,reset( )会把Matcher设到当前字符串的开始处。 |
若是你曾经用过Perl或任何其余内建正则表达式支持的语言,你必定知道用正则表达式处理文本和匹配模式是多么简单。若是你不熟悉这个术语,那么“正则表达式”(Regular Expression)就是一个字符构成的串,它定义了一个用来搜索匹配字符串的模式。 |
许多语言,包括Perl、PHP、Python、JavaScript和JScript,都支持用正则表达式处理文本,一些文本编辑器用正则表达式实现高级“搜索-替换”功能。那么Java又怎样呢?本文写做时,一个包含了用正则表达式进行文本处理的Java规范需求(Specification Request)已经获得承认,你能够期待在JDK的下一版本中看到它。 |
然而,若是如今就须要使用正则表达式,又该怎么办呢?你能够从Apache.org下载源代码开放的Jakarta-ORO库。本文接下来的内容先简要地介绍正则表达式的入门知识,而后以Jakarta-ORO API为例介绍如何使用正则表达式。 |
1、正则表达式基础知识 |
咱们先从简单的开始。假设你要搜索一个包含字符“cat”的字符串,搜索用的正则表达式就是“cat”。若是搜索对大小写不敏感,单词“catalog”、“Catherine”、“sophisticated”均可以匹配。也就是说: |
![]() |
1.1 句点符号 |
假设你在玩英文拼字游戏,想要找出三个字母的单词,并且这些单词必须以“t”字母开头,以“n”字母结束。另外,假设有一本英文字典,你能够用正则表达式搜索它的所有内容。要构造出这个正则表达式,你可使用一个通配符——句点符号“.”。这样,完整的表达式就是“t.n”,它匹配“tan”、“ten”、“tin”和“ton”,还匹配“t#n”、“tpn”甚至“t n”,还有其余许多无心义的组合。这是由于句点符号匹配全部字符,包括空格、Tab字符甚至换行符: |
![]() |
1.2 方括号符号 |
为了解决句点符号匹配范围过于普遍这一问题,你能够在方括号(“[]”)里面指定看来有意义的字符。此时,只有方括号里面指定的字符才参与匹配。也就是说,正则表达式“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配,由于在方括号以内你只能匹配单个字符: |
![]() |
1.3 “或”符号 |
若是除了上面匹配的全部单词以外,你还想要匹配“toon”,那么,你可使用“|”操做符。“|”操做符的基本意义就是“或”运算。要匹配“toon”,使用“t(a|e|i|o|oo)n”正则表达式。这里不能使用方扩号,由于方括号只容许匹配单个字符;这里必须使用圆括号“()”。圆括号还能够用来分组,具体请参见后面介绍。 |
![]() |
1.4 表示匹配次数的符号 |
表一显示了表示匹配次数的符号,这些符号用来肯定紧靠该符号左边的符号出现的次数: |
假设咱们要在文本文件中搜索美国的社会安全号码。这个号码的格式是999-99-9999。用来匹配它的正则表达式如图一所示。在正则表达式中,连字符(“-”)有着特殊的意义,它表示一个范围,好比从0到9。所以,匹配社会安全号码中的连字符号时,它的前面要加上一个转义字符“/”。 |
图一:匹配全部123-12-1234形式的社会安全号码 |
假设进行搜索的时候,你但愿连字符号能够出现,也能够不出现——即,999-99-9999和999999999都属于正确的格式。这时,你能够在连字符号后面加上“?”数量限定符号,如图二所示: |
图二:匹配全部123-12-1234和123121234形式的社会安全号码 |
下面咱们再来看另一个例子。美国汽车牌照的一种格式是四个数字加上二个字母。它的正则表达式前面是数字部分“[0-9]{4}”,再加上字母部分“[A-Z]{2}”。图三显示了完整的正则表达式。 |
图三:匹配典型的美国汽车牌照号码,如8836KV |
1.5 “否”符号 |
“^”符号称为“否”符号。若是用在方括号内,“^”表示不想要匹配的字符。例如,图四的正则表达式匹配全部单词,但以“X”字母开头的单词除外。 |
图四:匹配全部单词,但“X”开头的除外 |
1.6 圆括号和空白符号 |
假设要从格式为“June 26, 1951”的生日日期中提取出月份部分,用来匹配该日期的正则表达式能够如图五所示: |
图五:匹配全部Moth DD,YYYY格式的日期 |
新出现的“/s”符号是空白符号,匹配全部的空白字符,包括Tab字符。若是字符串正确匹配,接下来如何提取出月份部分呢?只需在月份周围加上一个圆括号建立一个组,而后用ORO API(本文后面详细讨论)提取出它的值。修改后的正则表达式如图六所示: |
图六:匹配全部Month DD,YYYY格式的日期,定义月份值为第一个组 |
1.7 其它符号 |
为简便起见,你可使用一些为常见正则表达式建立的快捷符号。如表二所示: |
表二:经常使用符号 |
例如,在前面社会安全号码的例子中,全部出现“[0-9]”的地方咱们均可以使用“/d”。修改后的正则表达式如图七所示: |
图七:匹配全部123-12-1234格式的社会安全号码 |
2、Jakarta-ORO库 |
有许多源代码开放的正则表达式库可供Java程序员使用,并且它们中的许多支持Perl 5兼容的正则表达式语法。我在这里选用的是Jakarta-ORO正则表达式库,它是最全面的正则表达式API之一,并且它与Perl 5正则表达式彻底兼容。另外,它也是优化得最好的API之一。 |
Jakarta-ORO库之前叫作OROMatcher,Daniel Savarese大方地把它赠送给了Jakarta Project。你能够按照本文最后参考资源的说明下载它。 |
我首先将简要介绍使用Jakarta-ORO库时你必须建立和访问的对象,而后介绍如何使用Jakarta-ORO API。 |
▲ PatternCompiler对象 |
首先,建立一个Perl5Compiler类的实例,并把它赋值给PatternCompiler接口对象。Perl5Compiler是PatternCompiler接口的一个实现,容许你把正则表达式编译成用来匹配的Pattern对象。 |
![]() |
▲ Pattern对象 |
要把正则表达式编译成Pattern对象,调用compiler对象的compile()方法,并在调用参数中指定正则表达式。例如,你能够按照下面这种方式编译正则表达式“t[aeio]n”: |
![]() |
默认状况下,编译器建立一个大小写敏感的模式(pattern)。所以,上面代码编译获得的模式只匹配“tin”、“tan”、 “ten”和“ton”,但不匹配“Tin”和“taN”。要建立一个大小写不敏感的模式,你应该在调用编译器的时候指定一个额外的参数: |
![]() |
建立好Pattern对象以后,你就能够经过PatternMatcher类用该Pattern对象进行模式匹配。 |
▲ PatternMatcher对象 |
PatternMatcher对象根据Pattern对象和字符串进行匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现,它根据Perl 5正则表达式语法进行模式匹配: |
![]() |
使用PatternMatcher对象,你能够用多个方法进行匹配操做,这些方法的第一个参数都是须要根据正则表达式进行匹配的字符串: |
· boolean matches(String input, Pattern pattern):当输入字符串和正则表达式要精确匹配时使用。换句话说,正则表达式必须完整地描述输入字符串。 |
· boolean matchesPrefix(String input, Pattern pattern):当正则表达式匹配输入字符串起始部分时使用。 |
· boolean contains(String input, Pattern pattern):当正则表达式要匹配输入字符串的一部分时使用(即,它必须是一个子串)。 |
另外,在上面三个方法调用中,你还能够用PatternMatcherInput对象做为参数替代String对象;这时,你能够从字符串中最后一次匹配的位置开始继续进行匹配。当字符串可能有多个子串匹配给定的正则表达式时,用PatternMatcherInput对象做为参数就颇有用了。用PatternMatcherInput对象做为参数替代String时,上述三个方法的语法以下: |
· boolean matches(PatternMatcherInput input, Pattern pattern) |
· boolean matchesPrefix(PatternMatcherInput input, Pattern pattern) |
· boolean contains(PatternMatcherInput input, Pattern pattern) |
3、应用实例 |
下面咱们来看看Jakarta-ORO库的一些应用实例。 |
3.1 日志文件处理 |
任务:分析一个Web服务器日志文件,肯定每个用户花在网站上的时间。在典型的BEA WebLogic日志文件中,日志记录的格式以下: |
![]() |
分析这个日志记录,能够发现,要从这个日志文件提取的内容有两项:IP地址和页面访问时间。你能够用分组符号(圆括号)从日志记录提取出IP地址和时间标记。 |
首先咱们来看看IP地址。IP地址有4个字节构成,每个字节的值在0到255之间,各个字节经过一个句点分隔。所以,IP地址中的每个字节有至少一个、最多三个数字。图八显示了为IP地址编写的正则表达式: |
图八:匹配IP地址 |
IP地址中的句点字符必须进行转义处理(前面加上“/”),由于IP地址中的句点具备它原本的含义,而不是采用正则表达式语法中的特殊含义。句点在正则表达式中的特殊含义本文前面已经介绍。 |
日志记录的时间部分由一对方括号包围。你能够按照以下思路提取出方括号里面的全部内容:首先搜索起始方括号字符(“[”),提取出全部不超过结束方括号字符(“]”)的内容,向前寻找直至找到结束方括号字符。图九显示了这部分的正则表达式。 |
图九:匹配至少一个字符,直至找到“]” |
如今,把上述两个正则表达式加上分组符号(圆括号)后合并成单个表达式,这样就能够从日志记录提取出IP地址和时间。注意,为了匹配“- -”(但不提取它),正则表达式中间加入了“/s-/s-/s”。完整的正则表达式如图十所示。 |
图十:匹配IP地址和时间标记 |
如今正则表达式已经编写完毕,接下来能够编写使用正则表达式库的Java代码了。 |
为使用Jakarta-ORO库,首先建立正则表达式字符串和待分析的日志记录字符串: |
![]() |
这里使用的正则表达式与图十的正则表达式差很少彻底相同,但有一点例外:在Java中,你必须对每个向前的斜杠(“/”)进行转义处理。图十不是Java的表示形式,因此咱们要在每一个“/”前面加上一个“/”以避免出现编译错误。遗憾的是,转义处理过程很容易出现错误,因此应该当心谨慎。你能够首先输入未经转义处理的正则表达式,而后从左到右依次把每个“/”替换成“//”。若是要复检,你能够试着把它输出到屏幕上。 |
初始化字符串以后,实例化PatternCompiler对象,用PatternCompiler编译正则表达式建立一个Pattern对象: |
![]() |
如今,建立PatternMatcher对象,调用PatternMatcher接口的contain()方法检查匹配状况: |
![]() |
接下来,利用PatternMatcher接口返回的MatchResult对象,输出匹配的组。因为logEntry字符串包含匹配的内容,你能够看到类以下面的输出: |
![]() |
3.2 HTML处理实例一 |
下面一个任务是分析HTML页面内FONT标记的全部属性。HTML页面内典型的FONT标记以下所示: |
![]() |
程序将按照以下形式,输出每个FONT标记的属性: |
![]() |
在这种状况下,我建议你使用两个正则表达式。第一个如图十一所示,它从字体标记提取出“"face="Arial, Serif" size="+2" color="red"”。 |
图十一:匹配FONT标记的全部属性 |
第二个正则表达式如图十二所示,它把各个属性分割成名字-值对。 |
图十二:匹配单个属性,并把它分割成名字-值对 |
分割结果为: |
![]() |
如今咱们来看看完成这个任务的Java代码。首先建立两个正则表达式字符串,用Perl5Compiler把它们编译成Pattern对象。编译正则表达式的时候,指定Perl5Compiler.CASE_INSENSITIVE_MASK选项,使得匹配操做不区分大小写。 |
接下来,建立一个执行匹配操做的Perl5Matcher对象。 |
![]() |
假设有一个String类型的变量html,它表明了HTML文件中的一行内容。若是html字符串包含FONT标记,匹配器将返回true。此时,你能够用匹配器对象返回的MatchResult对象得到第一个组,它包含了FONT的全部属性: |
![]() |
接下来建立一个PatternMatcherInput对象。这个对象容许你从最后一次匹配的位置开始继续进行匹配操做,所以,它很适合于提取FONT标记内属性的名字-值对。建立PatternMatcherInput对象,以参数形式传入待匹配的字符串。而后,用匹配器实例提取出每个FONT的属性。这经过指定PatternMatcherInput对象(而不是字符串对象)为参数,反复地调用PatternMatcher对象的contains()方法完成。PatternMatcherInput对象之中的每一次迭代将把它内部的指针向前移动,下一次检测将从前一次匹配位置的后面开始。 |
本例的输出结果以下: |
![]() |
3.3 HTML处理实例二 |
下面咱们来看看另外一个处理HTML的例子。这一次,咱们假定Web服务器从widgets.acme.com移到了newserver.acme.com。如今你要修改一些页面中的连接: |
![]() |
执行这个搜索的正则表达式如图十三所示: |
图十三:匹配修改前的连接 |
若是可以匹配这个正则表达式,你能够用下面的内容替换图十三的连接: |
![]() |
注意#字符的后面加上了$1。Perl正则表达式语法用$一、$2等表示已经匹配且提取出来的组。图十三的表达式把全部做为一个组匹配和提取出来的内容附加到连接的后面。 |
如今,返回Java。就象前面咱们所作的那样,你必须建立测试字符串,建立把正则表达式编译到Pattern对象所必需的对象,以及建立一个PatternMatcher对象:![]() |
接下来,用com.oroinc.text.regex包Util类的substitute()静态方法进行替换,输出结果字符串: |
![]() |
Util.substitute()方法的语法以下: |
![]() |
这个调用的前两个参数是之前建立的PatternMatcher和Pattern对象。第三个参数是一个Substiution对象,它决定了替换操做如何进行。本例使用的是Perl5Substitution对象,它可以进行Perl5风格的替换。第四个参数是想要进行替换操做的字符串,最后一个参数容许指定是否替换模式的全部匹配子串(Util.SUBSTITUTE_ALL),或只替换指定的次数。 |
【结束语】在这篇文章中,我为你介绍了正则表达式的强大功能。只要正确运用,正则表达式可以在字符串提取和文本修改中起到很大的做用。另外,我还介绍了如何在Java程序中经过Jakarta-ORO库利用正则表达式。至于最终采用老式的字符串处理方式(使用StringTokenizer,charAt,和substring),仍是采用正则表达式,这就有待你本身决定了。 |
Jakarta-ORO篇
陈广佳 (cgjmail@163.net)
电子信息工程系工科学士
2001 年 12 月
因为工做的须要,本人常常要面对大量的文字电子资料的整理工做,所以曾对在JAVA中正则表达式的应用有所关注,并对其有必定的了解,但愿经过本文与同行进行有关方面的心得交流。
正则表达式:
正则表达式是一种能够用于模式匹配和替换的强有力的工具,一个正则表达式就是由普通的字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式,它描述在查找文字主体时待匹配的一个或多个字符串。正则表达式做为一个模板,将某个字符模式与所搜索的字符串进行匹配。
正则表达式在字符数据处理中起着很是重要的做用,咱们能够用正则表达式完成大部分的数据分析处理工做,如:判断一个串是不是数字、是不是有效的Email地址,从海量的文字资料中提取有价值的数据等等,若是不使用正则表达式,那么实现的程序可能会很长,而且容易出错。对这点本人深有体会,面对大量工具书电子档资料的整理工做,若是不懂得应用正则表达式来处理,那么将是很痛苦的一件事情,反之则将能够轻松地完成,得到事半功倍的效果。
因为本文目的是要介绍如何在JAVA里运用正则表达式,所以对刚接触正则表达式的读者请参考有关资料,在此因篇幅有限不做介绍。
JAVA对正则表达式的支持:
在JDK1.3或以前的JDK版本中并无包含正则表达式库可供JAVA程序员使用,以前咱们通常都在使用第三方提供的正则表达式库,这些第三方库中有源代码开放的,也有需付费购买的,而现时在JDK1.4的测试版中也已经包含有正则表达式库---java.util.regex。
故此如今咱们有不少面向JAVA的正则表达式库可供选择,如下我将介绍两个较具表明性的 Jakarta-ORO和java.util.regex,首先固然是本人一直在用的 Jakarta-ORO:
1.简介:
Jakarta-ORO是最全面以及优化得最好的正则表达式API之一,Jakarta-ORO库之前叫作OROMatcher,是由Daniel F. Savarese编写,后来他将其赠与Jakarta Project,读者可在Apache.org的网站下载该API包。
许多源代码开放的正则表达式库都是支持Perl5兼容的正则表达式语法,Jakarta-ORO正则表达式库也不例外,他与Perl 5正则表达式彻底兼容。
2.对象与其方法:
★PatternCompiler对象:
咱们在使用Jakarta-ORO API包时,最早要作的是,建立一个Perl5Compiler类的实例,并把它赋值给PatternCompiler接口对象。Perl5Compiler是PatternCompiler接口的一个实现,容许你把正则表达式编译成用来匹配的Pattern对象。
PatternCompiler compiler=new Perl5Compiler();
★Pattern对象:
要把所对应的正则表达式编译成Pattern对象,须要调用compiler对象的compile()方法,并在调用参数中指定正则表达式。举个例子,你能够按照下面这种方式编译正则表达式"s[ahkl]y":
Pattern pattern=null; try { pattern=compiler.compile("s[ahkl]y "); } catch (MalformedPatternException e) { e.printStackTrace(); } |
在默认的状况下,编译器会建立一个对大小写敏感的模式(pattern)。所以,上面代码编译获得的模式只匹配"say"、"shy"、 "sky"和"sly",但不匹配"Say"和"skY"。要建立一个大小写不敏感的模式,你应该在调用编译器的时候指定一个额外的参数:
pattern=compiler.compile("s[ahkl]y",Perl5Compiler.CASE_INSENSITIVE_MASK);
Pattern对象建立好以后,就能够经过PatternMatcher类用该Pattern对象进行模式匹配。
★PatternMatcher对象:
PatternMatcher对象依据Pattern对象和字符串展开匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现,它根据Perl 5正则表达式语法进行模式匹配:
PatternMatcher matcher=new Perl5Matcher();
PatternMatcher对象提供了多个方法进行匹配操做,这些方法的第一个参数都是须要根据正则表达式进行匹配的字符串:
但以上三种方法只会查找输入字符串中匹配正则表达式的第一个对象,若是当字符串可能有多个子串匹配给定的正则表达式时,那么你就能够在调用上面三个方法时用PatternMatcherInput对象做为参数替代String对象,这样就能够从字符串中最后一次匹配的位置开始继续进行匹配,这样就方便的多了。
用PatternMatcherInput对象做为参数替代String时,上述三个方法的语法以下:
★Util.substitute()方法:
查找后须要要进行替换,咱们就要用到Util.substitute()方法,其语法以下:
public static String substitute(PatternMatcher matcher, Pattern pattern,Substitution sub,String input, int numSubs) |
前两个参数分别为PatternMatcher和Pattern对象。而第三个参数是个Substiution对象,由它来决定替换操做如何进行。第四个参数是要进行替换操做的目标字符串,最后一个参数用来指定是否替换模式的全部匹配子串(Util.SUBSTITUTE_ALL),或只进行指定次数的替换。
在这里我相信有必要详细解说一下第三个参数Substiution对象,由于它将决定替换将怎样进行。
Substiution:
Substiution是一个接口类,它为你提供了在使用Util.substitute()方法时控制替换方式的手段,它有两个标准的实现类:StringSubstitution与Perl5Substitution。固然,同时你也能够生成本身的实现类来定制你所须要的特殊替换动做。
StringSubstitution:
StringSubstitution 实现的是简单的纯文字替换手段,它有两个构造方法:
StringSubstitution()->缺省的构造方法,初始化一个包含零长度字符串的替换对象。
StringSubstitution(java.lang.String substitution)->初始化一个给定字符串的替换对象。
Perl5Substitution:
Perl5Substitution 是StringSubstitution的子类,它在实现纯文字替换手段的同时也容许进行针对MATH类里各匹配组的PERL5变量的替换,因此他的替换手段比其直接父类StringSubstitution更为多元化。
它有三个构造器:
Perl5Substitution()
Perl5Substitution(java.lang.String substitution)
Perl5Substitution(java.lang.String substitution, int numInterpolations)
前两种构造方法与StringSubstitution同样,而第三种构造方法下面将会介绍到。
在Perl5Substitution的替换字符串中能够包含用来替代在正则表达式里由小扩号围起来的匹配组的变量,这些变量是由$1, $2,$3等形式来标识。咱们能够用一个例子来解释怎样使用替换变量来进行替换:
假设咱们有正则表达式模式为b/d+:(也就是b[0-9]+:),而咱们想把全部匹配的字符串中的"b"都改成"a",而":"则改成"-",而其他部分则不做修改,如咱们输入字符串为"EXAMPLE b123:",通过替换后就应该变成"EXAMPLE a123-"。要作到这点,咱们就首先要把不作替换的部分用分组符号小括号包起来,这样正则表达式就变为"b(/d+):",而构造Perl5Substitution对象时其替换字符串就应该是"a$1-",也就是构造式为Perl5Substitution("a$1-"),表示在使用Util.substitute()方法时只要在目标字符串里找到和正则表达式" b(/d+): "相匹配的子串都用替换字符串来替换,而变量$1表示若是和正则表达式里第一个组相匹配的内容则照般原文插到$1所在的为置,如在"EXAMPLE b123:"中和正则表达式相匹配的部分是"b123:",而其中和第一分组"(/d+)"相匹配的部分则是"123",因此最后替换结果为"EXAMPLE a123-"。
有一点须要清楚的是,若是你把构造器Perl5Substitution(java.lang.String substitution,int numInterpolations)
中的numInterpolations参数设为INTERPOLATE_ALL,那么当每次找到一个匹配字串时,替换变量($1,$2等)所指向的内容都根据目前匹配字串来更新,可是若是numInterpolations参数设为一个正整数N时,那么在替换时就只会在前N次匹配发生时替换变量会跟随匹配对象来调整所表明的内容,但N次以后就以一致以第N次替换变量所表明内容来作为之后替换结果。
举个例子会更好理解:
假如沿用以上例子中的正则表达式模式以及替换内容来进行替换工做,设目标字符串为"Tank b123: 85 Tank b256: 32 Tank b78: 22",而且设numInterpolations参数为INTERPOLATE_ALL,而Util.substitute()方法中的numSub变量设为SUBSTITUTE_ALL(请参考上文Util.substitute()方法内容),那么你得到的替换结果将会是:
Tank a123- 85 Tank a256- 32 Tank a78- 22
可是若是你把numInterpolations设为2,而且numSubs依然设为SUBSTITUTE_ALL,那么这时你得到的结果则会是:
Tank a123- 85 Tank a256- 32 Tank a256- 22
你要注意到最后一个替换所用变量$1所表明的内容与第二个$1同样为"256",而不是预期的"78",由于在替换进行中,替换变量$1只根据匹配内容进行了两次更新,最后一次就使第二次匹配时所更新的结果,那么咱们能够由此知道,若是numInterpolations设为1,那么结果将是:
Tank a123- 85 Tank a123- 32 Tank a123- 22
3.应用示例:
恰好前段时间公司准备出一个《伊索预言》的英语学习互动教材,其中有电子档资料的整理工做,咱们就以此为例来看一下Jakarta-ORO与JDBC2.0 API结合起来对数据库内的资料进行简单提取与整理的实现。假设由录入部的同事送过来的存放在MS SQLSERVER 7数据库里的电子档的表结构以下(注:或许在不一样的DBMS中有相应的正则表达式的应用,但这不在本文讨论范围内):
表名:AESOP, 表中每条记录包含有三列:
ID(int):单词索引号
WORD(varchar):单词
CONTENT(varchar):存放单词的相关解释与例句等内容
其中CONTENT列中内容的格式以下:
[音标] [词性] (解释){(例句一/例句解释/例句中该词的词性: 单词在句中的意思) (例句二/例句解释/例句中该词的词性: 单词在句中的意思)}
如对应单词Kevin,CONTENT中的内容以下:
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now./凯文现住在珠海/名词: 凯文)}
咱们的例子主要针对CONTENT列中内容进行字符串处理。
★查找单个匹配:
首先,让咱们尝试把CONTNET列中的[音标]字段的内容列示出来,因为全部单词的记录中都有这一项而且都在字串开始位置,因此这个查找工做比较简单:
这个是很简单的正则表达式,其意思是要求相匹配的字符串必须为以一对中括号包含的全部内容,如['kevin] 、[名词]等,但内容中不包括"]"符号,也就是要避免出现"[][]"会做为一个匹配对象的状况出现(有关正则表达式的基础知识请参照有关资料,这里再也不详述)。
注意,在Java中,你必须对每个向前的斜杠("/")进行转义处理。因此咱们要在上面的正则表达式里每一个"/"前面加上一个"/"以避免出现编译错误,也就是在JAVA中初始化正则表达式的字符串的语句应该为:
String restring=" //[[^]]+//]";
而且在表达式里每一个符号中间不能有空格,不然就会一样出现编译错误。
PatternCompiler compiler=new Perl5Compiler();
Pattern pattern=compiler.compile(restring);
PatternMatcher matcher=new Perl5Matcher(); if (matcher.contains(content,pattern)) { //处理代码片断 } |
这里matcher.contains(content,pattern)中的参数 content是从数据库里取来的字符串变量。该方法只会查到第一个匹配的对象字符串,可是因为音标项均在CONETNET内容字符串中的起始位置,因此用这个方法就已经能够保证把每条记录里的音标项找出来了,但更为直接与合理的办法是使用boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)方法,该方法验证目标字符串是否以正则表达式所匹配的字串为起始。
具体实现的完整的程序代码以下:
package RegularExpressions; //import…… import org.apache.oro.text.regex.*; //使用Jakarta-ORO正则表达式库前须要把它加到CLASSPATH里面,若是用IDE是//JBUILDER,那么也能够在JBUILDER里直接自建新库。 public class yisuo{ public static void main(String[] args){ try{ //使用JDBC DRIVER进行DBMS链接,这里我使用的是一个第三方JDBC //DRIVER,Microsoft自己也有一个面向SQLSERVER7/2000的免费JDBC //DRIVER,但其性能真的是奇差,不用也罢。 Class.forName("com.jnetdirect.jsql.JSQLDriver"); Connection con=DriverManager.getConnection ("jdbc:JSQLConnect://kevin:1433","kevin chen","re"); Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); //为使用Jakarta-ORO库而建立相应的对象 String rsstring=" //[[^]]+//]"; PatternCompiler orocom=new Perl5Compiler(); Pattern pattern=orocom.compile(rsstring); PatternMatcher matcher=new Perl5Matcher(); ResultSet uprs = stmt.executeQuery("SELECT * FROM aesop"); while (uprs.next()) { Stirng word=uprs.getString("word"); Stirng content=uprs.getString("content"); if(matcher.contains(content,pattern)){ //或if(matcher.matchesPrefix(content,pattern)){ MatchResult result=matcher.getMatch(); Stirng pure=result.toString(); System.out.println(word+"的音标为:"+pure); } } } catch(Exception e) { System.out.println(e); } } } |
输出结果为:kevin的音标为['kevin]
在这个处理中我是用toString()方法来取得结果,可是若是正则表达式里是用了分组符号(圆括号),那么就能够用group(int gid)的方法来取得相应各组匹配的结果,如正则表达式改成" (/[[^]]+/])",那么就能够用如下方法来取得结果:pure=result.group(0);
用程序验证,输出结果一样为:kevin的音标为['kevin]
而若是正则表达式为(/[[^]]+/])(/[[^]]+/]),则会查找到两个连续的方括号所包含的内容,也就找到[音标] [词性]两项,可是两项的结果分别在两个组里面,分别由下面语句得到结果:
result.group(0)->返回[音标] [词性]两项内容,也就是与整个正则表达式相匹配的结果字符串,在这里也就为['kevin] [名词]
result.group(1) ->返回[音标]项内容,结果应是['kevin]
result.group(2) ->返回[词性]项内容,结果应是[名词]
继续用程序验证,发现输出并不正确,主要是当内容有中文时就不能成功匹配,考虑到多是Jakarta-ORO正则表达式库版本不支持中文的问题,回看一下原来我一直用的仍是2.0.1的老版本,立刻到Jakarta.org上下载最新的2.0.4版本装上再用程序验证,得出的结果就和预期同样正确。
★查找多个匹配:
通过第一步的尝试使用Jakarta-ORO后,咱们已经知道了如何正确使用该API包来查找目标字符串里一个匹配的子串,下面咱们接着来看一看当目标字符串里包含不止一个匹配的子串时咱们如何把它们一个接一个找出来进行相应的处理。
首先咱们先试个简单的应用,假设咱们想把CONTNET字段内容里全部用方括号包起来的字串都找出来,很清楚地,CONTNET字段的内容里面就只有两项匹配的内容:[音标]和 [词性],刚才咱们其实已经把它们分别找出来了,可是咱们所用的方法是分组方法,把"[音标] [词性]"做为一整个正则表达式匹配的内容先找到,再根据分组把[音标]和 [词性]分别挑出来。可是如今咱们须要作的是把[音标]和[词性]分别作为与同一个正则表达式匹配的内容,先找到一个接着再找下一个,也就是刚才咱们的表达式为(/[[^]]+/])(/[[^]]+/]),而如今应为" /[[^]]+/] "。
咱们已经知道在匹配操做的三个方法里只要用PatternMatcherInput对象做为参数替代String对象就能够从字符串中最后一次匹配的位置开始继续进行匹配,实现的程序片断以下:
PatternMatcherInput input=new PatternMatcherInput(content); while (matcher.contains(input,pattern)) { result=matcher.getMatch(); System.out.println(result.group(0)) } |
接着咱们来作复杂一点的处理,就是咱们要先把下面内容:
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now. /凯文现住在珠海/名词: 凯文)}中的整个例句部分(也就是由大括号所包含的部分)找出来,再分别把例句一和例句二找出,而各例句中的各项内容(英文句、中文句、词性、解释)也要分项列出。
第一步固然是要定出相应的正则表达式,须要有两个,一是和整个例句部分(也就是由大括号包起来的部分)匹配的正则表达式:"/{.+/}",
另外一个则要和每一个例句部分匹配(也就是小括号中的内容),:/(([^)]+/)
并且因为要把例句的各项分离出来,因此要再把里面的各部分用分组的方法匹配出来:" ([^(]+)/(.+)/(.+):([^)]+) "。
为了简便起见,咱们再也不和从数据库里读出,而是构造一个包含一样内容的字符串变量,程序片断以下:
try{ String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词:凯文) (Kevin is living in ZhuHai now./凯文现住在珠海/名词: 凯文)}"; String ps1="//{.+//}"; String ps2="//([^)]+//)"; String ps3="([^(]+)/(.+)/(.+):([^)]+)"; String sentence; PatternCompiler orocom=new Perl5Compiler(); Pattern pattern1=orocom.compile(ps1); Pattern pattern2=orocom.compile(ps2); Pattern pattern3=orocom.compile(ps3); PatternMatcher matcher=new Perl5Matcher(); //先找出整个例句部分 if (matcher.contains(content,pattern1)) { MatchResult result=matcher.getMatch(); String example=result.toString(); PatternMatcherInput input=new PatternMatcherInput(example); //分别找出例句一和例句二 while (matcher.contains(input,pattern2)){ result=matcher.getMatch(); sentence=result.toString(); //把每一个例句里的各项用分组的办法分隔出来 if (matcher.contains(sentence,pattern3)){ result=matcher.getMatch(); System.out.println("英文句: "+result.group(1)); System.out.println("句子中文翻译: "+result.group(2)); System.out.println("词性: "+result.group(3)); System.out.println("意思: "+result.group(4)); } } } } catch(Exception e) { System.out.println(e); } |
输出结果为:
英文句: Kevin loves comic.
句子中文翻译: 凯文爱漫画
词性: 名词
意思: 凯文
英文句: Kevin is living in ZhuHai now.
句子中文翻译: 凯文现住在珠海
词性: 名词
意思: 凯文
★查找替换:
以上的两个应用都是单纯在查找字符串匹配方面的,咱们再来看一下查找后如何对目标字符串进行替换。
例如我如今想把第二个例句进行改动,换为:Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。/名词:凯文。
也就是把
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now. /凯文现住在珠海/名词: 凯文)}
改成:
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。/名词:凯文。)}
以前,咱们已经了解Util.substitute()方法与Substiution接口,以及Substiution的两个实现类StringSubstitution和Perl5Substitution,咱们就来看看怎么用Util.substitute()方法配合Perl5Substitution来完成咱们上面提出的替换要求,肯定正则表达式:
咱们要先找到其中的整个例句部分,也就是由大括号包起来的字串,而且把两个例句分别分组,因此正则表达式为:"/{(/([^)]+/))(/([^)]+/))/}",若是用替换变量来代替分组,那么上面的表达式能够看为"/{$1$2/}",这样就能够更容易看出替换变量与分组间的关系。
根据上面的正则表达式Perl5Substitution类能够这样构造:
Perl5Substitution("{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。/名词:凯文。)}")
再根据这个Perl5Substitution对象来使用Util.substitute()方法即可以完成替换了,实现的代码片断以下:
try{ String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)(Kevin lives in ZhuHai now./凯文现住在珠海/名词: 凯文)}"; String ps1="//{(//([^)]+//))(//([^)]+//))//}"; String sentence; String pure; PatternCompiler orocom=new Perl5Compiler(); Pattern pattern1=orocom.compile(ps1); PatternMatcher matcher=new Perl5Matcher(); String result=Util.substitute(matcher, pattern1,new Perl5Substitution( "{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。/名词:凯文。)}",1), content,Util.SUBSTITUTE_ALL); System.out.println(result); } catch(Exception e) { System.out.println(e); } |
输出结果是正确的,为:
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。/名词:凯文。)}
至于有关使用numInterpolations参数的构造器用法,读者只要根据上面的介绍本身动手试一下就会清楚了,在此就再也不例述。
总结:
本文首先介绍了Jakarta-ORO正则表达式库的对象与方法,而且接着举例让读者对实际应用有进一步的了解,虽然例子都比较简单,但但愿读者们在看了该文后对Jakarta-ORO正则表达式库有必定的认知,在实际工做中有所帮助与启发。
其实在Jakarta org里除了Jakarta-ORO外还有一个百分百的纯JAVA正则表达式库,就是由Jonathan Locke赠与Jakarta ORG的Regexp,在该包里面包含了完整的文档以及一个用于调试的Applet例子,对其有兴趣的读者能够到此下载。
关于做者 陈广佳 Kevin Chen,汕头大学电子信息工程系工科学士,台湾大新出版社珠海区开发部,现正围绕中日韩电子资料使用JAVA开发电子词典等相关项目。可经过E-mail:cgjmail@163.net于他联系。 |
陈广佳 (cgjmail@163.net)
电子信息工程系工科学士
2001 年 12 月
如今JDK1.4里终于有了本身的正则表达式API包,JAVA程序员能够免去找第三方提供的正则表达式库的周折了,咱们如今就立刻来了解一下这个SUN提供的迟来恩物- -对我来讲确实如此。
1.简介:
java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工做的类库包。
它包括两个类:Pattern和Matcher
Pattern | 一个Pattern是一个正则表达式经编译后的表现模式。 |
Matcher | 一个Matcher对象是一个状态机器,它依据Pattern对象作为匹配模式对字符串展开匹配检查。 |
首先一个Pattern实例订制了一个所用语法与PERL的相似的正则表达式经编译后的模式,而后一个Matcher实例在这个给定的Pattern实例的模式控制下进行字符串的匹配工做。
如下咱们就分别来看看这两个类:
2.Pattern类:
Pattern的方法以下:
static Pattern | compile(String regex) 将给定的正则表达式编译并赋予给Pattern类 |
static Pattern | compile(String regex, int flags) 同上,但增长flag参数的指定,可选的flag参数包括:CASE INSENSITIVE,MULTILINE,DOTALL,UNICODE CASE, CANON EQ |
int | flags() 返回当前Pattern的匹配flag参数. |
Matcher | matcher(CharSequence input) 生成一个给定命名的Matcher对象 |
static boolean | matches(String regex, CharSequence input) 编译给定的正则表达式而且对输入的字串以该正则表达式为模开展匹配,该方法适合于该正则表达式只会使用一次的状况,也就是只进行一次匹配工做,由于这种状况下并不须要生成一个Matcher实例。 |
String | pattern() 返回该Patter对象所编译的正则表达式。 |
String[] | split(CharSequence input) 将目标字符串按照Pattern里所包含的正则表达式为模进行分割。 |
String[] | split(CharSequence input, int limit) 做用同上,增长参数limit目的在于要指定分割的段数,如将limi设为2,那么目标字符串将根据正则表达式分为割为两段。 |
一个正则表达式,也就是一串有特定意义的字符,必须首先要编译成为一个Pattern类的实例,这个Pattern对象将会使用 matcher()方法来生成一个Matcher实例,接着即可以使用该 Matcher实例以编译的正则表达式为基础对目标字符串进行匹配工做,多个Matcher是能够共用一个Pattern对象的。
如今咱们先来看一个简单的例子,再经过分析它来了解怎样生成一个Pattern对象而且编译一个正则表达式,最后根据这个正则表达式将目标字符串进行分割:
import java.util.regex.*; public class Replacement{ public static void main(String[] args) throws Exception { // 生成一个Pattern,同时编译一个正则表达式 Pattern p = Pattern.compile("[/]+"); //用Pattern的split()方法把字符串按"/"分割 String[] result = p.split( "Kevin has seen《LEON》seveal times,because it is a good film." +"/ 凯文已经看过《这个杀手不太冷》几回了,由于它是一部" +"好电影。/名词:凯文。"); for (int i=0; i<result.length; i++) System.out.println(result[i]); } } |
输出结果为:
Kevin has seen《LEON》seveal times,because it is a good film.
凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。
名词:凯文。
很明显,该程序将字符串按"/"进行了分段,咱们如下再使用 split(CharSequence input, int limit)方法来指定分段的段数,程序改动为:
tring[] result = p.split("Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。/名词:凯文。",2);
这里面的参数"2"代表将目标语句分为两段。
输出结果则为:
Kevin has seen《LEON》seveal times,because it is a good film.
凯文已经看过《这个杀手不太冷》几回了,由于它是一部好电影。/名词:凯文。
由上面的例子,咱们能够比较出java.util.regex包在构造Pattern对象以及编译指定的正则表达式的实现手法与咱们在上一篇中所介绍的Jakarta-ORO 包在完成一样工做时的差异,Jakarta-ORO 包要先构造一个PatternCompiler类对象接着生成一个Pattern对象,再将正则表达式用该PatternCompiler类的compile()方法来将所需的正则表达式编译赋予Pattern类:
PatternCompiler orocom=new Perl5Compiler();
Pattern pattern=orocom.compile("REGULAR EXPRESSIONS");
PatternMatcher matcher=new Perl5Matcher();
可是在java.util.regex包里,咱们仅需生成一个Pattern类,直接使用它的compile()方法就能够达到一样的效果:
Pattern p = Pattern.compile("[/]+");
所以彷佛java.util.regex的构造法比Jakarta-ORO更为简洁并容易理解。
3.Matcher类:
Matcher方法以下:
Matcher | appendReplacement(StringBuffer sb, String replacement) 将当前匹配子串替换为指定字符串,而且将替换后的子串以及其以前到上次匹配子串以后的字符串段添加到一个StringBuffer对象里。 |
StringBuffer | appendTail(StringBuffer sb) 将最后一次匹配工做后剩余的字符串添加到一个StringBuffer对象里。 |
int | end() 返回当前匹配的子串的最后一个字符在原目标字符串中的索引位置 。 |
int | end(int group) 返回与匹配模式里指定的组相匹配的子串最后一个字符的位置。 |
boolean | find() 尝试在目标字符串里查找下一个匹配子串。 |
boolean | find(int start) 重设Matcher对象,而且尝试在目标字符串里从指定的位置开始查找下一个匹配的子串。 |
String | group() 返回当前查找而得到的与组匹配的全部子串内容 |
String | group(int group) 返回当前查找而得到的与指定的组匹配的子串内容 |
int | groupCount() 返回当前查找所得到的匹配组的数量。 |
boolean | lookingAt() 检测目标字符串是否以匹配的子串起始。 |
boolean | matches() 尝试对整个目标字符展开匹配检测,也就是只有整个目标字符串彻底匹配时才返回真值。 |
Pattern | pattern() 返回该Matcher对象的现有匹配模式,也就是对应的Pattern 对象。 |
String | replaceAll(String replacement) 将目标字符串里与既有模式相匹配的子串所有替换为指定的字符串。 |
String | replaceFirst(String replacement) 将目标字符串里第一个与既有模式相匹配的子串替换为指定的字符串。 |
Matcher | reset() 重设该Matcher对象。 |
Matcher | reset(CharSequence input) 重设该Matcher对象而且指定一个新的目标字符串。 |
int | start() 返回当前查找所获子串的开始字符在原目标字符串中的位置。 |
int | start(int group) 返回当前查找所得到的和指定组匹配的子串的第一个字符在原目标字符串中的位置。 |
(光看方法的解释是否是很很差理解?不要急,待会结合例子就比较容易明白了)
一个Matcher实例是被用来对目标字符串进行基于既有模式(也就是一个给定的Pattern所编译的正则表达式)进行匹配查找的,全部往Matcher的输入都是经过CharSequence接口提供的,这样作的目的在于能够支持对从多元化的数据源所提供的数据进行匹配工做。
咱们分别来看看各方法的使用:
★matches()/lookingAt ()/find():
一个Matcher对象是由一个Pattern对象调用其matcher()方法而生成的,一旦该Matcher对象生成,它就能够进行三种不一样的匹配查找操做:
以上三个方法都将返回一个布尔值来代表成功与否。
★replaceAll ()/appendReplacement()/appendTail():
Matcher类同时提供了四个将匹配子串替换成指定字符串的方法:
replaceAll()与replaceFirst()的用法都比较简单,请看上面方法的解释。咱们主要重点了解一下appendReplacement()和appendTail()方法。
appendReplacement(StringBuffer sb, String replacement) 将当前匹配子串替换为指定字符串,而且将替换后的子串以及其以前到上次匹配子串以后的字符串段添加到一个StringBuffer对象里,而appendTail(StringBuffer sb) 方法则将最后一次匹配工做后剩余的字符串添加到一个StringBuffer对象里。
例如,有字符串fatcatfatcatfat,假设既有正则表达式模式为"cat",第一次匹配后调用appendReplacement(sb,"dog"),那么这时StringBuffer sb的内容为fatdog,也就是fatcat中的cat被替换为dog而且与匹配子串前的内容加到sb里,而第二次匹配后调用appendReplacement(sb,"dog"),那么sb的内容就变为fatdogfatdog,若是最后再调用一次appendTail(sb),那么sb最终的内容将是fatdogfatdogfat。
仍是有点模糊?那么咱们来看个简单的程序:
//该例将把句子里的"Kelvin"改成"Kevin" import java.util.regex.*; public class MatcherTest{ public static void main(String[] args) throws Exception { //生成Pattern对象而且编译一个简单的正则表达式"Kelvin" Pattern p = Pattern.compile("Kevin"); //用Pattern类的matcher()方法生成一个Matcher对象 Matcher m = p.matcher("Kelvin Li and Kelvin Chan are both working in Kelvin Chen's KelvinSoftShop company"); StringBuffer sb = new StringBuffer(); int i=0; //使用find()方法查找第一个匹配的对象 boolean result = m.find(); //使用循环将句子里全部的kelvin找出并替换再将内容加到sb里 while(result) { i++; m.appendReplacement(sb, "Kevin"); System.out.println("第"+i+"次匹配后sb的内容是:"+sb); //继续查找下一个匹配对象 result = m.find(); } //最后调用appendTail()方法将最后一次匹配后的剩余字符串加到sb里; m.appendTail(sb); System.out.println("调用m.appendTail(sb)后sb的最终内容是:"+ sb.toString()); } } |
最终输出结果为:
第1次匹配后sb的内容是:Kevin
第2次匹配后sb的内容是:Kevin Li and Kevin
第3次匹配后sb的内容是:Kevin Li and Kevin Chan are both working in Kevin
第4次匹配后sb的内容是:Kevin Li and Kevin Chan are both working in Kevin Chen's Kevin
调用m.appendTail(sb)后sb的最终内容是:Kevin Li and Kevin Chan are both working in Kevin Chen's KevinSoftShop company.
看了上面这个例程是否对appendReplacement(),appendTail()两个方法的使用更清楚呢,若是仍是不太确定最好本身动手写几行代码测试一下。
★group()/group(int group)/groupCount():
该系列方法与咱们在上篇介绍的Jakarta-ORO中的MatchResult .group()方法相似(有关Jakarta-ORO请参考上篇的内容),都是要返回与组匹配的子串内容,下面代码将很好解释其用法:
import java.util.regex.*; public class GroupTest{ public static void main(String[] args) throws Exception { Pattern p = Pattern.compile("(ca)(t)"); Matcher m = p.matcher("one cat,two cats in the yard"); StringBuffer sb = new StringBuffer(); boolean result = m.find(); System.out.println("该次查找得到匹配组的数量为:"+m.groupCount()); for(int i=1;i<=m.groupCount();i++){ System.out.println("第"+i+"组的子串内容为: "+m.group(i)); } } } |
输出为:
该次查找得到匹配组的数量为:2
第1组的子串内容为:ca
第2组的子串内容为:t
Matcher对象的其余方法因比较好理解且因为篇幅有限,请读者本身编程验证。
4.一个检验Email地址的小程序:
最后咱们来看一个检验Email地址的例程,该程序是用来检验一个输入的EMAIL地址里所包含的字符是否合法,虽然这不是一个完整的EMAIL地址检验程序,它不能检验全部可能出现的状况,但在必要时您能够在其基础上增长所需功能。
import java.util.regex.*; public class Email { public static void main(String[] args) throws Exception { String input = args[0]; //检测输入的EMAIL地址是否以 非法符号"."或"@"做为起始字符 Pattern p = Pattern.compile("^//.|^//@"); Matcher m = p.matcher(input); if (m.find()){ System.err.println("EMAIL地址不能以'.'或'@'做为起始字符"); } //检测是否以"www."为起始 p = Pattern.compile("^www//."); m = p.matcher(input); if (m.find()) { System.out.println("EMAIL地址不能以'www.'起始"); } //检测是否包含非法字符 p = Pattern.compile("[^A-Za-z0-9//.//@_//-~#]+"); m = p.matcher(input); StringBuffer sb = new StringBuffer(); boolean result = m.find(); boolean deletedIllegalChars = false; while(result) { //若是找到了非法字符那么就设下标记 deletedIllegalChars = true; //若是里面包含非法字符如冒号双引号等,那么就把他们消去,加到SB里面 m.appendReplacement(sb, ""); result = m.find(); } m.appendTail(sb); input = sb.toString(); if (deletedIllegalChars) { System.out.println("输入的EMAIL地址里包含有冒号、逗号等非法字符,请修改"); System.out.println("您如今的输入为: "+args[0]); System.out.println("修改后合法的地址应相似: "+input); } } } |
例如,咱们在命令行输入:java Email www.kevin@163.net
那么输出结果将会是:EMAIL地址不能以'www.'起始
若是输入的EMAIL为@kevin @163.net
则输出为:EMAIL地址不能以'.'或'@'做为起始字符
当输入为:cgjmail#$%@163.net
那么输出就是:
输入的EMAIL地址里包含有冒号、逗号等非法字符,请修改
您如今的输入为: cgjmail#$%@163.net
修改后合法的地址应相似: cgjmail@163.net
5.总结:
本文介绍了jdk1.4.0-beta3里正则表达式库--java.util.regex中的类以及其方法,若是结合与上一篇中所介绍的Jakarta-ORO API做比较,读者会更容易掌握该API的使用,固然该库的性能将在将来的日子里不断扩展,但愿得到最新信息的读者最好到及时到SUN的网站去了解。
6.结束语:
原本计划再多写一篇介绍一下需付费的正则表达式库中较具表明性的做品,但以为既然有了免费且优秀的正则表达式库可使用,何须还要去找需付费的呢,相信不少读者也是这么想的:,因此有兴趣了解更多其余的第三方正则表达式库的朋友能够本身到网上查找或者到我在参考资料里提供的网址去看看。
关于做者 陈广佳 Kevin Chen,汕头大学电子信息工程系工科学士,台湾大新出版社珠海区开发部,现正围绕中日韩电子资料使用JAVA开发电子词典等相关项目。可经过E-mail:cgjmail@163.net于他联系。 |