掌握正则表达式:从零开始轻松学习字符串处理与校验技巧
本文来自作者JPM在GitChat上分享《正则表达式从入门到实战》

文章地址:
在开发过程中,字符串处理往往非常频繁。比如我们经常验证用户输入:手机号、身份证号、邮箱、密码、域名、IP地址、URL或者其他与字符串验证相关的业务场景。
正则表达式是一种强大而灵活的文本处理工具。正则表达式可以很好的解决这类字符串验证问题。掌握正则表达式可以大大提高开发过程的效率。
正则表达式(Regular Expression)在代码中常常缩写为regex。正则表达式通常用于检索和替换符合某些规则的文本。它是一个强大而灵活的文本处理工具。
这次聊天将从两个方面开始:
通过本Chat的学习,您可以从零开始轻松掌握正则表达式,并具备解决实际项目问题的能力。
一个有用的常规工具
我向大家推荐一个常规工具“RegexBuddy”。获取资源可以关注公众号。故事还在继续。扫描左上角二维码即可领取对应资源。
阐明:
为了方便理解,文中所有例子中的正则表达式都用“regex=regular”来表示,“=”号后面跟的是正则表达式。为了方便阅读,我将在GegexBuddy工具中匹配到的字符串进行截图。
例如:regex=study regex,其中study regex是正则表达式,它匹配字符串“study regex”,如下:
正则表达式的语法规则
学习正则表达式语法主要是学习元字符及其在正则表达式上下文中的行为。
元字符包括:普通字符、标准字符、特殊字符、限定字符(也称为量词)和定位字符(也称为边界字符)。下面介绍不同元字符的用法。
普通字符
字母[a-zA-Z]、数字[0-9]、下划线[-]、汉字、标点符号:
这里引入两个特殊字符,方括号“[]”和下划线“-”“[]”。专业名称是“字符集”,你也可以叫它“方括号”。
“-”表示“范围”,也可以称为“到”,regex=[AZ] 匹配从 A 到 Z 的 26 个字母中的任意一个。
那么匹配字母a或者b或者c或者d就可以是regex=[abcd]。
要匹配1到8之间的任意数字,可以regex=[1-8],这样0和9这两个数字就不会被匹配,如下:
标准字符集
标准字符集是一个简单的表达式,可以匹配“许多常见字符”,例如:\d、\w、\s。
可以使用 regex=[0-9] 或 regex=\d 匹配从 0 到 9 的任何数字。标准字符集应区分大小写。大写字母具有相反的含义。
regex=\D,匹配非数字字符,即不能匹配数字0到9,如下:
黄色标记的常用标准字符描述应记住。
特殊字符
特殊字符在正则表达式中具有特殊含义,例如:*、+、? ,\,ETC。
黄色标记的常用特殊字符应记住。
合格角色
限定符字符也称为量词,用于指示匹配字符的数量。
匹配任意8位数字,这样写有点混乱。这里引入了用于表示数量限制的字符“{n}”。 “{n}”,n为非负整数,匹配某n次。
注意:regex=\d\d{3} 匹配任意 4 个数字,而不是 6 个。量词仅负责其前面的字符。 regex=\d\d{3} 的匹配内容如下:
从上图中我们可以看到regex=\d{1,8}可以匹配任意1-8个数字。超过8位后,重新开始匹配。
“贪婪模式”和“非贪婪模式”的匹配次数:
正则匹配默认为贪婪模式,即匹配的字符越多越好,而非贪婪模式则意味着匹配的字符越少越好。只需添加一个问号“?”修改匹配单词数量的量词后。
那么上面的相同字符串,regex=\d{1,8}?它匹配什么?
因为一个问号“?”添加在量词{1,8}后面,表示非贪婪模式,只能匹配一个数字,即匹配字符越少越好。
常用的黄色标记的合格字符描述应记住。
要匹配至少一个 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=[az],匹配从a到z的26个字母中的任意一个。
除小数点“.”外,标准字符集包含在方括号中,仍然表示该集的范围。 regex=[\d.+] 匹配 0 到 9 之间的任何数字或小数点“.”或加号“+”
也就是说,\d仍然代表自定义集合中的数字,但小数点只代表字符集合中的小数点本身,而不代表除“\r\n”之外的任何单个字符。
选择器和分组
regex=x|y,匹配字符x或y。 ( ) 代表捕获组。 ( ) 的作用如下:
括号内的表达式可以整体修改,表示括号内的表达式被匹配的次数。 regex=(abc){2,3} 可以匹配2个或3个连续的abc,如下:
括号内的表达式匹配的内容会被存储起来,并且可以获得括号内的表达式匹配的内容。
每对括号都分配有一个编号。使用 () 进行的捕获会根据左括号的顺序自动从 1 开始编号。编号为 0 的捕获是整个正则表达式匹配的文本。
捕获组()可以存储匹配的内容,那么如何获取()捕获的内容呢?下面描述反向引用。
反向引用“\number”
每对括号都会分配一个编号,使用 ( ) 进行捕获时会根据左括号的顺序自动从 1 开始编号。
反向引用允许您引用分组的捕获字符串。 “\number”中的数字为组号
regex=(abc)d\1 可以匹配字符串 abcdabc,即 \1 表示再次匹配得到的第一组,如下:
(?:pattern) 表示非捕获组,匹配括号内表达式匹配到的内容,但不存储匹配到的内容。
当使用 OR 字符 ?(|)? 组合正则表达式的各个部分时,这非常有用。
例如:匹配字符“story”或“stories”,regex=stor(?:y|ies) 是比 regex=story|stories 更简单的表达式。
预搜索
预搜索,也叫零宽度断言,也叫环视,是一种位置匹配,类似于定位字符(边界字符)。
regex=love(?=story)的匹配结果如下(匹配“love?”后接story):
regex=love(?!story)的匹配结果如下(匹配“love”后面不能跟story):
运算符优先级
正则表达式从左到右计算并遵循优先顺序,就像算术表达式一样。下表按优先级从高到低排序。
注:“|” or 操作的优先级最低,低于普通字符。因此,regex=r|loom 匹配“r”或“loom”,如下所示:
如果要匹配“room”或“loom”,请使用括号创建子表达式,regex=(r|l)oom,如下所示:
开发过程中使用正则表达式的流程
分析待匹配数据的特征,模拟各种测试数据;
使用正则工具编写正则表达式,并与测试数据进行匹配,验证你编写的正则表达式;
在程序中调用已经在正则工具中验证过的正则表达式。
练习使用正则表达式实现5个小功能
常规电话号码
电话号码由数字和“-”组成
如果包含区号,则区号为三位数或四位数,第一位为0。
区号以“-”等数字分隔。
除区号外,电话号码为 7 至 8 位数字
手机号码为11位
11位手机号码的前2位分别是“13”、“14”、“15”、“17”、“18”
分析
电话号码分为固定电话号码和手机号码。先匹配固定电话号码,再匹配手机号码。
所以电话号码的常规模式是:
正则表达式=(0\d{2,3}-\d{7,8})|(1[34578]\d{9})
电话号码匹配结果如下:
身份证号码规则
长度:15 或 18 位数字
如果是15位的话都是数字
如果是18位,最后一位可能是数字或字母X
分析
因此,省级证书编号的常规规则是:
正则表达式=(^\d{15}$)|(^\d{18}$)|(^\d{17}X|x$)
身份证号匹配结果如下:
常规电子邮件地址
邮件格式:用户名@网站.域名
用户名:由字母、数字、下划线组成
网址:字母、数字
域名:2-4个字母,1-2个域名
不区分大小写
分析
因此,电子邮件的常规正则表达式为=\w+@[a-zA-Z0-9]+(.[a-zA-Z]{2,4}){1,2}。
邮件地址匹配结果如下:
IP地址规则
IP地址格式:(1~255).(0~255).(0~255).(0~255)
分析
所以IP地址的正则表达式=^([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
分析
因此,格式为 yyyy-mm-dd 正则表达式的日期=^([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 包下。主要使用到两个类,如下:
图案类:
匹配器类:
注意:在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个问题
问题一:一键获取短信验证码
短信验证码在当前互联网上应用广泛。在一些重要操作中,您需要输入短信验证码来验证身份信息。
列出3条不同的验证码短信内容如下:
【京东】尊敬的用户,634561是您这次的省份验证码。 30分钟内有效。请完成验证。
[滴滴]您的验证码是6678,请在页面提交验证码以完成验证。
【百度】376687(动态验证码),请在30分钟内填写。
那么如何使用正则表达式获取三种不同类型短信内容中的数字验证码呢?
首先分析一下以上三条短信的内容,找出它们的共同点:
验证码全为数字,可以是4位或6位
每条短信均包含“验证码”三个汉字
“验证码”三个字符与数字的顺序关系。 “验证码”三个字符可以在数字之前,也可以在数字之后。
根据上面的分析,我们可以在正则工具中编写正则表达式来进行验证。
4位或6位数字可以用“\d{4}|\d{6}”来匹配。我们使用capture group()来获取数字部分,即regex=(\d{4}|\d{ 6})
如果验证码有3个字符,则使用“验证码”来匹配,regex=验证码
“验证码”在数字前有3个字符,可以 regex=验证码\D(\d{4}|\d{6}),“验证码”在数字后有3个字符,可以 regex=(\ d{ 4}|\d{6})\D 验证码,这两个表达式是通过or联系起来的,需要用括号来组织这两个表达式,然后使用or“|”选择,即regex=(验证码\D(\d{4} |\d{6}))|((\d{4}|\d{6})\D验证码)
由于我们需要通过捕获组()获取数值内容并使用括号来组织关系,所以我们需要在or“|”两边标记表达式部分与非捕获组 (?:) 一起使用,因为我们只需要获取数字部分 括号 ( ) 匹配的数字。
即regex=(?:验证码\D(\d{4}|\d{6}))|(?:(\d{4}|\d{6})\D验证码)
最后,我们将分析后的表达式代入 Java 代码补全函数中。注意,在Java中,反斜杠需要转义,即一个破折号变成两个破折号。
Java代码:
package regex;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestRegex {
public static void main(String[] args) {
List inputList = new ArrayList(16);
inputList.add("【京东】尊敬的用户,634561是您本次的省份验证码,30分钟内有效,请完成验证。");
inputList.add("滴滴】您的验证码是6678,请在页面中提交验证码完成验证。");
inputList.add("【百度】376687(动态验证码),请在30分钟内填写。");
String regex = "(?:验证码\\D*(\\d{4}|\\d{6}))|(?:(\\d{4}|\\d{6})\\D*验证码)";
Pattern pattern = Pattern.compile(regex);
System.out.println("一键获取到的验证码如下:");
for (String input : inputList) {
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
for (int i = 1; i <= matcher.groupCount(); i++) {
if (matcher.group(i) != null) {
System.out.println(matcher.group(i));
}
}
}
}
}
}
运行效果:
问题2:判断用户密码是否为强密码
用户设置的弱密码会导致信息安全问题。一般来说,系统需要强密码。以下是京东注册页面截图:
以京东注册为例,京东建议使用字母、数字和符号的组合,6-20个字符。
接下来,我们使用正则表达式来验证用户输入的密码是否符合密码规则。首先分析密码要求,如下:
密码由字母、数字、符号3个字符组成
必须包含 2 个或更多字符
密码长度6-20个字符
字母包括:A-Za-z,数字包括:0-9,
符号包括32个:`-=][';/.,~!@#$%^&()_+|}{“:?><
需要注意的是,如果使用32个符号,特殊字符“\”、“[”、“]”需要转义。为了简单和直观,我们假设只有 @#$3 个符号。
进一步分析发现,该密码只有字母、数字、符号三种字符。要求必须包含2种以上。那么密码组合有4种(3选2+全选3=4),即:字母+数字、字母+符号、数字+符号、字母+数字+符号。
如果我们从正面思考这个问题,那么正则表达式会很难写,所以我们从反面思考:“必须包含2种或以上类型”的反面是“只包含1种类型”,这意味着密码要求“不能仅包含 1 种类型“1 个字符”。
密码长度为6-20个字符。需要使用开始标记“^”和结束标记“$”,以及量词{6,20}
密码要求归根到底是:密码从头到尾必须是6-20个字符,不能全部是单个字符。
因此正则表达式可以写成这样:
解释:
需要注意的是,起始符“^”和预搜索“(?!)”都是零宽度,表示位置,所以起始符“^”只需要写在整个开头即可正则表达式。
最后,我们将分析后的表达式代入 Java 代码补全函数中。
Java代码片段:
密码验证效果: