正则表达式细读

前言

        在之前接触到一些问题,我都面对着难以处理一个成分非常复杂字符串的问题。之后一点点地接触到了正则表达式。本文将用JAVA语言简单地描述了一下正则表达式。正则表达式不局限于语言,在很多语言都能用的,关键是同学们对正则表达式的上手入门。

        正则表达式具有强大的字符串处理能力,广泛用 于web开发等等,现在许多语言,如Perl、Python、php和JavaScript,都支持正则表达式。正则表达式是对字符串规定的一种逻辑公式,换句话说,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”, 这个是“规则字符串”用来表达对字符串的一种过滤逻辑。比如正则表达式\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} 用来匹配IPv4地址的,\d表示0-9的整数,{1,3}表示最少一个最多3个,\.就是中间那个点,因为.在正则表达式里面有特殊含义,所以如果要表 示文本里的内容含有.,则需要加一个反斜杠。这个正则表达式就给出了一个逻辑公式,来表示符合这种规则的所有字符串。


一、正则表达式用处

给定一个正则表达式和另一个字符串,我们可以达到如下的目的:

1. 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”);

2. 可以通过正则表达式,从字符串中获取(或者替换)我们期望的特定部分。


正则表达式的特点是:

1. 灵活性、逻辑性和功能性非常的强;

2. 可以迅速地用极简单的方式达到字符串的复杂控制。

3. 对于刚接触的人来说,比较晦涩难懂。


下面举个例子,来简单说明一下正则表达式的的用途之一:


        现在需要分析web服务器日志文件,确定每一个互联网访客的IP地址以及统计每一个访客的访问时间。现在我手上有一段Apache 日志文件,其中日志记录的格式如下:


66.249.73.207 – – [19/Dec/2013:04:00:03 +0800] “GET /robots.txt HTTP/1.1″ 200 441 “-” “Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)”


119.147.146.195 – – [19/Dec/2013:09:40:59 +0800] “GET / HTTP/1.1″ 200 13094 “-” “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; MAXTHON 2.0)”


175.0.171.109 – – [19/Dec/2013:09:41:27 +0800] “GET / HTTP/1.1″ 200 15124 “-” “Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36″


        现在,我要从这个日志文件需要提取的内容有两项:IP地址和页面访问时间(红色部分)。 如果不知道要用正则表达式的话,单纯采用字符串类提供的方法(函数)来提取每个访客的IP地址和访问时间,可想而知是很复杂。而如果用正则表达式的话,现 在只需要构造出一个适当的正则表达式:”(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s-\s-\s\[([^ \]]+)\]“,则可以利用Pattern类和Matcher类方便地将每一个访客的IP地址、访问时间信息都取出来,省事多了。


二、正则表达式的简单认识

1.句点符:

        如果你要在一段很长的字符串中找出三个连续字 符,假设这三个字符是以a开头,z结尾,中间为任意字符,即“a*z”形式的。那么需要构造”a.z”这样一个正则表达式,它能匹配单词 “abz”“ayz”“a%z”“a#z”等等。句点符号“.”,是通配符,可以匹配除 “\n” 之外的任何单个字符(若要匹配包括 ‘\n’ 在内的任何字符,请使用像‘[.\n]‘ 的模式)。


2.方括号符号:

        如果用句点符来匹配的话,匹配到的字符范围过 于广泛;很多时候我们需要获取特定的字符,比如26个英文字母、数字0~9等特定范围。为了解决句点符号匹配范围过于广泛这一问题,你可以在方括号 (“[]”)里面指定有意义的字符。此时,只有方括号里面指定的字符才参与匹配。比方说,正则表达式“c[au]t”匹配“cat”和“cut”。为什么 不是匹配caut呢?因为在方括号之内只能匹配单个字符。在给几个例子,”[a-z]“匹配单个a-z的小写字母,”[0-9]“匹配单个0-9之间的数字,”[a-z][a-z][a-z]“匹配三个连续字母。


