论天下事

谈天说地

Java正则表达式详解

Java正则表达式详解


仙人掌工作室
01-7-31 下午 04:13:03


如果你曾经用过Perl或任何其他内建正则表达式支持的语言,你一定知道用正则表达式处理文本和匹配模式是多么简单。如果你不熟悉这个术语,那么“正则表达式”(Regular Expression)就是一个字符构成的串,它定义了一个用来搜索匹配字符串的模式。
许多语言,包括Perl、PHP、Python、JavaScript和JScript,都支持用正则表达式处理文本,一些文本编辑器用正则表达式实现高级“搜索-替换”功能。那么Java又怎样呢?本文写作时,一个包含了用正则表达式进行文本处理的Java规范需求(Specification Request)已经得到认可,你可以期待在JDK的下一版本中看到它。
然而,如果现在就需要使用正则表达式,又该怎么办呢?你可以从Apache.org下载源代码开放的Jakarta-ORO库。本文接下来的内容先简要地介绍正则表达式的入门知识,然后以Jakarta-ORO API为例介绍如何使用正则表达式。
一、正则表达式基础知识
我们先从简单的开始。假设你要搜索一个包含字符“cat”的字符串,搜索用的正则表达式就是“cat”。如果搜索对大小写不敏感,单词“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是说:
01_7_31_4_a.jpg
1.1 句点符号
假设你在玩英文拼字游戏,想要找出三个字母的单词,而且这些单词必须以“t”字母开头,以“n”字母结束。另外,假设有一本英文字典,你可以用正则表达式搜索它的全部内容。要构造出这个正则表达式,你可以使用一个通配符——句点符号“.”。这样,完整的表达式就是“t.n”,它匹配“tan”、“ten”、“tin”和“ton”,还匹配“t#n”、“tpn”甚至“t n”,还有其他许多无意义的组合。这是因为句点符号匹配所有字符,包括空格、Tab字符甚至换行符:
01_7_31_4_b.jpg
1.2 方括号符号
为了解决句点符号匹配范围过于广泛这一问题,你可以在方括号(“[]”)里面指定看来有意义的字符。此时,只有方括号里面指定的字符才参与匹配。也就是说,正则表达式“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配,因为在方括号之内你只能匹配单个字符:
01_7_31_4_c.jpg
1.3 “或”符号
如果除了上面匹配的所有单词之外,你还想要匹配“toon”,那么,你可以使用“|”操作符。“|”操作符的基本意义就是“或”运算。要匹配“toon”,使用“t(a|e|i|o|oo)n”正则表达式。这里不能使用方扩号,因为方括号只允许匹配单个字符;这里必须使用圆括号“()”。圆括号还可以用来分组,具体请参见后面介绍。
01_7_31_4_d.jpg
1.4 表示匹配次数的符号
表一显示了表示匹配次数的符号,这些符号用来确定紧靠该符号左边的符号出现的次数:

01_7_31_4n.jpg

假设我们要在文本文件中搜索美国的社会安全号码。这个号码的格式是999-99-9999。用来匹配它的正则表达式如图一所示。在正则表达式中,连字符(“-”)有着特殊的意义,它表示一个范围,比如从0到9。因此,匹配社会安全号码中的连字符号时,它的前面要加上一个转义字符“/”。

01_7_31_4a.gif

图一:匹配所有123-12-1234形式的社会安全号码

假设进行搜索的时候,你希望连字符号可以出现,也可以不出现——即,999-99-9999和999999999都属于正确的格式。这时,你可以在连字符号后面加上“?”数量限定符号,如图二所示:

01_7_31_4b.gif

图二:匹配所有123-12-1234和123121234形式的社会安全号码

下面我们再来看另外一个例子。美国汽车牌照的一种格式是四个数字加上二个字母。它的正则表达式前面是数字部分“[0-9]{4}”,再加上字母部分“[A-Z]{2}”。图三显示了完整的正则表达式。

01_7_31_4c.gif

图三:匹配典型的美国汽车牌照号码,如8836KV

1.5 “否”符号
“^”符号称为“否”符号。如果用在方括号内,“^”表示不想要匹配的字符。例如,图四的正则表达式匹配所有单词,但以“X”字母开头的单词除外。

