跳至主要內容

正则表达式

Mr.WenYue大约 13 分钟

正则表达式

转变你的思维一切当做字符处理

正则:具有一定规则的表达式,用于验证、匹配字符串。

元字符

字符意义
.匹配除换行符以外的任意字符
\d任意数字
\D非数字 [^0-9]
\w字母 数字 _
\W[^a-zA-Z0-9_]
\s任意空白字符
\S匹配任意不是空白符的字符(例子: \S+ 匹配不包含空白符的字符串)
\b边界
\转义字符
^匹配字符串的开始(一行的开头)
[^x]匹配除了x以外的任意字符(例子: <a[^>]+> 匹配用 尖括号括起来的以a开头的字符串。)
^aeiou]匹配除了aeiou这几个字母以外的任意字符
$匹配字符串的结束(匹配必须出现在字符串的末尾或出现在行或字符串末尾的 \n 之前.)
字符意义
出现0次或者1次
*出现0次或者多次
+出现1次或者多次相当于 {1,}
{m}出现m次
{m,}至少出现m次
{m,n}出现m~n次

(x|y) : x或y[] : 任意一个字符

[abc]:a b c 中的任意一个字符
[a-z]:任意一个小写字母
[A-Z]:任意一个大写字母
[a-zA-Z]:任意一个字母
[a-d[m-p]]:abcdmnop,取并集
[0-9]:任意一个数字

中文: [\u4e00-\u9fa5]

分支条件

如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。 当然分支的顺序也很重要: 例如: \d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编 的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。 如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮 编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

分组概念

分组 ()(.) 第一组:第一组内容的含义为:任意字符

引用 \num\num : 引用组(num代表组号) 组的编号从1开始(注:分组0对应整个正则表达式) $1 : 上一个正则表达式中第一组的内容 例如:\1 :引用第一组的内容

例如(用来匹配重复出现的单词): \b(\w+)\b\s+\1\b 可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词, 也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b), 这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)

例如:

String s2 = "asffasdfd^^^^dasd@@@@fe%%%seadflj000klajsdf";
//按照重复的字符串进行切割
String[] arr = s2.split("(.)\\1+");

String s4 = "aaabbbbbbddddddeeeeeeee####%%%CCCC";
//去掉重复的abde#%C
System.out.println(s4.replaceAll("(.)\\1+","$1"));//$1:上一个正则表达式中第一组的内容

零宽断言

语法(exp:表达式)说明
(?=exp)匹配exp前面的位置
(?<=exp)匹配exp后面的位置
(?!exp)匹配后面跟的不是exp的位置
(?<!exp)匹配前面不是exp的位置

(?=exp)也叫零宽度正预测先行断言, 它断言自身出现的位置的后面能匹配表达式 exp。 比如 \b\w+(?=ing\b), 匹配以 ing 结尾的单词的前面部分(除了 ing 以外的部分),如查找 I'm singing while you're dancing. 时,它会匹配 singdanc

(?<=exp)也叫零宽度正回顾后 发断言,它断言自身出现的位置的前面能匹配表达式exp。 比如 (?<=\bre)\w+\b 会匹配以 re 开头的单词的后半部分(除了 re 以外的部分), 例如在查找reading a book时,它匹配ading

假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分: ((?<=\d)\d{3})+\b ,用它对1234567890进行查找时结果是234567890

下面这个例子同时使用了这两种断言: (?<=\s)\d+(?=\s) 匹配以空白符间隔的数字(再次强调,不包括这些空白符)

###负向零宽断言

前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u, 我们可以尝试这样:

\b\w*q[^u]\w*\b 匹配包含后 面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将 会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w*\b将会匹配下一 个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b

零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。 例如: \d{3}(?!\d) 匹配三位数字,而且这三位数字的后面不能是数字; \b((?!abc)\w)+\b 匹配不包含连续字符串abc的单词。

同理,我们可以用(?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])\d{7} 匹配前面不是小写字母的七位数字

