正则表达式教程
正则表达式教程
本文是搬运,来源是我五年以前在贴吧发的帖(
最近度娘不吃了,就整回来吧。以此缅怀忘却的初中时代。
不过现在再仔细看,发现已经都忘了
看着五年以前自己写的帖子学习.jpg
正则表达式是字符串学习重点,也是难点。lz我苦学一个国庆,对其略有收获,谨以此专题让大家对正则表达式有一些认识。

- 一、正则表达式的“卵用”
- 二、最简单的正则表达式
- 三、转义字符与元字符
- 四、分支、反义、与或非
- 五、Unicode与正则表达式
- 六、分组,注释
- 七、零宽断言
- 八、贪婪、懒惰、优先级
- 九、平衡组和递归匹配
- 附录一 元字符
- 附录二 常用正则表达式
笔者学习资料为 C语言中文网 。
本帖原载 百度贴吧链接
一、正则表达式的“卵用”
在Bilibili弹幕网的度娘词条下,有这么一句话:“……ID封禁一般是由触犯特定的正则表达式引起的……”我国庆没补成番怎么想也是正则表达式的错!由此可见,正则表达式的一大用途在于匹配。
而常用功能查找替换实质也是正则表达式。由此足矣见得正则表达式运用范围之广。
那么问题来了,什么是正则表达式?顾名思义,正则表达式就是正确法则表达式,是对于字符串的匹配操作时所用的表达式。
二、最简单的正则表达式


Java的正则表达式算法与.net相同,有专用的测试工具。lz我呢,没电脑,所以就随便安了个正则表达式测试工具。
在java中使用.maches方法,语法格式如下:
str.matches("String regex");
返回值为boolean型。例如:
1 | String str1=new String("Hi"); |
结果为True.
三、转义字符与元字符
在正则表达式中转义字符是很令人心烦的。在转义字符前我们必须引出一个概念,元字符。元字符是一系列正则表达式独有的表示特殊意思的符号。其典型代表即是\b。
\b是正则表达式的一个特殊字符。为什么这么说呢?它表示的是一个单词的开头结尾。假使要查找hi,万一碰见hime,history,his,him,hire,hippo,.etc就不妙了。因而来个开头结尾的标志是很有用的。因此,查找hi时我们可以用:\bhi\b
下面是一些常用的元字符:
.匹配换行符外的任意字符\w匹配字母或数字或下划线或汉字\s匹配任意空白符\d匹配数字\b匹配开始和结束^匹配开始&匹配结束
我们发现,^$等价于\b。
.表示任意一个字符。
而另一对元字符{}表示重复次数。例如:
\ba.{3}\b表示以a打头的4字母单词。不幸的是,这连ae33都能表示。因而我们可以改成:\ba[a-z]{3}\b,我们一会讲到。
{}有三种形式,{a},{a,},{a,b},分别表示a次,大于等于a次,和大于等于a小于等于b次。
表示重复的还有?(表示0-1次)*(表示大于等于0次)+(表示大于等于1次)
()表示优先级。这个我们先不讨论。
如果元字符为[,那么正表达式字符为何?答案是\[。换句话讲,\具有取消转义的作用。同样的,\.,\|,\$,\?等等的意思便不言自明了。
\n与\t很特别。它们既属于char型转义序列,又属于正则转义序列。因此,\t等价于\\t,\n等价于\\n.
但更复杂还在于后面。我们主要说的是java,而java又有所不同。在java中,正则表达式被””扩起,实质还是字符串。因而需要再一次转义:
原始字符 { \b \
正则表达式转义 \{ \\b \\
Java中maches方法调用表示 \\{ \\\\b \\\\
熟悉了java中转义调用后,我们之后就不再涉及有关问题了。
还要注意一点,成对出现的不可拆散。
例一:有理数
\d不支持大于等于10的数、负数、小数与虚数(你说的什么废话),换句话说它就是1-9。怎么确定是不是有限小数,我们来编辑一个正则表达式:
?-\d+?(\.\d+)
表示任意有理数。但虚数怎么办?大家下去思考一下。
四、分支、反义、与或非
假如想要查找一个英文单词是不是相对开音节(即aeiou+辅+e)怎么办?别着急,马上揭晓。
事实上,我们上一节埋了一个伏笔:[]与|。后者我们很熟悉,是或,而前者也是或。[12345_ah]表示1,2,3,4,5,_,a,h中任意一个。同时还可以[1-7],表示1-7中7个数中的一个。很简单w!
当然,[]内可以加个,分割
注意,[]内使用-最好进行转义。
因此\w=[a-zA-Z_1-9]
但是只有在[]内才用转义喔,千万要小心……
开头的问题便可以解决了:
\w+[aeiou]\we
另一个常见的是反义。下表列出了一些常见的反义字符:
\W不是字母、数字、下划线、汉字\S非空白字符\D非数字\B非开始或结束[^x]非x[^aeiou]非aeiou
换句话讲,\W=[^\w]=[^a-zA-Z_1-9],对吧?
例二 天朝电话号码
天朝电话号码有两种:3-8与4-7,分别又有许多种写法:010-85427975;01075426886;(062)75424675;(033)-75422466……
因而这是相当复杂的,特别是组合到一起的时候……
先来看3-7:
这个正则表达式是这么写的:
\(?0\d{2}\(?[- ]?\d{8}
但是残念的是,它还能匹配(010-86429765这种不正确的电话番号。怎么破?答案很简单:用|隔开。
\(0\d{2}\)[ -]?\d{8}|0\d{2}[ -]?\d{8}
怎么样,看懂了吗?试着匹配一下4-7吧!
例三 ip地址
这个可谓是相当坑爹的了,其表达式如下:
\d+\.\d+\.\d+\.\d+
然而这是不太对的,至少,1554.86454.86534.8753277合法吗?当然不合法!那怎么办?有人想出了这个:
\d{1,3}\.d{1,3}\.d{1,3}\.d{1,3}
然后进一步简化:
(\d{1,3}\.){3}\d{1,3}
然而还有问题:ip地址每个值不能超过255.但正则表达式没有定义任意一个数学运算,因而最后我们得到了这么一个奇异的表达式:
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
怎么样,试着分析分析吧!
然后我们来看一看补集、交集、并集:
[a-z[def]],等价于[a-zdef],指的是并集;
[a-z&&[def]],等价于[def],指的是交集;
[a-e&&[^db]],等价于[ace],指的是补集;
这就是正则表达式的与或非。
五、Unicode与正则表达式
如果你用的是Phython语言的话,这确实很难处理:涉及到GBK编码与Unicode一向很复杂。幸亏java默认Unicode,这节会比较简单。

是的——这是万恶之源——大家都能看懂,我就不说什么了
实际上还有几个:
\p{XDigit}表示16进制数字;\p{Space}表示空白字符;\p{InGreek}Greek块中的字符\p{Lu}大写字母\p{Sc}货币字符\P{InGreek}非Greek块中的字符
当然,p可以随意反义表示非,如\P表示非货币字符
注意根据Unicode编码,\u4e00-\u9fa5是中文字符,因而[\u4e00-\u9fa5]可用来匹配中文字符。
Unicode定义了三种形式,Unicode Property、Unicode Block与Unicode Script。JAVA支持前两种,第一种是按功能分类,即上述;第二种是按国家地区分类,这里举一例:\p{InCJK_Compatibility_Ideographs}表示中日韩文字符。
六、分组,注释
(exp表示某一表达式)
分组是方便表示的方法。这个名字是我为了理解起的,真实名字应该是捕获,或者后向引用……
小括号扩住的可以称作一组。组号随括号叠加而递增。引用时可以用\组号来定。
注意嵌套,前后为准。
例四 重复单词
本例想来探讨探讨连续单词的正则语法,而我们用以前的知识可以写出来:
\b\w+\b\s\b\w+\b
但是这样匹配出来并不是重复的。
可以用这种方法:
\b(\w+)\b\s+\1\b
注意,引用组的时候是需要用”+”连接的。这里是\1实质上是对第一个组的引用,组的标号从1开始。其效用是,将第一个捕获的结果存储起来,和数组一样放到一个组里。下次直接调用看是否相等。
除去数字默认的以外,组名也可以是自己起的,例如:
\b(?<QwQ>\w+)\b\s+\k<QwQ>\b
或者将<name>改为'name'
然而java似乎不支持……
注释则是和//一样的东西,格式为:
(?#exp)
随便写,反正不管
例如:(?#LittleBusters最高!)(\b\w+\b)\s+\1(?#lzsb)
七、零宽断言
XD…终于跋涉到了零宽断言…你会感到坑爹的…
什么叫断言呢?断言是某种条件,而零宽指的是某种位置。下面四种用法是零宽断言最常见的四种用法。
第一个是?=。这个零宽断言叫做零宽度正预测先行断言(啥?),卵用就在于,断言此位置之后能够匹配表达式。
什么?看不懂?哦。你若看得懂就是一个天才了。
那么来解释解释:
看一句话:
I’m singing while you’re dancing.
假设我们要查找一个单词中ing之外的东西怎么破?
我们来提供一个正则表达式:
\b\w+(?=ing\b)
那么在一个正则表达式工具测试截图如下:

换句话说,?:用于预测一个原文中是否有表达式之前的部分
第二个是?<=。这个零宽断言叫做零宽度正回顾后发断言(啥?),卵用就在于,断言此位置前面能匹配表达式。
同样看一句话:
This is a simple sample of the book.
我们来写这么一个正则表达式:
(?<=\bs)\w+\b
然后进行查找的话,结果是
imple ample
换句话说,?<=用于预测一个原文中是否有表达式之后的部分
假如还是看不懂,我们来这么解释一下:
两个正向零宽断言都是用于提供一个查找的索引,之后匹配索引之前(?<=)或之后(?=)的一些字符。
那么下面的两个零宽断言是负向零宽断言。
负向零宽断言的来源是,假设我们想要匹配一个单词,不是相对开音节,亦即不是元加辅加e,这个时候你可能会写出这么一个表达式:
\b\w*[^aeiou]\w*\b|\b\w*[aeiou]\w[^e]\b|\b\w*[aeiou]\w\b
很长对吧?而且对非单词没法处理,比如this?
那么怎么破?也许你会这么做:
将[^e]改为[a-df-z]
好吧你狠,然而你的隔壁却用了一个简单的负向零宽断言,然后解决了这个问题。他是这样写的:
(?!e)
?!,即零宽度负预测先行断言,断言此位置后面无法匹配表达式。例如,abc(?!def)表示abc后面不能匹配def.
相对应的,?<!,零宽度正回顾后发断言,断言此位置前面不能匹配表达式。例如,(?<![a-z])\d{7}匹配前面不是字母的七位数字。
下面一个例子是零宽断言的最重要运用之一。
(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内里的内容。(<?(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)。
恭喜,零宽断言结束。
八、贪婪、懒惰、优先级
正则表达式中有一项原则,前大于后。这个无须赘述。
我们主要要讨论的优先级是,贪婪/懒惰。
”当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。考虑这个表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:
a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。
常用如下:
*?重复任意次,尽可能少重复+?重复至少一次,尽可能少重复??重复0-1次,尽可能少重复{n, m}?重复n到m次,尽可能少重复
九、平衡组和递归匹配
这部分内容当时候没学,现在补上吧
其核心思想其实可以看成一个递归。当匹配到什么什么东西的时候就压入栈,然后规定什么时候出栈,最后用一个断言来讨论其正确性。
这一形式最常见的地方就在于多重嵌套的括号匹配问题。比如((()()))()如果直接匹配\(.*\),得到的是整个字符串;而如果用懒惰匹配\(.*?\),得到是((()。哪种都不符合人意。
(?'qwq')将捕获内容命名为qwq,并压入堆栈。如果有(?'-qwq'),就将栈顶出栈。(?(qwq)Branch1|Branch2)表示栈里是否有qwq,如果有执行branch1,没有执行branch2。
因此,我们可以写出下面的表达式:
1 | \( |
解释一下。首先匹配左右的两个括号,然后我们分组。在组内,我们讨论三种情况:
- 是
\(,压入堆栈 - 是
\),弹出栈顶 - 都不是,不操作
这一过程可以重复n次,直到我们见到下一个断言:
如果qwq还有,我们直接执行零宽断言(?!)。由于后继表达式为空,所以直接匹配失败。反之我们记匹配成功。
最后写起来,就是
\(((?<qwq>\()|(?<!qwq>\()|[^()]+)*(?(qwq)(?!))\)
附录一 元字符




附录二 常用正则表达式
中国电话号码验证
匹配形式如:0511-4405222 或者021-87888822 或者 021-44055520-555 或者 (0511)4405222
正则表达式 (\(\d{3,4})|\d{3,4}-)?\d{7,8}(-\d{3})*
中国邮政编码验证
匹配形式如:215421
正则表达式 \d{6}
电子邮件验证
匹配形式如:justali@justdn.com
正则表达式 \w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]w+)*
身份证验证
匹配形式如:15位或者18位身份证
正则表达式 \d{18}|\d{15}
非法字符验证
匹配非法字符如:< > & / ‘ |
正则表达式 [^<>&/|'\\]+
日期验证
匹配形式如:20030718,030718
范围:1900–2099
正则表达式((((19){1}|(20){1})\d{2})|\d{2})[01]{1}\d{1}[0-3]{1}\d{1}