01_7_31_4d.gif

图四:匹配所有单词,但“X”开头的除外

1.6 圆括号和空白符号
假设要从格式为“June 26, 1951”的生日日期中提取出月份部分,用来匹配该日期的正则表达式可以如图五所示:

01_7_31_4e.gif

图五:匹配所有Moth DD,YYYY格式的日期

新出现的“/s”符号是空白符号,匹配所有的空白字符,包括Tab字符。如果字符串正确匹配,接下来如何提取出月份部分呢?只需在月份周围加上一个圆括号创建一个组,然后用ORO API(本文后面详细讨论)提取出它的值。修改后的正则表达式如图六所示:

01_7_31_4f.gif

图六:匹配所有Month DD,YYYY格式的日期,定义月份值为第一个组

1.7 其它符号
为简便起见,你可以使用一些为常见正则表达式创建的快捷符号。如表二所示:
表二:常用符号

01_7_31_4o.jpg

例如,在前面社会安全号码的例子中,所有出现“[0-9]”的地方我们都可以使用“/d”。修改后的正则表达式如图七所示:

01_7_31_4g.gif

图七:匹配所有123-12-1234格式的社会安全号码

二、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对象。
01_7_31_4_e.jpg
▲ Pattern对象
要把正则表达式编译成Pattern对象,调用compiler对象的compile()方法,并在调用参数中指定正则表达式。例如,你可以按照下面这种方式编译正则表达式“t[aeio]n”:
01_7_31_4_f.jpg
默认情况下,编译器创建一个大小写敏感的模式(pattern)。因此,上面代码编译得到的模式只匹配“tin”、“tan”、 “ten”和“ton”,但不匹配“Tin”和“taN”。要创建一个大小写不敏感的模式,你应该在调用编译器的时候指定一个额外的参数:
01_7_31_4_g.jpg
创建好Pattern对象之后,你就可以通过PatternMatcher类用该Pattern对象进行模式匹配。
▲ PatternMatcher对象
PatternMatcher对象根据Pattern对象和字符串进行匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现,它根据Perl 5正则表达式语法进行模式匹配:
01_7_31_4_h.jpg
使用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)
三、应用实例
下面我们来看看Jakarta-ORO库的一些应用实例。
3.1 日志文件处理
任务:分析一个Web服务器日志文件,确定每一个用户花在网站上的时间。在典型的BEA WebLogic日志文件中,日志记录的格式如下:
01_7_31_4_i.jpg
分析这个日志记录,可以发现,要从这个日志文件提取的内容有两项:IP地址和页面访问时间。你可以用分组符号(圆括号)从日志记录提取出IP地址和时间标记。
首先我们来看看IP地址。IP地址有4个字节构成,每一个字节的值在0到255之间,各个字节通过一个句点分隔。因此,IP地址中的每一个字节有至少一个、最多三个数字。图八显示了为IP地址编写的正则表达式:

01_7_31_4h.gif

图八:匹配IP地址

IP地址中的句点字符必须进行转义处理(前面加上“/”),因为IP地址中的句点具有它本来的含义,而不是采用正则表达式语法中的特殊含义。句点在正则表达式中的特殊含义本文前面已经介绍。
日志记录的时间部分由一对方括号包围。你可以按照如下思路提取出方括号里面的所有内容:首先搜索起始方括号字符(“[”),提取出所有不超过结束方括号字符(“]”)的内容,向前寻找直至找到结束方括号字符。图九显示了这部分的正则表达式。

01_7_31_4i.gif

图九:匹配至少一个字符,直至找到“]”

现在,把上述两个正则表达式加上分组符号(圆括号)后合并成单个表达式,这样就可以从日志记录提取出IP地址和时间。注意,为了匹配“- -”(但不提取它),正则表达式中间加入了“/s-/s-/s”。完整的正则表达式如图十所示。

01_7_31_4j.gif

图十:匹配IP地址和时间标记

