正则表达式从入门到实战

Posted 技术杂谈哈哈哈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了正则表达式从入门到实战相关的知识,希望对你有一定的参考价值。


本文来自作者 JPM 在 GitChat 上分享「正则表达式从入门到实战」,阅读原文」查看交流实录

文末高能

编辑 | 坂本

在开发的过程中,字符串处理往往很频繁。比如我们经常会对用户输入做校验:手机号,身份证号,邮箱,密码,域名,IP 地址,URL 或者其他与字符串相关校验的业务场景。

正则表达式就是一种强大而灵活的文本处理工具,正则可以很好的解决这类字符串校验问题。掌握正则表达式,就能大大提高开发过程的效率。

正则表达式(Regular Expression)在代码中常常简写为regex。正则表达式通常被用来检索、替换那些符合某个规则的文本,它是一种强大而灵活的文本处理工具。

本场Chat将从2个方面入手:

  • 学习正则表达式的语法规则,并介绍开发过程中使用正则表达式的流程,提供一款正则工具。

  • 通过实现 5 个小功能练习使用正则,然后解决 2 个实际开发中遇到的问题。

通过本场 Chat 的学习,从零开始轻松掌握正则表达式,并且具备解决实际项目问题的能力。

一款好用的正则工具

给大家推荐一个正则工具“RegexBuddy”,( http://pan.baidu.com/s/1jHRshpW "正则工具RegexBuddy" ),密码:c509

说明:

为了便于理解,文章所有示例的正则表达式用“regex=正则”表示,“=”号后面就是正则表达式,为了阅读效果,我会把工具GegexBuddy里匹配到的字符串截图展示。

比如:regex=study regex,其中 study regex 就是一个正则,它匹配字符串“study regex”,如下:

正则表达式的语法规则

学习正则表达式语法,主要就是学习元字符以及它们在正则表达式上下文中的行为。

元字符包括:普通字符、标准字符、特殊字符、限定字符(又叫量词)、定位字符(也叫边界字符)。下面分别介绍不同元字符的用法。

普通字符

字母[a-zA-Z]、数字[0-9]、下划线[-]、汉字,标点符号:

  • 匹配字母a可以 regex=a

  • 匹配字母b可以 regex=b

  • 匹配字母a或者b可以 regex=a|b,这个正则引入一个特殊字符“|”,专业名称为“或”,你也可以叫它“竖线”,它表示“或”的意思。

  • 匹配字母a或者b或者c可以 regex=a|b|c

  • 匹配字母a或者b或者c或者d可以 regex=a|b|c|d

  • 如果匹配所有26个字母,这种写法明显很二了。

这里引入两个特殊字符方括号“[ ]”和中划线“-” “[ ]”,专业名称为“字符集合”,你也可以叫它“方括号”。

“-” ,表示“范围”,你也可以叫它“到”,regex=[A-Z] 匹配从A到Z26个字母中的任意一个。

那么匹配字母a或者b或者c或者d可以 regex=[abcd]。

匹配数字1到8的任意数字可以 regex=[1-8],这样就不会匹配到0与9这2个数字了,如下:

标准字符集合

标准字符集合是能够与“多种普通字符”匹配的简单表达式,比如:\\d、\\w、\\s。

匹配数字0到9的任意数字可以 regex=[0-9] 也可以 regex=\\d。标准字符集要注意区分大小写,大写是相反的意思。

regex=\\D,则匹配非数字字符,即不能匹配数字0到9,如下:

常用的标准字符说明 标黄的要熟记。

特殊字符

特殊字符在正则表达式中表示特殊的含义,比如:*,+,?,\\,等等。

  • “\\”是转义字符,用于匹配特殊字符

  • 匹配反斜杠“\\”可以 regex=\\\\,因为“\\”是特殊字符,所以需要在它前边再加一个“\\”进行转义

  • 匹配星号“*”,可以 regex=\\,因为“\\”是特殊字符,所以需要在它前边再加一个“\\”进行转义

常用的特殊字符说明 标黄的要熟记。

限定字符

限定字符又叫量词,是用于表示匹配的字符数量的。

  • 匹配任意1位数字可以 regex=\\d

  • 匹配任意2位数字可以 regex=\\d\\d

  • 匹配任意3位数字可以 regex=\\d\\d\\d

匹配任意8位数字,再这么写就有点二了。这里引入用于表示数量限定字符“{n}”。“{n}”,n是一个非负整数,匹配确定的n次。

注意:regex=\\d\\d{3} 匹配任意4个数字不是6个,量词只对它前面的字符负责, regex=\\d\\d{3} 匹配的内容如下:

  • 匹配任意8位数字可以 regex=\\d{8}

  • 匹配任意8位以上的数字可以 regex=\\d{8,}

  • 匹配任意1到8位以上的数字可以 regex=\\d{1,8}

从上图,我们可以看到 regex=\\d{1,8},可以匹配到任意1-8个数字,超过8位数字后,从新开始匹配。

匹配次数中的“贪婪模式”与“非贪婪模式”:

正则的匹配默认是贪婪模式,即匹配的字符越多越好,而非贪婪模式是匹配的字符越少越好,在修饰匹配字数的量词后再加上一个问号“?”即可。

那么同样是上面的字符串,regex=\\d{1,8}?匹配到什么呢?

因为在{1,8}这个量词后面加上了问号“?”,表示非贪婪模式,所以只能匹配到1个数字,即匹配的字符越少越好。

常用的限定字符说明 标黄的要熟记。

  • 匹配0个或多个字母A可以 regex=A* 或者 regex=A{0,}

  • 匹配至少一个字母A可以 regex=A+ 或者 regex=A{1,}

  • 匹配0个或1字母A可以 regex=A?或者 regex=A{0,1}

匹配至少一个 Hello可以 regex=(Hello)+,匹配的效果如下:

定位字符

定位字符也叫字符边界,标记匹配的不是字符而是符合某种条件的位置,所以定位字符是“零宽的”。

常用的定位字符:

匹配以 Hello 开头的字符串可以 regex=^Hello

匹配以 Hello 结尾的字符串可以 regex=Hello$,如下:

匹配以H开头以o结尾的任意长度字符串可以regex=^H.*o$,如下:

\\b匹配这样一个位置:前面的字符和后面的字符不全是\\w。如果在“hello,hello1 helloregex,hello regexhello.”这个字符串里匹配regex=hello\\b,匹配到的结果如下:

分析一下:为什么 hello1 匹配不了“hello\\b”这个正则?

首先\\b是一个定位字符,它是零宽的,标识一个位置,这个位置的前面和这个位置的后面不能全是\\w,即不能全是字母数字和下划线 [A-Za-z0-9_],而hello1的o与1之间的位置前面是o后面是1,前后全是\\w,不符合\\b匹配的含义,因此hello1不能匹配正则表达式 “hello\\b”。

但是 bhello 可以匹配 “hello\\b” 这个正则,因为 hello 的结尾的位置,前面是o,后面是空白,所以符合\\b匹配的含义,因此 bhello 可以匹配 “hello\\b” 这个正则。

自定义字符集合

方括号[ ]表示字符集合,即[ ]表示自定义集合,用[ ]可以匹配方括号里的任意一个字符。

regex=[aeiou] 匹配“a”,“e”,“i”,“o”,“u”任意一个字符,也就是可以匹配集合 [aeiou] 的任意一个字符。

但是,特殊字符(除了小尖角“^”和中划线“-”外)被包含到方括号中,就会失去特殊意义,只代表其字符本身。

regex=[abc+?] 匹配“a”,“b”,“c”任意一个字符或者**“+”,“\\”,“?”,即包含在自定义集合中的特殊字符“+”,“*”,“?”**失去了特殊含义,只表示其字符本身的意思。

特殊字符小尖角“^”,原本含义是匹配字符串的开始位置,如果包含在自定义集合[ ]中,则表示取反的意思。

比如:regex=[^aeiou] 匹配“a”,“e”,“i”,“o”,“u”之外的任意一个字符。

中划线“-”,在自定义集合[ ]中,表示“范围”,而不是字符“-”本身,regex=[a-z],匹配从a到z中26个字母中的任意一个。

除小数点“.”外,标准字符集合包含在方括号中,仍然表示集合范围。regex=[\\d.+] 匹配0-9的任意一个数字或者小数点“.”或者加号“+”

也就是说\\d在自定义集合中仍然表示数字,但是小数点在字符集合中只表示小数点本身,而不是除“\\r\\n”之外的任何单个字符。

选择符和分组

regex=x|y,匹配字符x或y。( )表示捕获组,( )的作用如下:

  1. 括号中的表达式可以作为整体被修饰,用来表示匹配括号中表达式的次数,regex=(abc){2,3},可以匹配连续的2个或3个abc,如下:

  2. 括号中的表达式匹配到的内容会存储起来,并可以获取到括号中表达式匹配到的内容

  3. 每一对括号会分配一个编号,使用( )的捕获根据左括号的顺序从1开始自动编号,编号为0的捕获是整个正则表达式匹配到的文本。

捕获组( )可以把匹配的内容存储起来,那么如何获取( )捕获到的内容呢,下面介绍反向引用。

反向引用 “\\number”

每一对括号会分配一个编号,使用( )的捕获根据左括号的顺序从1开始自动编号。

通过反向引用,可以对分组已捕获的字符串进行引用。“\\number” 中的 number 就是组号

regex=(abc)d\\1 可以匹配字符串 abcdabc,即\\1表示把获取到的第一组再匹配一次,如下:

(?:pattern) 表示非捕获组,匹配括号中表达式匹配到的内容,但是不进行存储匹配到的内容。

这在使用 “或” 字符?(|)?来组合一个正则的各个部分是很有用的。

例如:匹配字符 “story” 或者 “stories”,regex=stor(?:y|ies) 就是一个比 regex=story|stories 更简略的表达式。

预搜索

预搜索,又叫零宽断言,又叫环视,它是对位置的匹配,与定位字符(边界字符)类似。

regex=love (?=story)匹配的结果如下(匹配 “love?” 后面是 story):

regex=love (?!story)匹配的结果如下(匹配 “love” 后面不能是 story):

运算符的优先级

正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。下表的优先级从高到低排序。

说明:“|” 或操作是优先级最低的,它比普通字符的优先级低。因此,regex=r|loom 匹配 “r” 或 “loom”,如下:

如果想匹配 “room” 或 “loom”,请用括号创建子表达式,regex=(r|l)oom,如下:

开发过程中使用正则表达式的流程

  1. 分析所要匹配的数据特点,模拟各种测试数据;

  2. 利用正则工具,写正则表达式与测试数据进行匹配,从而验证你写的正则;

  3. 在程序里调用在正则工具中验证通过的正则表达式。

练习使用正则实现 5 个小功能

电话号码的正则

  1. 电话号码由数字和“-”组成

  2. 如果包含区号,那么区号为三位或四位,首位是0

  3. 区号用“-”和其他数字分割

  4. 除了区号,电话号码为7到8位

  5. 手机号码为11位

  6. 11位手机号码的前2位为“13”,“14”,“15”,“17”,“18”

分析

电话号码分为固话和手机号,首先匹配固话,然后匹配手机号。

  • 固话的正则:regex=0\\d{2,3}-\\d{7,8}

  • 手机号的正则:regex=1[34578]\\d{9}

所以电话号码的正则:

regex=(0\\d{2,3}-\\d{7,8})|(1[34578]\\d{9})

电话号码匹配结果如下:

身份证号码的正则

  1. 长度:15位或者18位

  2. 如果是15位,则都是数字

  3. 如果是18位,最后一位可能为数字或字母X

分析

  • 15位数字:regex=\\d{15}

  • 18位数字:regex=\\d{18}

  • 17位数字+X:regex=\\d{17}X|x

所以省份证号码的正则:

regex=(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}X|x$)

