前言
在之前接触到一些问题,我都面对着难以处理一个成分非常复杂字符串的问题。之后一点点地接触到了正则表达式。本文将用JAVA语言简单地描述了一下正则表达式。正则表达式不局限于语言,在很多语言都能用的,关键是同学们对正则表达式的上手入门。
正则表达式具有强大的字符串处理能力,广泛用于web开发等等,现在许多语言,如Perl、Python、PHP和JavaScript,都支持正则表达式。正则表达式是对字符串规定的一种逻辑公式,换句话说,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个是“规则字符串”用来表达对字符串的一种过滤逻辑。比如正则表达式\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})\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,},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})\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真的会调死人的。
代码片段1
importjava.util.regex.Matcher; importjava.util.regex.Pattern; publicclassRegrex{ publicstaticvoidmain(String[]args){ //定义正则表达式。这个正则表达式用来提取IP地址和访问时间 Stringregex="(\\d{1,3}\\.\\d{1,3})\\s-\\s-\\s\\[([^\\]]+)\\]"; //把正则表达式编译成Pattern对象,调用compile()方法,并在调用参数中指定正则表达式 Patternp1=Pattern.compile(regex); //要处理的字符串。 Stringinput1="66.249.73.207--[19/Dec/2013:04:00:03+0800]\"GET/robots.txtHTTP/1.1\"200441\"-\"\"Mozilla/5.0"; //匹配对象。利用已经指定好正则表达式的模式对象p1来对指定源字符串input1进行匹配。 Matcherm=p1.matcher(input1); //调用匹配对象m的find()方法来在源字符串input1中寻找一个新的符合指定正则表达式要求的子字符串 while(m.find()){ System.out.println(m.group(0));//输出整个符合正则表达式要求的字符串 //System.out.println(m.group());//group()方法中不带参数,与group(0)相同 System.out.println(m.group(1));//输出第一组圆括号里的内容 System.out.println(m.group(2));//输出第二组圆括号里的内容 } System.out.println(""); //匹配QQ号 Stringregex2="\\d{6,10}"; Patternp2=Pattern.compile(regex2); Stringinput2="Myqqis564923716."; Matcherm2=p2.matcher(input2); while(m2.find()){ System.out.println(m2.group()); } } }
运行结果:
/****************************************************************************************************/
66.249.73.207 – – [19/Dec/2013:04:00:03 +0800]
66.249.73.207
19/Dec/2013:04:00:03 +0800
564923716
/****************************************************************************************************/
此外,JAVA中还有很多方法涉及到正则表达式的,例如常用的分割字符串方法String[] String.split(String regex),替换字符串的方法public String replaceAll (String regex, String replacement)和public String replaceFirst(String regex, String replacement).
本文用JAVA语言简单地描述了一下正则表达式。正则表达式不局限于语言,在很多语言都能用的,关键是同学们对正则表达式的上手入门。
博客原发:http://blog.chenzuhuang.com/archive/12.html
From:陈祖煌
Blog:http://my.oschina.net/chenzuhuang/blog
转载必须说明出处
-------------------------------------------------------------------------------------------