现在正则表达式已经编写完毕,接下来可以编写使用正则表达式库的Java代码了。
为使用Jakarta-ORO库,首先创建正则表达式字符串和待分析的日志记录字符串:
01_7_31_4_j.jpg
这里使用的正则表达式与图十的正则表达式差不多完全相同,但有一点例外:在Java中,你必须对每一个向前的斜杠(“/”)进行转义处理。图十不是Java的表示形式,所以我们要在每个“/”前面加上一个“/”以免出现编译错误。遗憾的是,转义处理过程很容易出现错误,所以应该小心谨慎。你可以首先输入未经转义处理的正则表达式,然后从左到右依次把每一个“/”替换成“//”。如果要复检,你可以试着把它输出到屏幕上。
初始化字符串之后,实例化PatternCompiler对象,用PatternCompiler编译正则表达式创建一个Pattern对象:
01_7_31_4_k.jpg
现在,创建PatternMatcher对象,调用PatternMatcher接口的contain()方法检查匹配情况:
01_7_31_4_l.jpg
接下来,利用PatternMatcher接口返回的MatchResult对象,输出匹配的组。由于logEntry字符串包含匹配的内容,你可以看到类如下面的输出:
01_7_31_4_m.jpg
3.2 HTML处理实例一
下面一个任务是分析HTML页面内FONT标记的所有属性。HTML页面内典型的FONT标记如下所示:
01_7_31_4_n.jpg
程序将按照如下形式,输出每一个FONT标记的属性:
01_7_31_4_o.jpg
在这种情况下,我建议你使用两个正则表达式。第一个如图十一所示,它从字体标记提取出“"face="Arial, Serif" size="+2" color="red"”。

01_7_31_4k.gif

图十一:匹配FONT标记的所有属性

第二个正则表达式如图十二所示,它把各个属性分割成名字-值对。

01_7_31_4l.gif

图十二:匹配单个属性,并把它分割成名字-值对

分割结果为:
01_7_31_4_p.jpg
现在我们来看看完成这个任务的Java代码。首先创建两个正则表达式字符串,用Perl5Compiler把它们编译成Pattern对象。编译正则表达式的时候,指定Perl5Compiler.CASE_INSENSITIVE_MASK选项,使得匹配操作不区分大小写。
接下来,创建一个执行匹配操作的Perl5Matcher对象。
01_7_31_4_q.jpg
假设有一个String类型的变量html,它代表了HTML文件中的一行内容。如果html字符串包含FONT标记,匹配器将返回true。此时,你可以用匹配器对象返回的MatchResult对象获得第一个组,它包含了FONT的所有属性:
01_7_31_4_r.jpg
接下来创建一个PatternMatcherInput对象。这个对象允许你从最后一次匹配的位置开始继续进行匹配操作,因此,它很适合于提取FONT标记内属性的名字-值对。创建PatternMatcherInput对象,以参数形式传入待匹配的字符串。然后,用匹配器实例提取出每一个FONT的属性。这通过指定PatternMatcherInput对象(而不是字符串对象)为参数,反复地调用PatternMatcher对象的contains()方法完成。PatternMatcherInput对象之中的每一次迭代将把它内部的指针向前移动,下一次检测将从前一次匹配位置的后面开始。
本例的输出结果如下:
01_7_31_4_s.jpg
3.3 HTML处理实例二
下面我们来看看另一个处理HTML的例子。这一次,我们假定Web服务器从widgets.acme.com移到了newserver.acme.com。现在你要修改一些页面中的链接:
01_7_31_4_t.jpg
执行这个搜索的正则表达式如图十三所示:

01_7_31_4m.gif

图十三:匹配修改前的链接

如果能够匹配这个正则表达式,你可以用下面的内容替换图十三的链接:
01_7_31_4_u.jpg
注意#字符的后面加上了$1。Perl正则表达式语法用$1、$2等表示已经匹配且提取出来的组。图十三的表达式把所有作为一个组匹配和提取出来的内容附加到链接的后面。
现在,返回Java。就象前面我们所做的那样,你必须创建测试字符串,创建把正则表达式编译到Pattern对象所必需的对象,以及创建一个PatternMatcher对象:01_7_31_4_v.jpg
接下来,用com.oroinc.text.regex包Util类的substitute()静态方法进行替换,输出结果字符串:
01_7_31_4_w.jpg
Util.substitute()方法的语法如下:
01_7_31_4_x.jpg
这个调用的前两个参数是以前创建的PatternMatcher和Pattern对象。第三个参数是一个Substiution对象,它决定了替换操作如何进行。本例使用的是Perl5Substitution对象,它能够进行Perl5风格的替换。第四个参数是想要进行替换操作的字符串,最后一个参数允许指定是否替换模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只替换指定的次数。

