java正则表达式

正则表达式:

    java中自jdk1.4开始引入对正则表达式的支持,java.util.regex包提供对正则表达式的支持。

什么是正则表达式?
    简单的说,正则表达式就是记录文本规则的代码。这样说起来可能比较抽象很难理解。让我们先来回顾一下我们是如何在windows下查找文件的吧。通常如果我们想找出一个分区上的文本文件,我们会输入*.txt来查找,这里的*是一个通配符,它代表任何字符。正则表达式也和这些通配符类似也是用来进行文本匹配的工具,只是它比这些简单通配符更复杂一些。

为什么需要正则表达式?
    我们先来看一个列子,如果我们要在一边文章中查找hi这个单词,我们可以简单输入hi来查找,但是这时候就有问题出来了,我们查找出来的结果会有很多包含了hi这两个字母的单词,比如:this,him,hight等,这并不是我们需要的结果。我们需要更精确的结果,这个时候就是正则表达式发挥作用的时候了。正则表达式可以更精确的描述一种匹配关系。如果要精确的查找hi这个单词的话,我们可以使用/bhi/b这个正则表达式。其中/b是正则表达式的元字符(metacharacter),它表示一个单词的开始或者结束。也就是单词的分界处,那么这个正则表达式的意思就是一个以h开头以i结尾的字符串,那么this,him等就不再匹配这个正则表达式了,所以我们就能正确并精确的找出我们需要的结果。

另一个更复杂的正则表达式

    看一下这个正则表达式:/bhi/b.*/bLucy/b
    这里用到了正则表达式中的另一个元字符.,它匹配出了换行意外的任意字符。而我们刚刚提到的/b这个元字符,它并不匹配任何一个字符,它匹配的只是一个位置。这里的*号同样是元字符,但是它并不匹配任何字符也不是一个位置,而是代表数量,它的意思是*前边的内容可以连续重复出现任意次。那么现在在来看这个比较复杂的正则表达式,它表示的意思就是:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词

正则表达式中的其他元字符
    表1. 常用的元字符
代码        说明
.           匹配出了换行以外的任意字符
/w        匹配字母或数字或下划线或汉字
/s         匹配任意的空白字符
/d        匹配数字
/b        匹配单词的开始或结束
^         匹配字符串的开始
$         匹配字符查的结束

我们知道这些元字符的意义以后,就可以组建更复杂的的正则表达式了。比如我们可以用一个正则表达式来匹配一组电话号码:0/d/d-/d/d/d/d/d/d/d/d,这个正则表达式就匹配一个以0开头,然后是两个数字,然后是连字符-,然后是8为数字的一个典型的中国的电话号码,但是我们发现我们将/d重复了很多此,为了避免那么多麻烦的重复,正则表达式用一种特殊的方式来表示重复,我们可以将上面的正则表达式,改成这样的形式:0/d{2}-/d{8},它和前面那个正则表达式的效果是一样的。这里/d后面的{2}({8})的意思是前面/d必须连续重复匹配2次(8次)。正则表达式中还有另外一些用来表示重复的方式:
        表2.常用的限定符
代码        说明
*        重复零次或更多次
+        重复一次或更多此
?        重复零此或一次
{n}        重复n次
{n,}        重复n此或更多次
{n,m}        重复n到m次

