老司机出品——包教包会之玩转正则表达式
2017年2月16日14时11分更新Level3详解
结束了CoreAnimation系列之后,老司机心里仿佛也轻松了许多。今天说说开发中的一个利器吧,正则表达式
。
首先说正则表达式是什么?
正则表达式,又称规则表达式。(英语:Regular Expression,在代 码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表通常被用来检索、替换那些符合某个模式(规则)的文本。
其实借助正则表达式,我们可以对字符串进行很多操作
,最大的优势就是从字符串中获取字符串以及判断字符串
是否符合条件。
然而事实上有很多程序员都抱怨,正则表达式的语法晦涩难懂,的确,一个个符号看似毫无规律的拼凑在一起让人看起来确实头疼。
幸运的是,老司机曾经拿出两个下午来认真的研究了一下正则表达式,所以老司机也是目前我们组唯一一个手撸正则
表达式的骚年,so,让老司机沾沾自喜一下可好~
当然,嘚瑟完还是要来干货的,那老司机用老司机的方式带你看一下正则。
元字符
让我们先啃一下硬骨头,元字符
。
所谓元字符,就是几个特定的符号组合在一起,其代表的含义已经不是原先符号本身的含义,而更像是一个表达式。
正则表达式正是因为元字符而变的晦涩,也正是因为元字符而变的简洁。
元字符1 元字符2 元字符3以上图片截自百度百科正则表达式
Level1
元字符之多,多到让人记不住,不过老司机之前说过,元字符不过是带有特定含义的表达式
用来让表达式简洁,所以你不用表达式,用最基本的字符完全可以组成表达式,比如下面这种情况:
- [0123456789] 你可以用这个表达式来代表一个数字元素
- [0-9] 你同样可以用这个表达式代表一个数字元素
- \d 你更可以用这个表达式代表一个数字元素
从上至下,表达式形式越来越简单,不过其书写形式却越来越难懂。
所以即使你记不住\d
,你也可以用[0123456789]
来替代,当你熟练了,自然也就记住\d
了对不对。
所以说,并不是所有元字符你都需要记住的,不过老司机挑了这么几个出来,是能大幅度提高你书写速度的几个元字符:
- [] 对于他,我的命名是元素枚举表达式,表示中括号之间的任意一个元素均可以当做作为一个元素存在。
- {n,m} 这个,我姑且称他为范围表达式。他的含义是表示他前面的元素有x个,x介于[n,m]之间的闭区间(即包含n,m)。
eg. 结合上面两个表达式,我们可以这样写一个表达式:
[0123456789]{1,2}
,其含义是一个1到2位的纯数字。当然\d{1,2}
会让你的表达式看起来更加简洁。
- () 括号表达式,他的含义仅仅是将括号间的所有表达式作为一个整体看做新的元素。
eg. 到这里你可以写这样一个表达式:
([a-z]{1,2}[\d]{1,2}){1,2}
,恩,是不是已经乱七八糟了,我们一点一点拆解,[a-z]{1,2}
表示1或2个小写字母
,[\d]{1,2}
表示1或2个数字
,两个表达式连一起就是1或2个小写字母后面紧跟1或2个数字
(例如ab12
),然后[a-z]{1,2}[\d]{1,2}被扩在小括号表达式中,他们已经作为一个整体被视为一个元素
,这样的元素又有1或2个
,所以符合这个表达式的字符串可能是这个样子的:a12bc1
所以说这么一个难看的表达式一点一点拆解我们也是能看懂的是吧
-
. 点表达式,标识除“\r\n”之外的任何单个字符
-
| 或表达式,表示 | 前后的两个元素中任意一种情况
到了这里,你已经可以写出任意一个表达式了,这些就够了。
比如说既包含字母又包含数字的字符串:
\d{0,}([a-z]{1,}\d{1,}){1,}[a-z]{0,}
这是一个比较丑的表达式,不过他真的能匹配所有情况,你可以慢慢想一下(别太仔细想,虽然可以这么用不过太单纯了,没人这么用的)。
如果说你不介意,到这里,你已经学够了,我似乎看到了你满足的表情。
满足了Level2
你的手没有因为看到上面那张图而离开鼠标😏,而是继续看到这说明你已经不是随随便便就能春心荡漾的小朋友了,那么现在请你系好安全带,要发车了!
要发车了!所以说单纯一个同时包含数字字母就要这么臭又长的表达式的话,正则表达式也就真没有存在的意义了。这种有先决条件的表达式我们要怎么处理呢?
是否能先处理先决条件再进行匹配呢?
答案当然是肯定的,你要了解预查模式
。
所谓预查模式就是首先检查整个元素
是否满足条件,满足后再进行逐一匹配
。
预查有下面几种形式:(下列pattern均代表表达式)
- (?=pattern) 正向肯定预查
- (?!pattern) 正向否定预查
所谓正向就是固定字符串在前,条件字符串在后。肯定就是包含条件中的字符串,否定就是不包含条件中的字符串。
经典的例子就是windows(?=2000|Vista),首先固定字符串是windows,即字符串中一定要包含windows。条件字符串是包含2000或者Vista,所以windows2000和windowsVista都可以正确匹配,这是正向肯定预查。举一反三的你相信正向否定,反向肯定,反向否定都能理解了吧知道怎么用了吧。
此处我已经假设你通过自己的感悟已经理解了反向预查,只写式子了(如果没明白在慢慢想会,想累了就回头看看Level1最后的图片😏)。
- (?<=pattern) 反向肯定预查
- (?<!pattern) 反向否定预查
到这了你应该已经明白预查的含义,但是你跟我说你记不住这稀奇古怪的表达式。没关系,看好了,划重点啊,考试专门考这啊,老司机给你拆开:
“?”代表预查
“=”代表肯定
“!”代表否定
“<”代表反向
好了,你可以准备好仰天长啸了,大喊还有谁!
还有谁!恩,别嘚瑟,还有我!
事实上,我们用预查方式,更多的用到的是正向预查(反向预查通常有点反人类思维),而且用到他的变体更多:
- (?=[pattern]+$) 告诉我这是什么?“?”看到没有,预查,“=”看到没有?肯定预查,有没有“<”?没有,正向肯定预查!简不简单!!!!!额外解释一下,+等价于{1,},$代表的是字符串结尾(所以说一些特殊的助记符真的是可以多学学的)。
所以这个条件如果放在一个表达式的最开始就说明:
1.固定字符串没有,那么条件字符串就是开头了
2.条件字符串至少一个
3.条件字符串匹配到字符串结尾,所以条件字符串就是字符串结尾了
所以说结论是什么?就是字符串从头到尾都由pattern组成,也就是allIs。
- (?!.*[pattern].*) 告诉我这是啥?诶对了,正向否定预查!老司机再带你捋一个啊。
1.固定字符串没有,条件字符串开头了
2.条件字符串由任意任意字符开头,中间是条件字符串,后面又是任意个数任意字符,说明什么,条件就是包含pattern对吧!
3.这是什么?否定预查,所以是什么?不包含!
结论?字符串不包含pattern,什么意思?从头到尾都不是pattern,对不对?!!!allNot,诶,英语就是这么溜!
那么老司机就不一一带着你捋了,自己感悟啊!
-
(?![pattern]+$) 直接给我说答案?是什么?诶,不全是pattern对不对?英语是什么??notAll!
-
(?=.*[pattern]) 是不是包含?是不是!诶,对了!contain!对不对!!!!就问你对不对!
现在来,给我回到上面的需求,同时包含字母和数字怎么写?
是不是((?=.*\d)(?=.*[a-zA-Z]))[\da-zA-Z]*
?!简单不简单,透彻不透彻!!!
这回是不是又觉得自己无敌了!还有谁!!!
别闹,还有我啊~
还有我啊~Level3
还有什么?还有多着呢,那么多助记符、那么多控制符、负值字符范围、边界匹配、获取匹配、贪婪模式等等。。。
恩,我发现关注的人很多,所以今天来把Level3中内容补充一下。
- ^ 负字符范围。表示排除此字符集的其他范围。
[^\d]
即标识除数字外的其他字符合集。
实际使用中,他是可以配合预查来完成一些任务。
比如说要表示除了4的数字合集,你可以有几种表达方式:
- [012356789]
- [0-3,5-9]
- (?=\d+$)([^4]*)
前两个不说了,可读性很强,第三个,先是预查并且是allIs模式
对吧,就限制了全由数字
组成,然后用负字符范围排除了4
,这样就是排除了4的数组集合。不过你可能说你用第二种方式可读性高且也符合
,不过如果需求编程全部中文排除“我们”这两个字符呢,你要怎么搞。。所以说它是有它的应用范围
的。没有最合理的,只有最适合的
。
- \b 边界匹配,要求表达式要在字符串的边界。此处边界通常以结尾或者空格进行区分。
eg.比如说要匹配所有以er结尾的字符串,你可以这样
[a-z]*(er)\b
,他能匹配her,但是verb就不行。
同样的\B
就是不在边界。
- 贪婪模式,及尽可能的多去匹配。我们的正则引擎一般是默认贪婪模式的。如果想切换为非贪婪模式则在范围表达式后添加"?"来表示当前为非贪婪模式。
eg.字符串abcdefg,你要获取不重复的所有长度至少为2的子串,你可以这样写
[a-z]{2,}
,这时你获得的结果将只有一个abcdefg
,因为贪婪模式下会尽可能的多匹配,而下限是2上限没有,所以匹配到整串结果。如果你这样写[a-z]{2,}?
,那么当前条件被转换为非贪婪模式。你获得的结果是ab
、cd
、ef
三个结果。
- 获取与非获取,准确的说这不是作为条件出现的。上文中提到的括号表达式,就是否会获取结果。如上述中提到的预查模式,实际上都是非获取模式,就是并不会保存结果,只是提前对整串进行校验。而获取匹配到的结果并供后面的表达式使用。这个还是比较难以理解的,老司机要配合例子或许你才能懂。
eg.需求1. 找出字符串abbbccc中任意连续出现3次的字母。
乍一看任意连续出现三次的字母你怎么写?[a-z]{3}
这样么?这样子可不行,这可没规定连续。首先我们知道,一个正则表达式是由多个正则表达式组成的。所以你现在想的是不是我获取到一个符合结果的字符串,他的表达式应该由两部分组成,第一部分是匹配我要的第一个字母,第二部分是把第一部分匹配的字母重复两次。([a-z])
这样你获取到的第一个字母,然后我要使用第一个表达式的结果重复两次。所以你应该这么写([a-z])\1{2}
。老司机解释一下,这个表达式的意思是分成两部分([a-z])获取任意字符并将其保存在临时的地方作为一个后面可用的条件子串。\1
代表取出第一个临时子串,{2}
就是范围限定符,将前面取出的元素重复两次。所以abbbcbb中,当第一个字母a作为([a-z])
所保存的临时子串时,\1
就代表a,然而并没有连续的三个a,所以不符合。当b作为临时子串时,\1
代表b,后面有两个连续的b,符合结果。
再来一个深入理解一下。如果我想取出abbbccc中的bccc怎么办呢?我们的表达式应该由3部分组成,第一部分匹配一个字母,第二部分匹配一个字母,第三部分是第二部分重复两次。
([a-z])([a-z])\2{2}
么?不对,为什么?这样你将获取到abbb。因为a配[a-z],然后b匹配第二个[a-z],然后取出第二个结果b重复两次,那就是abbb。然后将从c开始继续检查后面的字符串。不过至少我们思路对了,只要我们不然abbb符合结果即可,简单修改([^a])([a-z])\2{2}
。这时a就不能作为第一个字母出现了,所以就轮到bccc了是吧。
最后一个例子不分拆讲解,想获取abbbcbb中的bcbb怎么办?看看是不是这个表达式:
([a-z])([a-z])\1{2}
。对的,不解释。
其余就是助记符了,虽然多,但都是等价替换,老司机不细讲,看看最开始的表吧。
助记符不算,另一种表达方式,控制符也就是几个特殊符号,负值、边界用起来也很简单,获取匹配、贪婪模式,多看看也能消化。而且这些Level3的东西都是锦上添花的东西,不必须掌握。
所以大声告诉我,正则会没会!!!
会!
简不简单!!!
简单!
老司机屌不屌!!!
屌!真屌!太屌了!非常之屌!24K纯屌!
然后放一些老司机搜集并验证过的常用正则吧:
类别 | 表格 |
---|---|
数字 | \d+ |
字母 | [a-zA-Z]+ |
中文 | [\u4E00-\u9FA5]+ |
所有符号 | [\W_]+ |
^[A-Za-z\d]+([-_.][A-Za-z\d]+)@([A-Za-z\d]+[-.])([A-Za-z\d]+[.])+[A-Za-z\d]{2,5}$ | |
手机号码 | 1[34578]\d{9} |
座机电话 | (0[\d]{2,3}-)?([2-9][\d]{6,7})(-[\d]{1,4})? |
自然数 | \d+(\.\d+)? |
URL这个放在表格里面格式就不对了,我就放外面了:
URL = ((http|ftp|https)://)?((([a-zA-Z0-9]+[a-zA-Z0-9_-]*\.)+[a-zA-Z]{2,6})|(([0-9]{1,3}\.){3}[0-9]{1,3}(:[0-9]{1,4})?))((/[a-zA-Z\d_]+)*(\?([a-zA-Z\d_]+=[a-zA-Z\d\u4E00-\u9FA5\s\+%#_-]+&)*([a-zA-Z\d_]+=[a-zA-Z\d\u4E00-\u9FA5\s\+%#_-]+))?)?
冲着老司机情人节不开房开博客的情怀,是不是应该有一大波赞!!!一大波关注!!!!一大波star!!!!!
不过熟悉老司机的童靴是知道老司机的套路的,老司机讲课要什么?
要赞!要关注!!要star!!!
这期带来的小工具:
正则工具类,这也是老司机的心血结晶啊,提供自然人思维的链式语法优雅的返回正则表达式。
虽然这点难以解释预置常用正则表达式。
预置校验1 预置校验2提供正则判断的工具类!
如果觉得好用可以给整个仓库一个star哟!整个仓库里面全都是工具类哟!上哪里去找这么好的仓库!!!仓库传送门
有童鞋说这个工具类不会使用,恩,我理解!因为提供的自由度较广所以传参比较多,所以老司机昨天发博客的时候自己想捋捋怎么用也回忆了一小下,那我就简单说一下吧。
首先,需明确的概念是,在这个工具类中,老司机是想以一种组件
的形式去生成正则表达式。组件对应的就是正则表达式中元素
的概念。
从头文件中可以看到,老司机使用一个枚举(严格点这是按位掩码)定义了组件类型DWRegexComponent。使用的时候你可以
DWRegexComponentNumber |DWRegexComponentUppercaseLetter
这样来表示数字和大写字母同时作为组件元素。
第二个概念是条件
。其实上面的教程里你应该发现了正则就是用一个个小的表达式组成一个大的表达式
。而老司机这里就是一添加条件的方式来添加表达式
。我用了另一个枚举类型DWRegexCondition定义了6种条件方式。其中4种是预查,allIs,allNot,notAll,contain,这些都是预查条件,所有都有PreSearch前缀。还有两种子式条件contain和without,即包含与非包含。
恩,两个枚举完事了,搞定剩下三个api,你就能用它写出正则了。
说说为什么会是链式语句呢?因为链式语句从形式上更能代表人的顺向思维
,所谓想到哪写到哪,所以采用了链式语句生成正则,用过masonry的童鞋一定可以很快上手。
首先调用+dw_GetRegexStringWithMaker:
方法来以block形式生成正则语句。
一下三个api都可以通过maker的点语法点出来,类比masonry就好了。
AddConditionWithComponentType()
这个方法你要传6个参数,有点多呵呵🙃。
分别是组件、额外字符串、条件、最小匹配数、最大匹配数及是否为贪婪模式
。
有了枚举的介绍其实你应该仅不知道额外字符串是什么鬼。
首先以组件来做成基本元素
,然而老司机预置的组件中可能不能代表全部你要的元素
,所以可以通过额外字符串来对组件进行补充
。然后以组件和额外字符串作为元素
,配合条件即范围生成一条正则表达式
。
上图例子中的,详情请见上面图的第一个表达式。
AddConditionWithComponentRegexString()
举一反三的想一下,套路是一样的。你需要5个参数,子串、条件、两端范围及贪婪模式
。
应用场景就是你不想用组件模式生成元素,而是想手撸一个元素集合
时,子串即传入你的元素集合,在添加条件、范围即可
,例子见第二个表达式。
AddConditionWithCompleteRegexString()
仅需要传入两个参数,即你连范围都能自己撸,你就写一个完整的正则表达式作为元素
,添加条件组成一个更加丰富的正则表达式
。
所以说借助这三个api加以你灵活的使用
你就能生成所有你想要的表达式,只要你能够把大需求拆分成一个个小条件。事实上第一个api自由度最高,你熟练使用这个就行
。
最后,这三个api中如果你的条件模式为预查
,则无论你在什么位置
添加条件,最后都会自动的拼接在正则表达式的前方
。而子式条件添加的顺序即为子式的顺序
。
恩,有了讲解配合上面的例子,这个工具类应该就可以使用了。不是吹捧自己的东西,只要熟练使用第一个api
你真的能写出所有正则,你需要的只是学习一个api的成本。另外,用多了或许你就能很好的拆分了,然后自己也就能写了
。
另外,DWCoreTextLabel已经全面支持自动链接匹配了,包括数字、email、url链接、手机号等。欢迎star!!!跪求star!!!这是老司机主力推的库😢。
软广:
DWCoreTextLabel更新到现在已经1.1.6版本了,现在除了图文混排功能,还支持文本类型的自动检测,异步绘制减少系统的卡顿,异步加载并缓存图片的功能。
version 1.1.0
全面支持自动链接支持、定制检测规则、图文混排、响应事件
优化大部分算法,提高响应效率及绘制效率
version 1.1.1
高亮取消逻辑优化
自动检测逻辑优化
部分常用方法改为内联函数,提高运行效率
version 1.1.2
绘制逻辑优化,改为异步绘制(源码修改自YYTextAsyncLayer)
version 1.1.3
异步绘制改造完成、去除事务管理类,事务管理类仍可改进,进行中
version 1.1.4
事务管理类去除,异步绘制文件抽出
DWCoreTextLabelversion 1.1.5
添加网络图片异步加载库,支持绘制网络图片
插入图片、绘制图片、添加事件统统一句话实现~
一句话实现尽可能保持系统Label属性让你可以无缝过渡使用~
无缝过渡恩,说了这么多,老司机放一下地址:DWCoreTextLabel,宝宝们给个star吧爱你哟
爱你哟