正则表达式简明教程
本文目录
一、正则表达式用途
二、资料与工具
三、正则基础知识
四、正则进阶知识
五、在Java中使用正则
六、Java正则中遇到的问题
一、正则表达式用途
正则表达式是一个有含义的字符串,它代表了一个字符串的组成规则,用于验证给定字符串是否与我匹配。比方说,当你要求用户填写邮箱时,你就需要用正则表达式来验证用户输入的是不是有效的邮箱地址。
二、资料与工具
网上的学习资料很多,但是滥竽充数的也不少。我的学习资料是《正则表达式30分钟入门教程》,本文相当于是在该文章的基础上提炼而成的。在学习过程中你不需要通过编程来验证,那样太慢,可以在正则验证工具-chinaZ验证你的正则表达式。
三、正则基础知识
如果你写过批处理或者善于使用windows系统的搜索功能,那么你一定对通配符不陌生。在windows中,*代表任意数量的字符。不要担心,正则表达式没有那么可怕,它与这个通配符是类似的。
3.1 内容匹配
我们把正则中具有特定含义的字符成为元字符,那么最基本的元字符有2种:(1)匹配字符内容(2)匹配字符数量。常用的内容匹配元字符见表1:
表1.常用的内容匹配元字符
代码 |
说明 |
. |
匹配除换行符以外的任意字符 |
\w |
匹配字母或数字或下划线或汉字 |
\s |
匹配任意的空白符 |
\d |
匹配数字 |
\b |
匹配单词的开始或结束 |
^ |
匹配字符串的开始 |
$ |
匹配字符串的结束 |
注意,没跟数量匹配元字符时,它们都只能匹配1个字符。从表1可以看到,”.”几乎能够匹配任何字符;如果你要匹配一个字母或者数字,使用\w就可以了;匹配数字可以使用\d。
下面是几个例子(正则表达式位于””内):
“\bre\w\b”的含义是:匹配一个单词,以re打头后面再跟一个字符。注意,正则表达式里面说的单词不等于英文单词,这里的单词可以包含数字或字符。以”re1d re好 re1 re_”为例,本正则不能匹配前两个单词,只能匹配后两个单词。
“^\d[135][a-f]$”的含义是:匹配一个字符串,且第一个字符是数字,第二个字符是1或者3或者5,第三个字符是a-f之间的字符。也就是说,[]可以定义字符的范围,里面的横线表示范围,如无横线则各字符为或的关系。”15c”可以匹配,”15g”不能匹配。
3.2 数量匹配
假设我要匹配一个长达1000位的字符串,我总不可能构造一个1000位的正则吧!当然,数量匹配元字符能够定义指定字符的数量。常用的数量匹配元字符见表2:
表2 常用的数量匹配元字符
代码/语法 |
说明 |
* |
重复零次或更多次 |
+ |
重复一次或更多次 |
? |
重复零次或一次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n到m次 |
结合表1,当你使用.*的时候基本就能表示任意内容了。下面仍然通过例子来进行解释:
“^0\d{2,3}-\d{7,8}$”的含义是:匹配一个字符串,且第一个字符是0,后面跟2位到3位数字,再跟1个横线,再跟7-8位数字。没错,这就是验证座机号码的正则。”010-77777779”能够匹配,而”010-7777777a”不能匹配。
“^\w+\@\w+\.\w{2,}$”的含义是:匹配一个字符串,首先是一位或多位字符,接下来是”@”符号,接下来是一位或多位字符,然后是”.”,最后是2位或多位字符。”111@163.com”可以匹配,”111@.com”不能匹配。
"^[1-9]\d{16}(\d|x|X)$"的含义是:匹配一个字符串,且第一位部位0,接下来是16位数字,最后一位是数字或大小写”x”。没错,这是验证18位身份证号的正则。注意,”|”表示逻辑或。”01066119880124001x”不能匹配,”41066119880124001x”可以匹配。
3.3 转义取反与分枝
如果你就想匹配元字符本身,使用转移字符就好了,这与编程语言是一样的。比如,”\\”匹配”\”,“\?” 匹配“?”。需要注意的是,在程序中制定正则字符串时,你需要使用很多转义。比如你的实际正则为“^0\d{2,3}-\d{7,8}$”,那你在程序中应写为“^0\\d{2,3}-\\d{7,8}$”,因为在编程语言层面就会对字符串中的转义字符过滤一遍。
[^xyz]表示取反,即不匹配xyz。与元字符对应,也有一系列取反代码,具体见表3:
表3 常用的取反匹配元字符
代码/语法 |
说明 |
\W |
匹配任意不是字母,数字,下划线,汉字的字符 |
\S |
匹配任意不是空白符的字符 |
\D |
匹配任意非数字的字符 |
\B |
匹配不是单词开头或结束的位置 |
[^x] |
匹配除了x以外的任意字符 |
[^aeiou] |
匹配除了aeiou这几个字母以外的任意字符 |
分枝条件就是逻辑或。比如,”\(0\d{2}\)-\d{8}|0\d{2}-\d{8}”匹配的内容为,3位区号-8位号码,其中区号为带括号或者不带括号。
四、正则进阶知识
4.1 分组与向后引用
我们常常用括号来包围一些子表达式,比如说,”(\d{1,3}\.){3}(\d{1,3})”用于匹配一个IP地址(不严谨匹配)。括号以内的范围就是一个分组。正则工作时会给每个分组编号,并记录分组所匹配的内容。简单来说,从左往右,以左括号为标志,第一个分组编号为1,第二个为2,以此类推,正则表达式自身编号为0。除此以外,正则允许你自定义分组名称,或者指定某分组不编号,具体如下:
表4.常用分组语法
序号 |
分类 |
代码/语法 |
说明 |
1 |
捕获 |
(exp) |
匹配exp,并捕获文本到自动命名的组里 |
2 |
(?<name>exp) |
匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) |
|
3 |
(?:exp) |
匹配exp,不捕获匹配的文本,也不给此分组分配组号 |
|
4 |
零宽断言 |
(?=exp) |
匹配exp前面的位置 |
5 |
(?<=exp) |
匹配exp后面的位置 |
|
6 |
(?!exp) |
匹配后面跟的不是exp的位置 |
|
7 |
(?<!exp) |
匹配前面不是exp的位置 |
|
8 |
注释 |
(?#comment) |
这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
由表可见,前三项都在解释组号命名的问题。那么组号有什么用呢?
- 组号能够被正则的其它部分引用,以再次匹配某个组,这样你就不用在正则中写很多重复的子表达式。比如说,”\b(\w+)\b\s+\1\b”用于匹配2个单词,第一个由若干字母组成,第二个引用第一个分组,即与第一个单词相同。它匹配”ha ha”。
- 以Java语言为例,使用matcher的group(i)方法能够输出每一个分组所匹配的内容。其中i是组号。
下面是几个示例。
例1."^(\d{1,3}\.){3}(\d{1,3})$"的含义是:匹配一个字符串,形式如xx.xx.xx.xx,其中xx为1至3位整数。”222.213.24.57”匹配。各分组匹配内容为:
group 0 : 222.213.24.57
group 1 : 24.
group 2 : 57
因为正则中包含2对括号,因此不算自身只有2个分组。由于分组1重复匹配3次,因此它只能记录第三次匹配的内容也就是”24.”。分组2记录的匹配内容是”57”。
例2."^(\\d{1,3})\\.\\1\\.\\1\\.\\1$"的含义为,匹配一个字符串,形式如xx.xx.xx.xx,其中xx为1至3位整数,所有的xx都相同。它与”222.213.24.57”不匹配,与”222.222.222.222”匹配。
例3."^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$"的含义与例1相同,只不过将每个分组单独写了出来。各分组匹配内容如下,不再解释。
group 0 : 222.213.24.57
group 1 : 222
group 2 : 213
group 3 : 24
group 4 : 57
例4."(?:\\d{1,3})\\.(\\d{1,3})\\.(?:\\d{1,3})\\.(\\d{1,3})"的含义与例1相同,仍然会与”222.213.24.57”匹配。区别在于分组1和分组3不分配组号且不记录匹配内容,各分组及匹配内容如下:
group 0 : 222.213.24.57
group 1 : 213
group 2 : 57
4.2 零宽断言
简单来说,零宽断言代表以下的规则:指定字符串以xxx打头、以xxx结尾、不以xxx打头或不以xxx结尾。并匹配除了xxx以外的内容。零宽指的是规则发生的位置,它没有宽度,不作为字符处理。
(?=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。
(?!exp)也叫做零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
(?<!exp)也叫做零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp。例如,(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
4.3 贪婪与懒惰
正则的匹配默认是贪婪的,也就是说如果条件允许它将匹配尽可能多的字符。比如,”a.*b”将匹配”aabab”的全部,而不是”aab”或”ab”。然而有时我们也需要最小匹配,只需要在正则后面加一个”?”即可。比如说,a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。
表5.懒惰限定符
代码/语法 |
说明 |
*? |
重复任意次,但尽可能少重复 |
+? |
重复1次或更多次,但尽可能少重复 |
?? |
重复0次或1次,但尽可能少重复 |
{n,m}? |
重复n到m次,但尽可能少重复 |
{n,}? |
重复n次以上,但尽可能少重复 |
五、在Java中使用正则
5.1基本代码格式
在Java中使用正则时,需要2个基本要素:Pattern和Matcher。前者是你定义的正则,后者是由该正则生成的匹配器。代码格式如下:
public boolean startCheck(String reg,String string) { boolean tem=false; Pattern pattern = Pattern.compile(reg); Matcher matcher = pattern.matcher(string); tem = matcher.matches(); return tem; }
5.2验证手机号
/** * 手机号码验证,11位,开头必须是1,总长11位数 * */ public boolean checkCellPhone(String cellPhoneNr) { String reg="^[1][\\d]{10}$"; return startCheck(reg,cellPhoneNr); }
5.3验证电子邮箱
/** * 检查EMAIL地址 ,用户名和网站名称必须>=1位字符 * 地址结尾必须是2位以上,如:cn,test,com,info * */ public boolean checkEmail(String email) { String regex="\\w+\\@\\w+\\.\\w{2,}"; return startCheck(regex,email); }
5.4验证身份证号
/** * 验证国内身份证号码:18位,由数字组成,不能以0开头,x或X结尾 * */ public boolean checkIdCard(String idNr) { String reg="^[1-9]\\d{16}(\\d|x|X)$"; return startCheck(reg,idNr); }
5.5验证IP
/** * 查看IP地址是否合法 ,0-255 = 250-255, 200-249, 100-199,10-99, 0-9, * */ public boolean checkIP(String ipAddress) { String subregex = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; String regex = "(" + subregex + "\\.)"+ "{3}" + subregex; return startCheck(regex,ipAddress); }
5.6验证邮编
/** * 检查邮政编码(中国),6位,第一位必须是非0开头,其他5位数字为0-9 * */ public boolean checkPostcode(String postCode) { String regex="^[1-9]\\d{5}"; return startCheck(regex,postCode); }
5.7验证用户名
/** * 检验用户名 * 取值范围为a-z,A-Z,0-9,"_",汉字,不能以"_"结尾 * 用户名有最小长度和最大长度限制,比如用户名必须是4-20位 * */ public boolean checkUsername(String username,int min,int max) { String regex="[\\w_\\-\u4e00-\u9fa5]{"+min+","+max+"}(?<!_)"; return startCheck(regex,username); }
5.8验证网址
/** * 网址验证<br> * 符合类型:<br> * url=协议类型:// 0或1次 * www. 0或1次 * xx.xx xx为数字字符不限长度 * */ public boolean checkWebSite(String url) { //http://www.163.com String reg="^((http|https|ftp)\\:\\/\\/)?(www.)?\\w+\\.\\w+"; //String reg="^(http)\\://(\\w+\\.\\w+\\.\\w+|\\w+\\.\\w+)"; return startCheck(reg,url); }
六、Java正则中遇到的问题
本章记录了我在Java中遇到的坑,按需更新。
6.1 测试通过然而matches()返回false
笔者在测试"\\w*(?=ing)"和"I’m singing while you’re dancing"时,遇到的情况为,测试网站显示能够匹配到sing和danc,然而程序中的matches()总是返回false。经点拨才发现,matches()方法总是匹配整个字符串,仅当完全匹配时才会返回true。在这种情况下应当使用find()方法,每当成功匹配到子字符串时,它就会返回true。
6.2 Start()与end()值异常
代码如下:
import java.util.regex.Matcher;import java.util.regex.Pattern; public class RegexDemo { public static void main(String[] args) { RegexDemo demo = new RegexDemo(); System.out.printf("%b%n", demo.zeroWidthAssertionEarly()); } public boolean zeroWidthAssertionEarly() { //匹配以ing结尾的单词 String reg="\\w*(?=ing)"; String word = "I’m singing while you’re dancing";// boolean tem=false; Pattern pattern = Pattern.compile(reg); Matcher matcher = pattern.matcher(word); while(matcher.find()){ System.out.printf("start = %d%n", matcher.start()); System.out.printf("end = %d%n", matcher.end()); } return tem; } }
输出如下:
start = 4
end = 8
start = 8
end = 8
start = 25
end = 29
start = 29
end = 29false
输出为什么不是这样呢?
start = 4
end = 8
start = 25
end = 29false
原因在于,"\w*"将匹配0或多个字符,因此”ing”将自己匹配自己一次。改为"\w+"后输出就正常了。