首页 > 技术文章 > 正则表达式简明教程

pzy4447 2016-04-19 22:01 原文

 

本文目录

一、正则表达式用途

二、资料与工具

三、正则基础知识

四、正则进阶知识

五、在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 rere1 re_”为例,本正则不能匹配前两个单词,只能匹配后两个单词。

“^\d[135][a-f]$”的含义是:匹配一个字符串,且第一个字符是数字,第二个字符是1或者3或者5,第三个字符是a-f之间的字符。也就是说,[]可以定义字符的范围,里面的横线表示范围,如无横线则各字符为或的关系。”15c”可以匹配,”15g”不能匹配。

3.2 数量匹配

假设我要匹配一个长达1000位的字符串,我总不可能构造一个1000位的正则吧!当然,数量匹配元字符能够定义指定字符的数量。常用的数量匹配元字符见表2

2 常用的数量匹配元字符

代码/语法

说明

*

重复零次或更多次

+

重复一次或更多次

?

重复零次或一次

{n}

重复n

{n,}

重复n次或更多次

{n,m}

重复nm

结合表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)

这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

 

由表可见,前三项都在解释组号命名的问题。那么组号有什么用呢?

  1. 组号能够被正则的其它部分引用,以再次匹配某个组,这样你就不用在正则中写很多重复的子表达式。比如说,”\b(\w+)\b\s+\1\b”用于匹配2个单词,第一个由若干字母组成,第二个引用第一个分组,即与第一个单词相同。它匹配”ha ha”。
  2. Java语言为例,使用matchergroup(i)方法能够输出每一个分组所匹配的内容。其中i是组号。

下面是几个示例。

1."^(\d{1,3}\.){3}(\d{1,3})$"的含义是:匹配一个字符串,形式如xx.xx.xx.xx,其中xx13位整数。”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,其中xx13位整数,所有的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.时,它会匹配singdanc

(?<=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}?

重复nm次,但尽可能少重复

{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)"和"Im singing while youre dancing"时,遇到的情况为,测试网站显示能够匹配到singdanc,然而程序中的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+"后输出就正常了。

 

推荐阅读