重学正则表达式(四)

进过前面的学习,我们已经可以使用正则解决很多场景的问题了,但是来看这样一个场景。原文如下:tom and helen are best friends, tomorrow they will go to school together.要求替换语句中的 tom 为 hanmeimei。easy!

		str = "tom and helen are best friends, tomorrow they will go to school together.";
        regex = "tom";
        str = str.replaceAll(regex,"lilei");
        log.info(str);

运行上面的代码,我们得到了

lilei and helen are best friends, lileiorrow they will go to school together.

请问 lileiorrow是个什么东西?oh mygod!为什么和我们预期的不一样。想必大家已经看出来了,因为tomorrowtom满足了,我们的正则表达式tom,所以tomorrow被替换成了lileiorrow。知道了原因所在,那么解决的思路也就出来了,即我们的正则需要有一个边界的概念,我要匹配的就是 tom这三个字母,m就是结尾,后面不应该紧跟其他的字母!也就是说,在有些情况下,我们对要匹配的文本的位置也有一定的要求。为了解决这个问题,正则中提供了一些结构,只用于匹配位置,而不是文本内容本身,这种结构就是断言。常见的断言有三种:单词边界、行的开始或结束以及环视。

单词边界(Word Boundary)
单词的组成一般可以用元字符 \w+ 来表示,\w 包括了大小写字母、下划线和数字(即 [A-Za-z0-9_])。那如果我们能找出单词的边界,也就是当出现了\w 表示的范围以外的字符,比如引号、空格、标点、换行等这些符号,我们就可以在正则中使用\b 来表示单词的边界。 \b 中的 b 可以理解为是边界(Boundary)这个单词的首字母。因此,上面的的例子我们只需要把正则调整为 \btom\b,也可以用^tom$就可以得到我们想要的结果了。

行的开始或结束
和单词的边界类似,在正则中还有文本每行的开始和结束,如果我们要求匹配的内容要出现在一行文本开头或结尾,就可以使用 ^$ 来进行位置界定。
在计算机中,回车\r和换行\n其实是两个概念,并且在不同的平台上,换行的表示也是不一样的。windows的换行符使用\r\n表示,linux、macos使用\n表示。那么,匹配行的开始或结束有什么用呢?最常见的例子就是日志收集,我们在收集日志的时候,通常可以指定日志行的开始规则,比如以时间开头,那些不是以时间开头的可能就是打印的堆栈信息。在这种情况下,我们就通过日期时间开头来判断哪一行是日志的第一行,在日期时间后面的日志都属于同一条日志。除非我们看见下一个日期时间的出现,才是下一条日志的开始。
再来看一个例子,web开发中我们经常需要校验用户输入的信息,例如某个输入框要求用户输入6位数字,如果你的校验正则是这样的\d{6},你会发现 1234561234567都能匹配上。在多行模式下,^ 和 $ 符号可以匹配每一行的开头或结尾。
解决这个问题还有一种做法,我们可以在使用正则校验前,先判断一下字符串的长度,如果不满足长度要求,那就不需要再用正则去判断了。

环视( Look Around)也叫先行断言
环视就是要求匹配部分的前面或后面要满足(或不满足)某种规则,有些地方也称环视为零宽断言。
举个栗子:邮政编码的规则是第一位是 1-9,一共
有 6 位数字组成。现在要求你写出一个正则,提取文本中的邮政编码。根据规则,我们很容易就可以写出邮编的组成 [1-9]\d{5}
使用以下3个案例进行匹配:

123456 可以正常匹配到结果 123456
1234567 匹配到了 123456
最离谱的是 123456654321 竟然匹配到了 123456、654321两个结果

我们需要的是只匹配满足条件的6位数字,不能多也不能少,也就是说,除了文本本身组成符合这 6 位数的规则外,这 6 位数左边或右边都不能是数字。
正则是通过环视来解决这个问题的。解决这个问题的正则有四种。

正则 名称 示例
(?<=expression) 零宽正向后行断言 (?<=\d)cat左边是数字的cat,可以匹配到0cat
(?<!expression) 零宽负向后行断言 (?<!\d)cat左边不是数字的cat,可以匹配到ccat
(?=expression) 零宽正向先行断言 six(?=\d)右边是数字的six,可以匹配到six6
(?!expression) 零宽负向先行断言 six(?!\d)右边不是数字的six,可以匹配到sixx

<代表看左边,没有 <是看右边,!是非的意思

因此,针对刚刚邮编的问题,就可以写成左边不是数字,右边也不是数字的 6 位数的正则。即 (?<!\d)[1-9]\d{5}(?!\d)。这样就能够符合要求了。

其实表示单词边界的 \b 如果用环视的方式来写。单词可以用 \w+ 来表示,单词的边界其实就是那些不能组成单词的字符,即左边和右边都不能是组成单词的字符,(?<!\w) 表示左边不能是单词组成字符,(?!\w) 右边不能是单词组成字符,即 \b\w+\b 也可以写成 (?<!\w)\w+(?!\w)。另外,根据前面学到的知识,非\w 也可以用\W 来表示。那单词的正则可以写成 (?<=\W)\w+(?=\W)。当然单词的边界使用\b表示明显更简洁,也更容易阅读和书写。

再来看这个栗子:
要求匹配cat* dog* cat dog pig 中的 cat*dog*,如果你的正则是这样cat*|dog*,那么它匹配到的结果将会是catdogcatdog4个结果,而不是你希望的cat*dog*
这是因为*是我们学到的元字符,它在正则表达式中有特殊的含义,这时如果要匹配*字符本身,我们就需要对它就行转义,正则表达式中使用\进行转义。因此,上面的正则调整为cat\*|dog\*就可以匹配到我们想要的结果啦。

今天的内容就到这里了,我们下节见,由于本人对正则的认知有限,如文中有表达不到位或者错误的地方,欢迎大家批评指正,感谢。

系列文章如下:
重学正则表达式(一)
重学正则表达式(二)
重学正则表达式(三)
重学正则表达式(五)

猜你喜欢

转载自blog.csdn.net/hxj413977035/article/details/121358570
今日推荐