3.或符号:

        在上一点方括号符号的介绍基础下,如果还想要匹配“caut”,那么你可以使用“|”操作符。“|”操作符的基本意义就是“或”运算。要匹配“caut”,使用“c(a|u|au)t”正则表达式。这里不能使用方扩号,因为方括号只允许匹配单个字符;这里必须使用圆括号“()”。此外,圆括号还可以用来分组,具体下文有讲。


4.表示匹配次数的符号

下面是表示匹配次数的符号,这些符号用来确定紧靠该符号左边的符号出现的次数:


*  匹配前面的子表达式(或者一个字符,下同)零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。


+   匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。


?   匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。


{n}  n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。


{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。


{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。


? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。


例如要在一个字符串中匹配出QQ号,则需要构造正则表达式:”[0-9]{6,10}”,表明有6-10位数的QQ号。由于[0-9]等价于\d,所以也能写成”\d{6,10}”.(下文有说)


又例如要在一个字符串中匹配出一个电话号码(如0731-1234567或07311234567),则需要构造正则表达式”/d{4}-? /d{7}”,因为“-”有可能不存在,所以需要加个“?”,这个正则表达式能匹配两种格式的电话号码(0731-1234567或 07311234567)。


5.否符号

        否符号为“^”。如果用在方括号内,“^”表示不想要匹配的字符。例如,正则表达式”[^x][a-z]*”匹配除了以“X”字母开头以外的所有单词。


6.空白符号

        \s为空白符,匹配所有的空白字符,包括空 格、Tab字符。例如正则表达式”June\s\d{1,2}”匹配的是June月份的日期,如“June 21”, “June 4”等等。注意不能写成”June空格\d{1,2}”。


7.圆括号符号

        回到文章开头举的那个从web服务器日志文件 中提取访客IP和访问时间的例子。观察正则表达式”(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s-\s-\s \[([^\]]+)\]”,里面加了两对圆括号,作用是将圆括号里面的数据作为一组,里面有两对圆括号,意味着有两组。在JAVA里,可以用 public String java.util.regex.Matcher.group(int i)方法来获得第i组的内容,i==0时是整个匹配出来的内容,i==1才是第一个圆括号里的内容,i==2则是第二组,第二对圆括号里的内容。


8.其他常用符号

\d 匹配一个数字字符。等价于 [0-9]。 

\D 匹配一个非数字字符。等价于 [^0-9]。 

\f 匹配一个换页符。等价于 \x0c 和 \cL。 

\n 匹配一个换行符。等价于 \x0a 和 \cJ。 

\r 匹配一个回车符。等价于 \x0d 和 \cM。 

\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 

\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 

\t 匹配一个制表符。等价于 \x09 和 \cI。 

\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。 

\w 匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]‘。 

\W 匹配任何非单词字符。等价于 ‘[^A-Za-z0-9_]‘


$  匹配到结尾


^[\u2E80-\u9FFF]+$   匹配所有东亚区的文字    

^[\u4E00-\u9FFF]+$   匹配简体和繁体中文 

^[\u4E00-\u9FA5]+$   匹配简体中文


后话

    在Java JDK 1.4或更新版本中,Java自带了支持正则表达式的包,(java.util.regex包)。


        在java中,与regex有关的包,并不都能理解和识别反斜线字符(\),尽 管可以试试看。但为避免这一点,即为了让反斜线字符(\)在模式对象中被完全地传递,应该用双反斜线字符(\\)。此外圆括号在正则表达中两层含义,如果 想让它解释为字面上意思(即圆括号),也需要在它前面用双反斜线字符“\\(”。写代码时,你可以首先输入未经转义处理的正则表达式,然后从左到右依次把 每一个“\”替换成“\\”。谨慎起见,建议每写一个复杂一点的正则表达式都要测试一次,免得你正在写的程序的代码写多了之后,忽略了这个可能存在的错误,后果不堪设想,调bug真的会调死人的。