【结束语】在这篇文章中,我为你介绍了正则表达式的强大功能。只要正确运用,正则表达式能够在字符串提取和文本修改中起到很大的作用。另外,我还介绍了如何在Java程序中通过Jakarta-ORO库利用正则表达式。至于最终采用老式的字符串处理方式(使用StringTokenizer,charAt,和substring),还是采用正则表达式,这就有待你自己决定了。

 

我正在为之服务的这个站点有很多的用户数据通过表单输入,并且所有的数据都必须在被送入数据库之前被检查。我知道PHP3的正则表达式函数能够解决我的问题,但是起先我并不知道怎么样去建立这种正则表达式。那时我所需要的是一些示例,很自然的我去查看了PHP3的手册和POSIX 1002.3的说明 但是他们作为例子没有帮多大忙。于是我又花了很多时间在网上找这方面的材料。我最后终于搞定它了,主要是通过实验。既然并没有那么多的关于这方面的材料,我决定把它写下来:我所知道的关于语法和和如何一步步建立正则表达式来验证money和e-mail地址等等.我希望它能够清楚你的迷雾,让你和你的伙伴弄清楚这方面的问题.

正则表达式的基本语法:

首先,让我们看一看两个特殊符号:'^' 和 '$'.它们的作用是指明一个字符串的开头和结尾.分别像这样:


"^The": 对应任何以 "The" 开始的字符串
"of despair$": 对应以 "of despair" 结尾的字符串
"^abc$": 一个以 "abc" 开头然后结尾的字符串---就是"abc"自己!
"notice": 一个包含"notice" 的字符串.

你可以看到如果你两个符号都不使用,就像最后的那个例子,你等于在表述:在字符串的任意位置符合样式都可以,就是说你不管它出现在头还是尾.

还有几个符号 '*', '+', 和 '?',它们表示字符或字符串出现的数目. 他们的意思分别是:"0或更多(任意)", "1或更多(至少1次)", 和"0或1次(至多1次)". 下面有一些例子:


"ab*": 对应一个含有一个 a 后跟有任意个 b 的字符串 ("a", "ab", "abbb", 等等.);
"ab+": 类似, 不过至少有一个 b ("ab", "abbb", 等等.);
"ab?": 要么一个 b 要么没有;
"a?b+$": 结尾部分可能有一个 a ,也可能没有,后面是1个以上的 b.

你还可以使用花括号,里面的数字将指明前面字符出现的范围:


"ab{2}": 对应含有一个 a 后面跟着2个 b ("abb") 的字符串;
"ab{2,}": 至少含有2个b 的("abb", "abbbb", 等等.);
"ab{3,5}": 3到5个 b ("abbb", "abbbb", or "abbbbb").

注意你必须注意范围的第一个数字.(例如:"{0,2}", 不可以是"{,2}"). 同时你可能已经注意到了,字符'*', '+', 和'?'与"{0,}", "{1,}", 和 "{0,1}"的功能是一样的.

现在,来量化一些字符序列/小字符串,把他们放入圆括号里:

"a(bc)*": 对应一个含有任意个 "bc" 在 a 后面的字符串;
"a(bc){1,5}": 1到5个 "bc" 都可以.

还有'|'字符,作用如同OR,用来选择:


"hi|hello": 对应一个有"hi" 或者 "hello" 的字符串;
"(b|cd)ef": 一个有 "bef" 或者 "cdef" 的字符串;
"(a|b)*c": 一个字符串有任意个a和b 的组合,然后以一个 c 结尾;
一个句号('.')表示任意单独字符:


"a.[0-9]": 表示拥有一个 a 后面跟着一个字符和一个数字的字符串;
"^.{3}$": 有 3 个字符的字符串.

方括号明确指出哪些字符可以出现在某个单个字符的位置:

"[ab]": 对应一个有一个 a 或者一个 b (等同于"a|b");
"[a-d]": 一个字符串有小写字母 'a' 到 'd' (等同于 "a|b|c|d" 甚至是 "[abcd]");
"^[a-zA-Z]": 一个开始字符是英文字母的字符串;
"[0-9]%": 有一个数字在百分号前的字串;
",[a-zA-Z0-9]$": 一个字符串结尾是逗号后面跟着一个数字或字母.