身份证号码匹配的结果如下:

电子邮箱的正则

  1. 邮箱格式:用户名@网址.域名

  2. 用户名:字母、数字、下划线组成

  3. 网址:字母、数字

  4. 域名:2-4位字母组成,1-2个域名

  5. 不区分大小写

分析

  • 用户名:regex=\\w+。

  • 网址:regex=[a-zA-Z0-9]+。

所以电子邮箱的正则 regex=\\w+@[a-zA-Z0-9]+(.[a-zA-Z]{2,4}){1,2}。

电子邮箱匹配的结果如下:

IP地址的正则

IP地址的格式:(1~255).(0~255).(0~255).(0~255)

分析

  • 1-255的正则 regex=^([1-9]|[1-9]\\d|1\\d\\d|2[0-5][0-5])。

  • 0-255的正则 regex=^(\\d|[1-9]\\d|1\\d\\d|2[0-5][0-5]) 。

所以 IP 地址的正则 regex=^([1-9]|[1-9]\\d|1\\d\\d|2[0-5][0-5]).((\\d|[1-9]\\d|1\\d\\d|2[0-5][0-5]).){2}(\\d|[1-9]\\d|1\\d\\d|2[0-5][0-5])$

IP地址匹配的结果如下:

日期格式的正则

日期格式:yyyy-mm-dd