一个更复杂的例子: (?<=<(\w+)>).*(?=<\/\1>)匹 配不包含属性的简单HTML标签内里的内容(<?(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比 如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b></b>之间的内容(再次提醒,不包括前缀和后缀本身)。

贪婪与懒惰

贪婪匹配

在使用修饰匹配次数的特殊符号时,有几种表示方法可以使同一个表达式能够匹配不同的次数,比如:"{m,n}", "{m,}", "?", "*", "+",具体匹配的次数随被匹配的字符串而定。这种重复匹配不定次数的表达式在匹配过程中,总是尽可能多的匹配

比如,针对文本 "dxxxdxxxd",举例如下:

表达式匹配结果
(d)(\w+)"\w+" 将匹配第一个 "d" 之后的所有字符 "xxxdxxxd"
(d)(\w+)(d)"\w+" 将匹配第一个 "d" 和最后一个 "d" 之间的所有字符 "xxxdxxx"。虽然 "\w+" 也能够匹配上最后一个 "d",但是为了使整个表达式匹配成功,"\w+" 可以 "让出" 它本来能够匹配的最后一个 "d"

由此可见,"\w+" 在匹配的时候,总是尽可能多的匹配符合它规则的字符。虽然第二个举例中,它没有匹配最后一个 "d",但那也是为了让整个表达式能够匹配成功。同理,带 "*" 和 "{m,n}" 的表达式都是尽可能地多匹配,带 "?" 的表达式在可匹配可不匹配的时候,也是尽可能的 "要匹配"。这 种匹配原则就叫作 "贪婪" 模式 。

懒惰匹配 在修饰匹配次数的特殊符号后再加上一个 "?" 号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的 "不匹配"。这种匹配原则叫作 "懒惰" 模式,也叫作 "勉强" 模式。如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,懒惰模式会最小限度的再匹配一些,以使整个表达式匹配成功。 举例如下,针对文本 "dxxxdxxxd" 举例:

表达式匹配结果
(d)(\w+?)"\w+?" 将尽可能少的匹配第一个 "d" 之后的字符,结果是:"\w+?" 只匹配了一个 "x"
(d)(\w+?)(d)为了让整个表达式匹配成功,"\w+?" 不得不匹配 "xxx" 才可以让后边的 "d" 匹配,从而使整个表达式匹配成功。因此,结果是:"\w+?" 匹配 "xxx"

懒惰限定符

语法说明
*?重复任意次,但尽可能少重复
+?重复1次或更多次,但尽可能少重复
??重复0次或1次,但尽可能少重复
{n,m}?重复n到m次,但尽可能少重复
{n,}?重复n次以上,但尽可能少重复

JAVA jdk提供的正则类:

Pattern类

Pattern p = Pattern.compile("\\d");
Matcher m = p.matcher("ass923asdf");
boolean b = m.matches();

String类: matches()

String reg = "\\d";
String str = "123";
str.matches(reg);

str.replaceAll(reg,"")
str.split(reg)

##正则示例:

邮编:六位数字

[0-9]{6} 
或 
\d{6}
或
[1-9]\d{5}(?!\d)	 //这一个是用的最多的版本

手机号码:1开头,第二位3 4 5 7 8 9 ,其余任意

1[345789]\d{9}

邮箱:用户名是字母 数字 _ . -组成, @ xxx . xxx

(\w|\.|-)+@\w+\.\w+  [a-zA-Z0-9_.-]+@\w+\.\w+
或
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*

身份证号:18位数字 或者17位数字+xX

[1-9]\d{16}(\d|[xX])

ip地址:0-255.0-255.0-255.0-255

(\d|([1-9]\d)|(1[0-9][0-9])|(2[0-4]\d)|(25[0-5]))\.
或
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

用户名:字母 _ 数字 - 组成,6-18位

(\w|-){6,18}

匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):

^[a-zA-Z][a-zA-Z0-9_]{4,15}$

qq号:10000以上

[1-9][0-9]{4,}

出生日期 XXXX-XX-XX XX-x-X XXXX-X-X XX-XX-XX

1900-2015      01-12   01-31
((19)?\d{2}|(20)?(0\d|1[0-5]))-
(0?[1-9]|1[0-2])-
(0?[1-9]|[12]\d|3[01])

匹配双字节字符(包括汉字在内) 可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

[^\x00-\xff]

匹配空白行(可以用来删除空白行):

\n\s*\r
或
\n[\s| ]*\r

匹配HTML标记(这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力)

<(\S*?)[^>]*>.*?</\1>|<.*? />
或
/<(.*)>.*<\/\1>|<(.*) \/>/ 

匹配首尾空白字符 可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等)

^\s*|\s*$
或
(^\s*)|(\s*$)

匹配网址URL 这个基本可以满足需求

[a-zA-z]+://[^\s]*

匹配国内电话号码: 匹配形式如 0511-4405222 或 021-87888822