你可以使用一个列表剔除你不想要的字符--只要使用一个'^'在你的方括号里的第一个位置 (例如, "%[^a-zA-Z]%" 表示在两个百分号之间的一个字符不是英文字母).另外,你必须注意,某些时候,你不必多加一个反斜杠表示特殊字符失效,比如在字符类的第一个位置时.看例子:"($|¥)[0-9]+"的含义可以表示成ereg("(/$|¥)[0-9]+", $str) (这匹配的是什么字符串?)

不要忘记了,在方括号里的所有特殊字符都将失去特殊含义(注: '^'和'-'例外吧),包括反斜杠,例如"[*+?{}.]"就是匹配这些符号中的任意一个. Regex man pages告诉我们:如果包含一个']',你可以把它放在第一个字符位置,也可以在它的前面放一个反斜杠(例如/[abc]]/)

最后,我应该提一下还有如:collating sequences, character classes, 和 equivalence classes之类,我不会再提它们的细节,因为这和本文章的深入关系不大,你可以在regex man pages找到更多内容.

 

/pattern/  结果 
. 匹配除换行符以外的所有字符
x? 匹配 0 次或一次 x 字符串
x* 匹配 0 次或多次 x 字符串,但匹配可能的最少次数
x+ 匹配 1 次或多次 x 字符串,但匹配可能的最少次数
.* 匹配 0 次或一次的任何字符
.+ 匹配 1 次或多次的任何字符
{m} 匹配刚好是 m 个 的指定字符串
{m,n} 匹配在 m个 以上 n个 以下 的指定字符串
{m,} 匹配 m个 以上 的指定字符串
[] 匹配符合 [] 内的字符
[^] 匹配不符合 [] 内的字符
[0-9] 匹配所有数字字符
[a-z] 匹配所有小写字母字符
[^0-9] 匹配所有非数字字符
[^a-z] 匹配所有非小写字母字符
^ 匹配字符开头的字符
$ 匹配字符结尾的字符
/d 匹配一个数字的字符,和 [0-9] 语法一样
/d+ 匹配多个数字字符串,和 [0-9]+ 语法一样
/D 非数字,其他同 /d
/D+ 非数字,其他同 /d+
/w 英文字母或数字的字符串,和 [a-zA-Z0-9] 语法一样
/w+ 和 [a-zA-Z0-9]+ 语法一样
/W 非英文字母或数字的字符串,和 [^a-zA-Z0-9] 语法一样
/W+ 和 [^a-zA-Z0-9]+ 语法一样
/s 空格,和 [/n/t/r/f] 语法一样
/s+ 和 [/n/t/r/f]+ 一样
/S 非空格,和 [^/n/t/r/f] 语法一样
/S+ 和 [^/n/t/r/f]+ 语法一样
/b 匹配以英文字母,数字为边界的字符串
/B 匹配不以英文字母,数值为边界的字符串
a|b|c 匹配符合a字符 或是b字符 或是c字符 的字符串
abc 匹配含有 abc 的字符串
(pattern) () 这个符号会记住所找寻到的字符串,是一个很实用的语法。第一个 () 内所找到的字符串变成 $1 这个变量或是 /1 变量,第二个 () 内所找到的字符串变成 $2 这个变量或是 /2 变量,以此类推下去。 
/pattern/i i 这个参数表示忽略英文大小写,也就是在匹配字符串的时候,不考虑英文的大小写问题。
/ 如果要在 pattern 模式中找寻一个特殊字符,如 "*",则要在这个字符前加上 / 符号,这样才会让特殊字符失效

阅读更多
个人分类: java技术文章
想对作者说点什么? 我来说一句

Java 正则表达式详解

2008年12月09日 7KB 下载

Java正则表达式详解.pdf

2008年11月10日 373KB 下载

Java正则表达式详解 .doc

2009年05月31日 353KB 下载

java正则表达式详解

2011年06月18日 52KB 下载

ava正则表达式详解

2012年10月10日 505KB 下载

java正则表达式

2007年06月26日 730KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