当前位置: 首页 > 科技观察

正则表达式真的很酷,可惜你不会写!

时间:2023-03-18 02:16:02 科技观察

正则基础1.元字符2.重复限定符3.分组4.转义5.条件或6.区间正则化进阶知识1.零宽度断言2.捕获与非捕获3.反向引用4.贪心与非-greedy5.几乎所有语言都可以使用反义正则表达式,不管是前端JavaScript,还是后端Java,c#。它们都提供了相应的接口/函数来支持正则表达式。但令人惊奇的是:无论你在大学里选择哪门计算机语言,都没有适合你的正则表达式课程。在没有学习正则表达式之前,只能看那些正则高手,写出一系列的外星人。类似文本的字符串,而不是你使用大量的ifelse代码来做一些数据验证。既然喜欢,那就自己去学吧,可是当你百度出一堆相关资料的时候,发现一个个都极其枯燥难学。本文旨在用最流行的语言讲述最枯燥的基础知识!正则表达式基础知识1、元字符注定一切,正则表达式也是如此。元字符是构造正则表达式的基本元素。我们先记住几个常用的元字符:元字符描述。匹配除换行符外的任意字符w匹配字母或数字或下划线或汉字s匹配任意空白字符d匹配数字匹配单词的开头或结尾^匹配字符串的开头$匹配字符串的结尾有了元字符后,我们可以用这些元字符写一些简单的正则表达式,例如:匹配以abc开头的字符串:abc或^abc匹配8位数字QQ号码:^dddddddddd$匹配11位以1开头的手机号码:^1ddddddddddd$2.repeatqualifier有了元字符,你可以写出很多正则表达式,但是细心的你可能会发现:别人写的regex简洁明了,而你写的regex是由一堆乱七八糟的重复的元字符。正则表达式不提供处理这些重复元字符的方法吗?答案是肯定的!为了处理这些重复问题,正则表达式中的一些重复限定符用适当的限定符代替了重复的部分。让我们看看下面的一些限定符:语法说明*重复零次或多次+重复一次或多次?重复零次或一次{n}重复n次{n,}重复n次或多次{n,m}重复n到m次有了这些限定符,我们就可以对正则表达式进行修饰,例如:匹配8位QQ号:^d{8}$匹配11位以1开头的手机号:^1d{10}$匹配银行卡号14~18位:^d{14,18}$匹配以a开头,以0个或多个b结尾的字符串^ab*$3。分组从上面的例子(4)可以看出,限定符作用在最靠近它左边的字符上,那么问题来了,如果我要同时限制ab呢?括号()用于正则表达式中的分组,即把括号内的内容作为一个整体。所以当我们要匹配多个abs时,我们可以这样做:匹配的字符串开头包含0到多个abs:^(ab)*4。转义我们看到正则表达式是用括号分组的,那么问题来了:如果要匹配的字符串中包含括号,是不是冲突了?应该做什么?针对这种情况,正则化提供了一种转义方法,即将这些元字符、限定词或关键字转义为普通字符。方法很简单,就是在要转义的字符前面加一个斜线,就是这样。例如:要匹配(ab)的开头:^((ab))*5。条件还是回到我们刚才的手机号码匹配,我们都知道:国内号码来自三大网络,都有自己的号码段,比如联通有130/131/132/155/156/185/186/145/176等。如果我们要匹配一个联通号码,那么按照我们目前所学的规则,应该是无法启动的,因为这里面包含了一些并列条件,即“or",那么它在正则表达式中是怎么表示"或"的呢?正则表达式使用符号|表示或,也称为分支条件。当满足正则表达式中的任一分支条件时,即认为匹配成功。那么我们可以用OR条件来处理这个问题^(130|131|132|155|156|185|186|145|176)d{8}$6.看上面的区间例子,是不是看出了什么?法律?是否还有简化的冲动?事实上,一些正则表达式提供了一个元字符括号[]来表示区间条件。限制0到9可以写成[0-9]限制A-Z可以写成[A-Z]限制某些数字[165]我们把上面的正则也改成这样:^((13[0-2])|(15[56])|(18[5-6])|145|176)d{8}$好了,正则表达式的基本用法到此结束。其实它还是有很多知识点和元字符的。我们这只是一些元字符和语法的列表。旨在为那些不了解正则表达式或想学习正则表达式但看不懂文档的人提供快速入门教程。看完这个教程,就算你写不高,至少你可以写出一些简单的规则,或者看懂别人写的规则。常规进阶知识点1.零宽断言无论是零宽还是断言,听上去都很奇怪,所以先解释一下这两个词。断言:俗话说的断言是“我决定某事”,而正则中的断言,即正则可以表明在指定内容之前或之后会有满足指定规则的内容,意思是正则也可以判断what和whatlikehumanbeings,比如“ss1aa2bb3”,正则表达式可以通过assertion查出aa2前面有bb3,也可以查出aa2后面有ss1。零宽度:没有宽度。在正则表达式中,断言只匹配位置,不占用字符,也就是说,匹配结果中不返回断言本身。意思是,你明白了,那他有什么用?举个栗子:假设我们要用爬虫抓取csdn的文章阅读量。查看源码可以看到,文章阅读量的内容是这样一个结构“Readingcount:641”,其中'641'是一个变量,也就是说,是不同文章的不同值,我们在获取这个字符串的时候,获取这里的'641'有很多种方法,但是如果是正则,应该怎么匹配呢?先说几种断言类型:Positivelookaheadassertion(正向前瞻断言):语法:(?=pattern)功能:匹配模式表达式前面的内容,不返回自身。这样一来,我还是一头雾水,好吧,我们还是回到刚才的栗子,获取阅读量,在正则表达式中,表示能够匹配''前面的数字内容,后面跟着上面提到的正向断言可以匹配表达式前面的内容,也就是说:(?=)可以匹配它前面的内容。它匹配什么?如果要全部内容就是:Stringreg=".+(?=)";Stringtest="Readcount:641";Patternpattern=Pattern.compile(reg);Matchermc=pattern.matcher(test);while(mc.find()){System.out.println("匹配结果:")System.out.println(mc.group());}//匹配结果://读取次数:641不过兄弟,我们要的只是前面的数,很简单,匹配数字d,那么可以改成:Stringreg="\d+(?=)";Stringtest="阅读次数:641";Patternpattern=Pattern.compile(reg);Matchermc=pattern.matcher(test);while(mc.find()){System.out.println(mc.group());}//匹配结果://641大功告成!前向后向断言(positivebackwardlook):语法:(?<=pattern)功能:匹配模式表达式后面的内容,不返回自身。如果有第一行,就会有第二行。第一行是匹配前面的内容,最后一行是匹配后面的内容。对于上面的栗子,我们也可以用post-assessment来处理//(?<=readingcount:)d+Stringreg="(?<=读数数:)\d+";Stringtest="读数数:641";Patternpattern=Pattern.compile(reg);Matchermc=pattern.matcher(test);while(mc.find()){System.out.println(mc.group());}//匹配结果://641就这么简单。否定先行断言(negativelookahead)语法:(?!pattern)功能:匹配非模式表达式之前的内容,不返回自身。有正有负,这里的负其实就是胡说八道。举个栗子:比如有一句话“我爱祖国,我是祖国的花朵”。现在要找不是前面那朵“花”的祖国,可以这样写:祖国(?!花)负向后断言(negativeback)语法:(?0\d{2})-(?\d{8})";Patternpattern=Pattern.compile(reg);Matchermc=pattern.matcher(test);if(mc.find()){System.out.println("组数为:"+mc.groupCount());System.out.println(mc.group("quhao"));System.out.println(mc.group("haoma"));}输出结果:组数:2组名为:quhao,the匹配内容为:020组名:haoma匹配内容为:85653333非捕获组:语法:(?:exp)说明:与捕获组相反,用来标识那些不需要捕获的组被捕获。更简单地说,您可以根据需要保存您的组。比如上面的正则表达式,程序不需要使用第一组,所以可以这样写:(?:d{2})-(d{8})序号组内容00(0d{2})-(d{8})020-8565333311(d{8})85653333验证:Stringtest="020-85653333";Stringreg="(?:0\d{2})-(\d{8})";Patternpattern=Pattern.compile(reg);Matchermc=pattern.matcher(test);if(mc.find()){System.out.println("组数:"+mc.groupCount());for(inti=0;i<=mc.groupCount();i++){System.out.println("+i+"thgroupingis:"+mc.group(i));}}输出结果:分组数是:1.第0组是:020-85653333第1组是:856533333回引用上面提到的capture,我们知道:capture会返回一个capturegroup,这个group存在内存中,不仅可以引用通过程序在正则表达式外部引用,也可以在正则表达式内部引用。这种引用就是反向引用。根据捕获组的命名规则,反向引用可以分为:数字组反向引用:k或umber命名numbergroupbackreferences:kor'name'好了,说完了,你明白吗?不知道!!!可能你连上面说的capture的用法都不明白?其实看capture就很正常而且不知道怎么用!因为capturegroups一般是和backreferences一起使用的,据说capturegroups是匹配内容的的子表达式,并通过序列号或名称保存它们以供使用。注意两个词:“内容”和“使用”这里所说的“内容”,是匹配结果,而不是子表达式本身,强调这个有什么用呢?嗯,先记住这里说的“使用”是怎么来的used?因为它的作用主要是查找一些重复的内容或者替换指定的字符。举个栗子:比如我们要在一个字母“aabbbgbdddesddfiid”的字符串中找出字母对,如果按照之前学过的规则,什么区间,限制,断言都可能做不出来。下面我们用程序思维来算一下思路:1)匹配一个字母2)匹配下一个字母,检查是否与上一个字母相同3)如果相同则匹配成功,否则匹配失败。这里,思路2中,在匹配下一个字母的时候,需要用到上面的一个字母,如何记住上一个字母呢???现在捕获很有用。我们可以使用capture,将上一次匹配成功的内容作为本次匹配的条件。有想法的话,首先要匹配一个字母:w我们需要对它进行分组来捕获它,所以写成这样:(w)然后这个表达式有一个捕获组:(w)然后我们需要使用这个捕获组作为条件,那么我们可以:(w)然后你就完成了。可能有些人不明白,那是什么?你是什??么意思?还记得捕获组有两种命名方式吗,一种是按照捕获组的顺序命名,另一种是自定义命名,捕获组默认是按数字命名的,数字的顺序是从1开始,所以要引用第一个捕获组,根据反向引用的编号命名规则,k<1>或者当然,通常是后者。让我们测试一下:Stringtest="aabbbgbddesddfiid";Patternpattern=Pattern.compile("(\w)\1");Matchermc=pattern.matcher(test);while(mc.find()){System.out.println(mc.group());}输出结果:aabbbbddddii好了,这就是我们想要的。举个替换的例子,如果想把字符串中的abc替换成aStringtest="abcbbabcbcgbdddesddfiid";Stringreg="(a)(b)c";System.out.println(test.replaceAll(reg,"$1"));;输出结果:abbabcgbddesddfiid4。贪心与不贪心1.贪心我们都知道,贪心就是不满足,想要的越多越好。在正则方面,贪心类似于:贪心匹配:当正则表达式包含可以接受重复的限定符时,通常的行为是匹配尽可能多的字符(在可以匹配整个表达式的前提下),这种匹配方式称为贪心匹配。特点:一次读取整个字符串进行匹配,当没有匹配时丢弃最右边的字符,继续匹配,依次匹配丢弃(这种匹配丢弃的方法也称为回溯),直到匹配成功或整个字符直到字符串被丢弃,是优化后的数据返回,可以多也可以少。我们之前讨论过重复限定符。事实上,这些限定符是贪心量词。例如,表达式:d{3,6}用于匹配3到6位数字。在这种情况下,它是一个贪婪的模式匹配。即如果字符串中有6个数字可以匹配,那么就全部匹配。如Stringreg="\d{3,6}";Stringtest="617628281762991871";System.out.println("文本:"+test);System.out.println("贪心模式:"+reg);Patternp1=Pattern.compile(reg);Matcherm1=p1.matcher(test);while(m1.find()){System.out.println("匹配结果:"+m1.group(0));}输出结果:text:61762828176299144871贪心模式:d{3,6}匹配结果:617628匹配结果:176匹配结果:2991匹配结果:871从结果可以看出:原字符串中的“61762828”其实只需要出现3(617)已经匹配成功,但他并不满足,而是匹配了最多的匹配字符,即6个字符。一个量词这么贪心,有人会问,如果多个贪心量词凑在一起,怎么控制它们的匹配权呢?是这样的,当多个贪心字符串在一起时,如果字符串能够满足各自的最大匹配度,则不会相互干扰,如果不能满足,则按照深度优先原则,即就是,从左到右对于每一个贪心量词,先满足最大的数量,剩下的分配给下一次匹配的量词。Stringreg="(\d{1,2})(\d{3,4})";Stringtest="61762828176299187321";System.out.println("文本:"+test);System.out.println("贪心模式:"+reg);Patternp1=Pattern.compile(reg);Matcherm1=p1.matcher(test);while(m1.find()){System.out.println("匹配结果:"+m1.group(0));}输出结果:文本:61762828176299187321贪心模式:(d{1,2})(d{3,4})匹配结果:617628匹配结果:2991匹配结果:87321"617628"为前d{1,2}匹配61,后面匹配7628"2991"是前d{1,2}匹配29,后面匹配91"87321"是前面d{1,2}匹配87,而followingmatchgives3212.Lazy(非贪婪)惰性匹配:当正则表达式包含可以接受重复的限定符时,通常的行为是(在整个表达式都可以被匹配的前提下)匹配尽可能少的字符,这种匹配方法称为惰性匹配。特点:从左到右,从字符串的最左边开始匹配,每次不读入字符就尝试匹配,如果匹配成功则完成匹配,否则读入一个字符再匹配,以此类推on(readincharacters,match)直到匹配成功或者匹配到字符串的字符。惰性量词是加一个“?”贪婪量词*?后的代码说明重复任意次数,但尽可能少重复+?重复1次或多次,但尽可能少重复??重复0次或1次,但尽可能少地重复{n,m}次?重复n到m次,但越少越好{n,}?重复n次以上,但尽可能少Stringreg="(\d{1,2}?)(\d{3,4})";Stringtest="61762828176299187321";System.out.println("Text:"+test);System.out.println("贪心模式:"+reg);Patternp1=Pattern.compile(reg);Matcherm1=p1.matcher(test);while(m1.find()){System.out.println("匹配结果:"+m1.group(0));}输出结果:text:61762828176299187321greedyMode:(d{1,2}?)(d{3,4})匹配结果:61762匹配结果:2991匹配结果:87321答案:“61762”是左边懒匹配得到6,贪心匹配到右边得到1762“2991”是左边懒匹配得到2,贪心匹配右边匹配991"87321"左边惰性匹配匹配8,右边贪婪匹配匹配73215。如果你想反过来做,不想匹配某些字符,正则化还提供了一些常用的反义元字符:元字符解释W匹配任何不是字母、数字、unde的字符rscore,即汉字S匹配任意非空白字符D匹配任意非数字字符B匹配任意非词首非词尾的位置[x]匹配除x以外的任意字符[aeiou]匹配任意字符除了aeiou正则进阶知识就到这里了,正则是一门博大精深的语言,其实学习它的一些语法和知识点并不算太难,但是想要真正应用到什么,还有很长的路要走你已经学习和编写了非常多的6条规律,只有真正感受到它的兴趣,并经常学习和使用它,你才会逐渐了解它的奥妙。我会带你到这里,剩下的就看你的了。