字符的转义:

    如果我们需要查找类似于*,^,.,这样的字符的时候该怎么办呢?他们都是正则表达式中的元字符,我们不能直接用*来匹配*号,因为它们会被解释成别的意思。这时你就得使用/来取消这些字符的特殊意义。因此,你应该使用/.和/*。当然,要查找/本身,你也得用//.
    例如:www/.163/.com匹配 www.163.com,c://test/.txt匹配 c:/test.txt

字符类:
    现在我们已经可以使用正则表达式的元字符来查找数字,字母和空白了,但是,如果我们需要查杂一个范围内的字符该怎么办呢?我们不能简单用/w,或/d,/s来组合,比如,我么要查找英语中的原因字母(a,e,i,o,u)该怎么办呢?很简单,你只需要在方括号里列出它们就行了,像[aeiou]就匹配任何一个英文元音字母,[.?!]匹配标点符号(.或?或!)。正则表达式中方括号表示一个范围,例如[0-9]表示任意数字与/d就是完全一致。
    一个更复杂的正则表达式:/(?0/d{2}[) -]?/d{8},我们来分析一下这个正则表达式,首先是一个转义字符/(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(/d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(/d{8}),它也匹配了一个中国的电话号码,但是不幸的是,这个表达式也会匹配,010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分支条件。正则表达式中用|来表示分支条件,表示或的意思,例如0/d{2}-/d{8}|0/d{3}-/d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)。再看一个例子:/d{5}-/d{4}|/d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成/d{5}|/d{5}-/d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

子表达式:
    正则表达式中用小括号()来定义一个子表达式(也叫分组),比如我们可以用/d{1,3}/./d{1,3}/./d{1,3}/./d{1,3}来简单的匹配一个IP地址,我们发现,这里的/d{1,3}/.重复出现了很多此,我们就可以用括号把它括起来变成一个子表达式,然后再指定子表达式出现的次数。如下(/d{1,3}/.){3}/d{1,3}。这样只是简单的匹配一个IP地址,并不能精确的匹配。因为256.300.888.999这种不可能的IP也匹配上面的表达式,因为正则表达式不提供数学的任何功能,所以,我们只能用冗长的分组、分支、字符类来描述一个正确的IP的正则表达式:((2[0-4]/d|25[0-5]|[01]?/d/d?)/.){3}(2[0-4]/d|25[0-5]|[01]?/d/d?)。

反义:
    有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义:
    表3.常用的反义代码
代码        说明
/W        匹配任意不是字母,数字,下划线,汉字的字符
/S        匹配任意不是空白符的字符
/D        匹配任意非数字的字符
/B        匹配不是单词开头或结束的位置
[^x]        匹配除了x以外的任意字符
[^aeiou]    匹配除了aeiou这几个字母以外的任意字符


更高级的正则表达式
后向引用
    使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
    后向引用用于重复搜索前面某个分组匹配的文本。例如,/1代表分组1匹配的文本。难以理解?请看示例:
    /b(/w+)/b/s+/1/b可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(/b(/w+)/b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(/s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(/1)。
    你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>/w+)(或者把尖括号换成'也行:(?'Word'/w+)),这样就把/w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用/k<Word>,所以上一个例子也可以写成这样:/b(?<Word>/w+)/b/s+/k<Word>/b。
    使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:
    表4.常用分组语法
分类                代码/语法        说明
                (exp)            匹配exp,并捕获文本到自动命名的组里
捕获                (?<name>exp)    匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
                (?:exp)            匹配exp,不捕获匹配的文本,也不给此分组分配组号
                (?=exp)            匹配exp前面的位置
零宽断言            (?<=exp)        匹配exp后面的位置
                (?!exp)            匹配后面跟的不是exp的位置
                (?<!exp)            匹配前面不是exp的位置
注释                (?#comment)        这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

        我们已经讨论了前两种语法。第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面,也不会拥有组号。


零宽断言
接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像/b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。最好还是拿例子来说明吧:

断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。

(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如/b/w+(?=ing/b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。

(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=/bre)/w+/b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。

假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=/d)/d{3})*/b,用它对1234567890进行查找时结果是234567890。

下面这个例子同时使用了这两种断言:(?<=/s)/d+(?=/s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。

负向零宽断言


前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:

/b/w*q[^u]/w*/b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的/w*/b将会匹配下一个单词,于是/b/w*q[^u]/w*/b就能匹配整个Iraq fighting。负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:/b/w*q(?!u)/w*/b。

零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。例如:/d{3}(?!/d)匹配三位数字,而且这三位数字的后面不能是数字;/b((?!abc)/w)+/b匹配不包含连续字符串abc的单词。

同理,我们可以用(?<!exp),零宽度正回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])/d{7}匹配前面不是小写字母的七位数字。


一个更复杂的例子:(?<=<(/w+)>).*(?=<///1>)匹配不包含属性的简单HTML标签内里的内容。(<?(/w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<///1>)。注意后缀里的//,它用到了前面提过的字符转义;/1则是一个反向引用,引用的正是捕获的第一组,前面的(/w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)。

贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。考虑这个表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:

a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。

为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。
表5.懒惰限定符
代码/语法        说明
*?            重复任意次,但尽可能少重复
+?            重复1次或更多次,但尽可能少重复
??             重复0次或1次,但尽可能少重复
{n,m}?         重复n到m次,但尽可能少重复
{n,}?            重复n次以上,但尽可能少重复


正则表达式要掌握还需要多多练习,将来我会收集一些常用的正则表达式放在我的blog中,有兴趣可以去分析分析,最后希望大家都能熟练的使用正则表达式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值