我相信,作为一名程序员或者准程序员,你肯定是知道正则表达式的。但实际开发中,真正去手写一个满足需求校验的正则,往往磕磕绊绊,最后都是去 Google 搜一搜,然后复制过来改一改。
此时大多数程序员心里可能是这么想的:我在工作中用到正则的时候并不多啊,要用的时候搜一下就好了啊,为什么还要专门花时间和精力学它呢?
大概会存在如下几点困惑:
正则,就是正则表达式,英文是 Regular Expression,简称 RE。顾名思
义,正则其实就是一种描述文本内容组成规律的表示方式。
在编程语言中,常用的使用场景
对于正则表达式,我们有没有可能“打出⼀⼝永不⼲涸的深井”呢?
当然有,那就是⼀次性多投⼊点时间,由表及里,由术及道。
第⼀步,做分解。
拿到一个问题后,我们要先思考:这个问题可以分为⼏个⼦问题?每个⼦问题是否独⽴?我们拿最常⻅的电⼦邮件地址匹配来说。从文本结构来看,它可以分为“username + @ + domain name”这三个独⽴的部分。怎么画呢?我们可以先画出逻辑结构图。通过这个过程来厘清思路。
第⼆步,分析各个⼦问题。
某个位置上可能有多个字符?那就⽤字符组。某个位置上可能有多个字符串?那就⽤多选结构。出现的次数不确定?那就⽤量词。对出现的位置有要求?那就⽤锚点锁定位置……
只要熟练掌握了,知道什么时候用字符组,什么时候用多选结构,什么时候用量词,什么时候用锚点,就很容易搭建起完整的概念模型。
第三步,套⽪。
正则中真正重要的是字符组、多选结构、量词等等这些概念。一旦你的概念模型清楚了,写出正则表达式就非常简单了,无非是查阅语法⼿册,把之前得到的概念模型按照对应语⾔或⼯具的约定写下来而已。虽然各种语⾔或⼯具对正则表达式的⽀持⼤同⼩异,但细微差别仍然不可忽视。
第四步,调试。
正则表达式的麻烦之处在于它像个⿊箱⼦,很难调适,只能笼统地知道“匹配了”或者“没匹配”。那到底怎么调试呢?我的经验是,复杂⼀点的正则表达式不能⼀次写对,这是很正常的。与其纠结“这个正则表达式看起来这么复杂,此处到底要用星号 * 还是加号 +,不如先搞清楚,星号( * )或加号( + )限定的到底是正则表达式中的哪一部分,对应要匹配文本中的哪一部分。这两个问题搞清楚了,整个问题就迎刃而解了
第五步伐,务必要保持克制 。
能⽤普通字符串处理的,坚决⽤普通字符串处理。
能写注释的正则表达式,⼀定要写注释。
能⽤多个简单正则表达式解决的,⼀定不要苛求⽤⼀个复杂的正则表达式。
首先,我可以把元字符大致分成这几类:
1. 特殊单字符
英文的点(.)表示换行以外的任意单个字符,\d 表示任意单个数字,\w 表示任意单个数字或字母或下划线,\s 表示任意单个空白符。另外,还有与之对应的三个 \D、\W 和 \S,分别表示着和原来相反的意思。
2. 空白符
不同的系统在每行文本结束位置默认的“换行”会有区别。比如在 Windows 里是 \r\n,在 Linux 和 MacOS 中是 \n。在正则中,也是类似于 \n 或 \r 等方式来表示空白符号,只要记住它们就行了。平时使用正则,大部分场景使用 \s 就可以满足需求,\s 代表任意单个空白符号。
3. 量词
在正则中,英文的星号(*)代表出现 0 到多次,加号(+)代表 1 到多次,问号(?)代表 0 到 1 次,{m,n}代表 m 到 n 次。
4. 范围
在正则表达式中,表示范围的符号有四个分类,如下图所示。
比如 ab|bc 能匹配上 ab,也能匹配上 bc。
中括号[]代表多选一,可以表示里面的任意单个字符,所以任意元音字母可以用 [aeiou] 来表示。另外,中括号中,我们还可以用中划线表示范围,比如 [a-z] 可以表示所有小写字母。如果中括号第一个是脱字符(^),那么就表示非,表达的是不能是里面的任何单个元素。比如某个资源可能以 http:// 开头,或者 https:// 开头,也可能以 ftp:// 开头,那么资源的协议部分,我们可以使用 (https?|ftp)😕/ 来表示。
正则中的三种模式,贪婪匹配、非贪婪匹配和独占模式。
1.贪婪匹配
在正则中,表示次数的量词默认是贪婪的,在贪婪模式下,会尝试尽可能最大长度去匹配。
例子:
首先,我们来看一下在字符串 aaabb 中使用正则 a* 的匹配过程
2.非贪婪匹配
我们可以在量词后面加上英文的问号 (?),正则就变成了 a*?。非贪婪模式会尽可能短地去匹配,我把这两者之间的区别写到了下
面这张图中。
3.独占模式
独占模式,它类似贪婪匹配,但匹配过程不会发生回溯,因此性能会更好。那什么是回溯呢?
举个例子:
如果我们把这个正则改成非贪婪模式,
如下图:
了解了回溯,我们再看下独占模式。
独占模式和贪婪模式很像,独占模式会尽可能多地去匹配,如果匹配失败就结束,不会进行
回溯,这样的话就比较节省时间。具体的方法就是在量词后面加上加号(+)
如下图:
4.总结
正则中量词默认是贪婪匹配,如果想要进行非贪婪匹配需要在量词后面加上问号。贪婪和非贪婪匹配都可能会进行回溯,独占模式也是进行贪婪匹配,但不进行回溯,因此在一些场景下,可以提高匹配的效率,具体能不能用独占模式需要看使用的编程语言的类库的支持情况,以及独占模式能不能满足需求。
所谓匹配模式,指的是正则中一些改变元字符匹配行为的方式,比如匹配时不区分英文字母大小写。常见的匹配模式有 4 种,分别是不区分大小写模式、点号通配模式、多行模式和注释模式。
1.不区分大小写模式(Case-Insensitive)
当我们把模式修饰符放在整个正则前面时,就表示整个正则表达式都是不区分大小写的。
例子:不区分大小写的 cat 就可以写成 (?i)cat
如果我们想要前面匹配上的结果,和第二次重复时的大小写一致,那该怎么做呢?
只需要用括号把修饰符和正则 cat 部分括起来:
例子: ((?i)cat) \1
如果用正则匹配,实现部分区分大小写,另一部分不区分大小
写,这该如何操作呢?就比如说我现在想要,the cat 中的 the 不区分大小写,cat 区分大
小写。
例子: ((?i)the) cat
2.点号通配模式(Dot All)
英文的点(.)有什么用吗?它可以匹配上任何符号,但不能匹配换行。当我们需要匹配真正的“任意”符号的时
候,可以使用 [\s\S] 或 [\d\D] 或 [\w\W] 等。
3.多行模式
^ 匹配整个字符串的开头
$匹配整个字符串的结尾
4.注释模式
正则中注释模式是使用(?#comment) 来表示。
例子:(\w+)(?#word) \1(?#word重复一次)
5.总结
什么是断言呢?简单来说,断言是指对匹配到的文本位置有要求。你应该知道 \d{11} 能匹配上 11 位数字,但这 11 位数字可能是 18 位身份证号中的一部分。再比如,去查找一个单词,我们要查找 tom,但其它的单词,比如 tomorrow 中也包含了 tom。也就是说,在有些情况下,我们对要匹配的文本的位置也有一定的要求。为了解决这个问题,正则中提供了一些结构,只用于匹配位置,而不是文本内容本身,这种结构就是断言。
常见的断言有三种:单词边界、行的开始或结束以及环视。
在讲单词边界具体怎么使用前,我们先来看一下例子。我们想要把下面文本中的 tom 替换成 jerry。注意一下,在文本中出现了 tomorrow 这个单词,tomorrow 也是以 tom 开头的。
利用前面学到的知识,我们如果直接替换,会出现下面这种结果。
2.行的开始或结束
和单词的边界类似,在正则中还有文本每行的开始和结束,如果我们要求匹配的内容要出现在一行文本开头或结尾,就可以使用 ^ 和 $ 来进行位置界定。我们先说一下行的结尾是如何判断的。你应该知道换行符号。在计算机中,回车(\r)和换行(\n)其实是两个概念,并且在不同的平台上,换行的表示也是不一样的。我在这里列出了 Windows、Linux、macOS 平台上换行的表示方式。
3.日志起始行判断
最常见的例子就是日志收集,我们在收集日志的时候,通常可以指定日志行的开始规则,比如以时间开头,那些不是以时间开头的可能就是打印的堆栈信息。我来给你一个以日期开头,下面每一行都属于同一篇日志的例子
4.输入数据校验
在 Web 服务中,我们常常需要对输入的内容进行校验,比如要求输入 6 位数字,我们可以使用 \d{6} 来校验。但你需要注意到,如果用户输入的是 6 位以上的数字呢?在这种情况下,如果不去要求用户录入的 6 位数字必须是行的开头或结尾,就算验证通过了,结果也可能不对。比如下面的示例,在不加行开始和结束符号时,用户输入了 7 位数字,也是能校验通过的:
5.环视( Look Around)
环视就是要求匹配部分的前面或后面要满足(或不满足)某种规则,有些地方也称环视为零宽断言。那具体什么时候我们会用到环视呢?我来举个例子。邮政编码的规则是第一位是 1-9,一共有 6 位数字组成。现在要求你写出一个正则,提取文本中的邮政编码。根据规则,我们很容易就可以写出邮编的组成 [1-9]\d{5}。我们可以使用下面的文本进行测试
6.环视与子组
环视中虽然也有括号,但不会保存成子组。保存成子组的一般是匹配到的文本内容,后续用于替换等操作,而环视是表示对文本左右环境的要求,即环视只匹配位置,不匹配文本内容。你也可以总结一下,圆括号在正则中都有哪些用途,不断地去复习学过的内容,巩固自己的记忆。
7.总结
最常见的断言有三种:单词的边界、行的开始或结束、环视。单词的边界是使用 \b 来表示。而多行模式下,每一行的开始和结束是使用^ 和 $ 符号。如果想匹配整个字符串的开始或结束,可以使用 \A 和 \z,它们不受匹配模式的影响。最后就是环视,它又分为四种情况:肯定逆向环视、否定逆向环视、肯定顺序环视、否定顺序环视。在使用的时候记住一个方法:有左尖括号代表看左边,没有尖括号是看右边,而感叹号是非的意思。
1.转义字符
下面是一些常见的转义字符以及它们的含义。
3.正则中元字符的转义
如果现在我们要查找比如星号(*)、加号(+)、问号(?)本身,而不是元字符的功能,这时候就需要对其进行转义,直接在前面加上反斜杠就可以了。
4.括号的转义
在正则中方括号 [] 和 花括号 {} 只需转义开括号,但圆括号 () 两个都要转义。在正则中,圆括号通常用于分组,或者将某个部分看成一个整体,如果只转义开括号或闭括号,正则会认为少了另外一半,所以会报错。
5.使用函数消除元字符特殊含义
这个转义函数可以将整个文本转义,一般用于转义用户输入的内容,即把这些内容看成普通字符串去匹配,但你还是得好好注意一下,如果使用普通字符串查找能满足要求,就不要使用正则,因为它简单不容易出问题。下面是一些其他编程语言对应的转义函数,供你参考
讲完了元字符的转义,我们现在来看看字符组中的转义。书写正则的时候,在字符组中,如果有过多的转义会导致代码可读性差。在字符组里只有三种情况需要转义
7.字符组中其它的元字符
8.总结
正则中转义有些情况下会比较复杂,从录入的字符串文本,到最终的正则表达式,经过了字
符串转义和正则转义两个步骤。元字符的转义一般在前面加反斜杠就行,方括号和花括号的
转义一般转义开括号就可以,但圆括号两个都需要转义,我们可以借助编程语言中的转义函
数来实现转义。另外我们也讲了字符组中三种需要转义的情况,详细的可以参考下面的脑
图。
目前正则表达式主要有两大流派(Flavor):POSIX 流派与 PCRE 流派。下面我们分别来看看。
1.POSIX 流派
POSIX 规范定义了正则表达式的两种标准:
2.PCRE 流派
除了 POSIX 标准外,还有一个 Perl 分支,也就是我们现在熟知的 PCRE。目前大部分常用编程语言都是源于 PCRE 标准,这个流派显著特征是有\d、\w、\s 这类字符组简记方式。
Unicode 基础知识
Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字进行了整理、编码。现在的 Unicode 字符分为 17 组编排,每组为一个平面(Plane)而每个平面拥有65536(即 2 的 16 次方)个码值(Code Point)。然而,目前 Unicode 只用了少数平面,我们用到的绝大多数字符都属于第 0 号平面,即 BMP 平面。除了 BMP 平面之外,其它的平面都被称为补充平面。
我目前是从事java开发,我就以java为例:
1.校验文本
在 Java 中,正则相关的类在 java.util.regex 中,其中最常用的是 Pattern 和 Matcher,Pattern 是正则表达式对象,Matcher 是匹配到的结果对象,Pattern 和 字符串对象关联,可以得到一个 Matcher。下面是 Java 中匹配的示例:
2.提取
在 Java 中,可以使用 Matcher 的 find 方法来获取查找到的内容,就像下面这样
在 Java 中,一般是使用 replaceAll 方法进行替换,一次性替换所有的匹配到的文本。
4.切割
Java 中切割也是类似的,由于没有原生字符串,转义稍微麻烦点。
5.总结
用的较少,优化思路参考下图:
1.问题处理思路
2.常见问题及解决方案
1). 匹配数字
2). 匹配正数、负数和小数
3). 浮点数
4). 十六进制数
5). 手机号码
6). 身份证号码
7). 邮政编码
8). 腾讯 QQ 号码
9). 中文字符
10). IPv4 地址
11). 日期和时间
12). 邮箱
13). 网页标签
总结
咱们学习任何内容,在一开始时,都不要过分去注重细节,重要的是掌握正确的方法,先从整体上入手,掌握核心的概念。比如当你学习了字符组、多选结构、量词、锚点、贪婪 & 非贪婪、流派、匹配原理这些核心知识之后,你要在后续遇到问题时,查阅一下相关文档,很快就可以解决掉了。
当你觉得你掌握了正则之后,你可以尝试把它教给别人,或者你可以想一下,如果让你来讲正则,你会怎么讲?这样能够让你有更深刻的理解
有时候在学习正则之后,遇到问题总是想“展示一下身手”,这种心理无可厚非,但一定要记住在使用正则的过程中,一定要克制
在这个技术太多,变化太快的时代,我们需要不断地学习。越是基础的内容越重要,一次性多投入些时间,弄清楚,搞明白。多动手练习,实践出真知
联系客服