今天来介绍一下正则表达式,以及其在Android中 扮演的重要角色
一.揭开正则表达式的神秘面纱
- 正则表达式的特点
- 表现形式比较高冷:
随便来一串:
- 表现形式比较高冷:
^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$
- 一旦学会了就会大大提升我们在检索数据时的效率。我们下面要举的例子就可以看出这一点。
2.正则表达式的原理。
- 有些人会认为正则表达式非常神奇,一定是用的一种特别高大上的算法或者技术来实现的,其实不然。正则表达式内部其实是用的字符串匹配,根据你写的正则表达式,来对目标字符串进行遍历。
3.正则表达式的效率。
- 没错,正则表达式也是讲效率的,同一个字符串,同一个目的,不同的正则表达式其效率可能差别很大。
注:我们在开篇就谈及正则表达式的原理和效率,不是为了做原理性的解释,只是为了打破正则表达式的神秘面纱,不要畏难,认真学习其中的奥秘。当然,正则表达式的知识可以写成一本上千页的书,我们这里只提到常用的用法,目的就是:能看懂,会应用。
二.用正则表达式实现数据检索
在开讲之前,我先写了一个Demo,实现了从我的博客首页扒取博客列表,并将它展现到一个ListView中,效果图如下:
用到的正则表达式:
检索博主: <title>(.*?)-.*</title>
检索博客列表:<span class="link_title">.*?>(.*?)</a></span>
怎么样,简单吧,轻松搞定数据检索。这个例子后面我会给出源码,现在我们开始从最简单的开始讲,相信本博客结束了之后,你也会轻松写出这个功能。
三.走进正则表达式的世界
1. 元字符:
脱字符号:^
意思:代表一行文本的开头
用处:当我们想从一行文本的开头处匹配时,那么这个字符是个很好的选择。美元符号:$
意思:代表一行文本的结尾
用处:当我们匹配一行文本的结尾时可以用到。单词分解符:\b
意思:代表一个单词的开始或者结尾
用处:我们可以用它来匹配整个单词。取非符号:^
意思:用在字符串组(下面会讲到)中,代表“非”的意思。
用处:这个符号和脱字符号是同一个字符,只是用在不同的地方表示不同的意思,下面的例子我们会用到它。点号:.
意思:你没看错,这个小圆点,代指任何一个字符。
用处:当我们对某个字符没有任何要求时,可以用它通配任意一个字符。
2. 字符串组:
- 何为字符串组?
我们可能会有这样一种场景,比如在如下字符中寻找以“fu”开头,以“k”结尾的单词,(有可能聪明的你直接想到了“fuck”,但是我们这里想找出除了“fuck”之外的,所有符合上述规则的单词!)
单词如下:
fugk,fukk,fuuk,fuok,fork,fuck,fuckyou,cat,door。
也许你一眼就看出来了:不就是fugk,fukk,fuuk,这三个吗。
但是计算机不会那么聪明。
显然,企图用“fu.k”来匹配的同学都是天真的。
有人会说,那找到之后直接把fuck去掉不就可以了,但这有两个问题?
1.这么简单的功能还要另外考虑,那还能叫“神奇的正则表达式”吗?
2.实际问题中,我们往往会需要排除某些字符,比如是个,那这就有点多了,人工排除就有点太low了。
以上问题可以用“fu[^c]k”来过滤。
相信聪明的你会发现,[^c]这种形式的字符串就是“字符串组”,它可以提供同一个字符的可选项。这里的意思是“^c”的单个字符,也就是除了“c”以外的字符。
但是,这里有个问题,“fuckyou”也会被匹配。
怎么解决这个问题呢。
这个很简单,“\bfu[^c]k\b”,搞定~
3. 环视:
- 什么是环视?
假设我们需要在下列一组人名中选出姓王的并且名字为两个字的同学,并把他们的名字打印出来(注意:不包含姓氏)
人名:张三,李四,王五,李二,老王,王可胜。
怎么来解决这个问题呢。。。是不是感觉无从下手呢。(别想着先过滤出姓王的人,然后再次过滤,这种方法太low,以后不会再考虑这种解决方案。)
这里就需要用到“环视”了。
先用一句话来描述我们遇到的问题:
1.一个姓名,以“王”开头。
2.名必须是两个字,也就是姓氏+名字总共三个字。
我们可以用这个表达式:“\b(?<=王)..\b”
其中(?<=王)这种形式就叫做“环视”, 其实是一个“逆序肯定环视”。它的意思是:在“某个字符之前有某个字符”
这么说起来有点抽象,我们想整体介绍一下环视,然后再帮助大家理解。
环视的种类 | 符号表示 | 具体含义 |
---|---|---|
顺序肯定环视 | (?=…) | 某个字符后面有某个字符 |
顺序否定环视 | (?!…) | 某个字符后面没有某个字符 |
逆序肯定环视 | (?<=…) | 某个字符前面有某个字符 |
逆序否定环视 | (?< !…) | 某个字符前面没有某个字符 |
是不是有些晕呢,下面我举几个例子,然后用环视给出答案,大家自己揣摩吧,相信聪明的你一定可以看得懂。
1.顺序肯定环视
比如我们想匹配”hellochillax helloxiao”里里面的“hello”,但是有个要求:在“hello”后面必须有”chillax”这个字符。
我们可以这样做:“hello(?=chillax)”
2.顺序否定环视
还是上面的这个字符串“hellochillax helloxiao”,这次,要求变了:在“hello”后面不能有”chillax”这个字符。
我们可以这样做:”hello(?!chillax)”
3.逆序肯定环视
比如我们想匹配”hellochillax xiaochillax”里里面的“chillax”,但是有个要求:在“chillax”前面必须有”hello”这个字符。
我们可以这样做:“(?<=hello)chillax”
4.逆序肯定环视
比如我们想匹配”hellochillax xiaochillax”里里面的“chillax”,但是有个要求:在“chillax”前面不能有”hello”这个字符。
我们可以这样做:“(?< !hello)chillax”
爽不爽,其实不难,好好体会一下,很容易就会明白。
4. 量词的三个分类:
分类 | 量词(以“,”分割) | 特性 |
---|---|---|
匹配优先量词 | *,+,?,{min,max} | 尽可能多的匹配 |
忽略优先量词 | *?,+?,??,{min,max}? | 尽可能少的匹配 |
占有优先量词 | *+,++,?+,{min,max}+ | 类似于匹配优先,但一旦匹配就不会退还,类似于“固化分组” |
这个,也不难。
1.匹配优先量词。
这个模式是默认的模式,也就是说,默认不加任何修饰时,正则表达式总是“匹配优先的”。”匹配优先量词“也称”贪婪模式“
假设我们有以下这个场景:从”abcdefghi-jklmn-abkdfljsj”中进行匹配开头到第一个“-”之间的所有的字符。也就是“abcdefghi-”。(这里我们先带着“-”,暂时还去不掉,后面我们讲”捕获“,就可以实现去掉它了。)
你可能会想到用:“\b.*-”(当然这里会涉及到反斜杠转义的问题,这里先不提,等到讲Java应用时再说。)
但是你可以自己去试试,它会匹配”abcdefghi-jklmn-“,而不是预期的“abcdefghi-”。也就是,这种模式下,它是默认匹配最多字符的。
这里我们可以用”忽略优先量词“,即 “\b.*?-“,这样就会正确匹配了。
2.忽略优先量词。
正如上面的那个例子说的那样,”忽略优先量词“总是匹配最少的字符,因此又被成为”勉强模式“。
咦,我发现在编辑器中会出现正则表达式被识别成其他字符的问题。啊,这个怎么办。。。
用语言描述吧。。。
上例中,我们希望用点号和星号来匹配开头和-之间的字符,但是我们必须把它设定为”勉强模式“,所以就有了”\b.*?-“。
3.占有优先量词。
这个一般用不到,如果想弄明白的话,还要学习一些关于引擎的知识以及工作原理。这里不细说了,大家搞明白上面两个就够用了。
5. 捕获vs取消捕获:
- 捕获
用法:“捕获”的用法特别简单,把想捕获的字符用括号“()”括起来就行。
- 取消捕获
用法:如果不想“捕获”,而又必须使用“()”,那么用这种形式的括号就可以取消捕获:”(?:…)”
6. 模式修饰符(modifier):
modifier | 作用 |
---|---|
(?i…) | 不区分大小写 |
(?-i…) | 取消不区分大小写 |
(?x…) | 宽松排列和注释模式 |
(?s…) | 点号通配模式 |
(?m…) | 增强的行锚点模式 |
7. 引擎(engine):
engine | 特点 |
---|---|
DFA | 文本主导 |
NFA | 表达式主导 |
8. 正则表达式在Java中的应用:
(啊,好累,不写了,如果大家有需要,再留言吧,恕我就懒惰一次了。)