\d{3}-\d{8}|\d{4}-\d{7}

匹配特定数字:

^[1-9]\d*$    //匹配正整数
^-[1-9]\d*$   //匹配负整数
^-?[1-9]\d*$   //匹配整数
^[1-9]\d*|0$  //匹配非负整数(正整数 + 0)
^-[1-9]\d*|0$   //匹配非正整数(负整数 + 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$   //匹配正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$  //匹配负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$  //匹配浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$   //匹配非负浮点数(正浮点数 + 0)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$  //匹配非正浮点数(负浮点数 + 0)

匹配特定字符串:

^[A-Za-z]+$  //匹配由26个英文字母组成的字符串
^[A-Z]+$  //匹配由26个英文字母的大写组成的字符串
^[a-z]+$  //匹配由26个英文字母的小写组成的字符串
^[A-Za-z0-9]+$  //匹配由数字和26个英文字母组成的字符串
^\w+$  //匹配由数字、26个英文字母或者下划线组成的字符串

常用正则表达式

说明:正则表达式通常用于两种任务:1.验证,2.搜索/替换。用于验证时,通常需要在前后分别加上^$,以匹配整个待验证字符串;搜索/替换时是否加上此限定则根据搜索的要求而定,此外,也有可能要在前后加上\b而不是^和$。此表所列的常用正则表达式,除个别外均未在前后加上任何限定,请根据需要,自行处理。

说明表达式
网址(URL)[a-zA-z]+://[^\s]*
电子邮件(Email)\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
QQ号码[1-9]\d{4,}
密码(由数字/大写字母/小写字母/标点符号组成,四种都必有,8位以上)(?=^.{8,}$)(?=.*\d)(?=.*\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\n).*$
汉字(字符)[\u4e00-\u9fa5]
中文及全角标点符号(字符)[\u3000-\u301e\ufe10-\ufe19\ufe30-\ufe44\ufe50-\ufe6b\uff01-\uffee]
中国大陆手机号码1\d{10}
中国大陆邮政编码[1-9]\d{5}
中国大陆身份证号(15位或18位)\d{15}(\d\d[0-9xX])?
非负整数(正整数或零)\d+
正整数[0-9]*[1-9][0-9]*
整数-?\d+
小数(-?\d+)(\.\d+)?
不包含abc的单词\b((?!abc)\w)+\b

中国大陆固定电话号码 :

(\d{4}-|\d{3}-)?(\d{8}|\d{7})

IP地址(IP Address):

((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

HTML标记(包含内容或自闭合)

<(.*)(.*)>.*<\/\1>|<(.*) \/>

日期(年-月-日)

(\d{4}|\d{2})-((1[0-2])|(0?[1-9]))-(([12][0-9])|(3[01])|(0?[1-9]))

时间(小时:分钟, 24小时制)

((1|0?)[0-9]|2[0-3]):([0-5][0-9])

匹配代码注释

# 问题:不能区分变量中包含注释的情况,比如String a = " ;sss/*test*/sss"; /*test*/也会被识别
(\/\/.*|\/\*[\S\s]+?\*\/)

单引号或双引号字符串

(["'])(?:\\.|[^\\\n])*?\1

eg: 加黑的是识别的部分 var a = "this id outer'sss'"hhhhh" string"; String a = " ;sss/sssss/sss"; fun("/*clousure*/this.a")

匹配正则表达式

\/(?!\*|span).+\/(?!span)[gim]*

匹配字符串中的表情标签

# 表情标签:中括号包含的[1,5)个汉字

(\[[\u4E00-\u9FA5]{1,5}?\])

# eg:表情测试[weixiao]【哭】[微笑] 
# 	识别结果: [微笑]

Test for C# Code

string text = "表情测试[weixiao]【哭】[微笑]";
Regex r = new Regex(@"(\[[\u4E00-\u9FA5]{1,5}?\])", RegexOptions.IgnoreCase);
Match m = r.Match(text);
int matchCount = 0;
while (m.Success)
{
    Console.WriteLine("Match" + (++matchCount));
    for (int i = 1; i <= 2; i++)
    {
        Group g = m.Groups[i];
        Console.WriteLine("Group" + i + "='" + g + "'");
        CaptureCollection cc = g.Captures;
        for (int j = 0; j < cc.Count; j++)
        {
            Capture c = cc[j];
            Console.WriteLine("Capture" + j + "='" + c + "', Position=" + c.Index);
        }
    }
    m = m.NextMatch();
}