分析

  • 4位的年,第一位只能是1或2,regex=^([12]\\d{3})

  • 一年的12个月(01~09和1~12),regex=^(0?[1-9]|1[0-2])

  • 一个月的31天(01~09和1~31), regex=^((0?[1-9])|((1|2)[0-9])|30|31)

所以 格式为 yyyy-mm-dd 的日期正则 regex=^([12]\\d{3})-(0?[1-9]|1[0-2])-(0?[1-9]|((1|2)[0-9])|30|31)$

yyyy-mm-dd 的日期匹配的结果如下:

在 Java 代码中如何使用正则

Java 中正则相关类位于 java.util.regex 包下,主要使用2个类,如下:

Pattern 类:

  • Pattern 是正则表达式 regex 的编译表示形式

  • 代码:Pattern pattern = Pattern.compile(regex);

Matcher 类:

  • 通过解释 Pattern 对输入的字符串 input 执行匹配操作的引擎

  • 代码:Matcher matcher = pattern.matcher(input);

注意:在 Java 代码中转义字符“\\”要写成“\\”才表示一个“\\”。比如regex=\\d,在 Java 代码中应该写成“\\d”。

Java示例代码:  

package regex; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TestRegex {    public static void main(String[] args) {        String input = "Hello regex 666!";        // java中要想表示\\需要通过转义字符\\进行转义        String regex = "\\\\w+";        Pattern pattern = Pattern.compile(regex);        Matcher matcher = pattern.matcher(input);        // matches()方法,将输入的整个字符串与给定的正则匹配        System.out.println(matcher.matches());        // 结果为:false,因为"Hello regex 666!"不全是\\w        String regex1 = "\\\\d+";        Pattern pattern1 = Pattern.compile(regex1);        Matcher matcher1 = pattern1.matcher(input);        // find()方法,从输入的字符串里找出与给定的正则匹配的子串        while (matcher1.find()) {            // 只要找到,则就能通过group()方法获取到符合条件的子串            System.out.println(matcher1.group());            // 结果为:666,通过find()找到了\\d,通过group()方法获取匹配到的值        }    } }

利用正则解决 2 个实际开发中遇到的问题

问题1:一键获取短信验证码

短信验证码在目前的互联网应用的非常广泛,在一些重要操作中都需要输入短信验证码来验证身份信息。

列举3条不同的验证码短信内容如下:

  1. 【京东】尊敬的用户,634561是您本次的省份验证码,30分钟内有效,请完成验证。

  2. 【滴滴】您的验证码是6678,请在页面中提交验证码完成验证。

  3. 【百度】376687(动态验证码),请在30分钟内填写。

那么如何通过一个正则表达式来获取到3个不同类型的短信内容里的数字验证码呢?

首先分析以上3条短信内容,找出共同点:

  1. 验证码都是数字,可以是4位数字,也可以是6位数字

  2. 每条短信都包含“验证码”3个汉字

  3. “验证码”3个字与数字的顺序关系,“验证码”3个字可以在数字前,也可以在数字后

按照以上的分析,我们就可以写在正则工具里写正则表达式进行验证了。

  1. 4位数字或者6位数字,可以用 “\\d{4}|\\d{6}” 来匹配,我们使用捕获组( )来获取数字部分,即 regex=(\\d{4}|\\d{6})

  2. 验证码3个字就用“验证码”来匹配,regex=验证码

  3. 以上是关于正则表达式从入门到实战的主要内容,如果未能解决你的问题,请参考以下文章

    MySQL从入门到精通50讲-MySQL正则表达式及事务

    从入门到实战,Android学习路线大全

    用正则表达式校验手机号,邮箱就是流弊python爬虫入门进阶(08)

    Python应用实战系列-正则表达式大全

    用正则表达式爬取古诗文网站,边玩边学python爬虫入门进阶(09)

    学好正则表达式,啥难匹配的内容都给我匹配上python爬虫入门进阶(07)