正则表达式入门

226 阅读13分钟

正则表达式入门

1、前言

正则表达式,对大家来说既熟悉又陌生。熟悉是因为工作中有很多场景能用到,比如手机号、邮箱、密码等规则校验。

陌生则是因为正则表达式看上去就是一堆乱码,且一眼看上去很难看懂匹配规则。有时候在网上去找一个特定规则的正则表达式,搜出来的结果各不相同,执行效果更是不尽人意,想自己去修改,感觉也无从下手。

今天就和大家一起来学习正则表达式

2、几个常用规则

1、 完全匹配

正则表达式是用来匹配文本的,现在假设我们要匹配 "abcdefg" 这一字符串中的 "abc" 三个字母,我们该怎么写呢?

abc

对,不要怀疑,就是这么简单。

如果想匹配 "def",那么就是

def

2、 符号 |

那我们再继续往后看。

还是 "abcdefg" 字符串,我们想匹配其中的 "ab" 和 "ef" 两个子串,怎么写呢?按照上面的写法,就是写两个正则 "ab" 和 "ef" 来匹配。这样可以,不过略显麻烦。我们写法如下:

ab|ef

这里我们新加了 "|" 这个字符,表示 或,即匹配 ab 或者 ef,是不是就方便了许多。

我们在 "acabcdefgh acbbcdefgh accbcdefgh" 字符串中,要匹配 "abcdef","bbcdef","cbcdef" 三个子串。

这题非常简单,用上面的方法,正则表达式如下:

abcdef|bbcdef|cbcdef

写法正确,不过略显繁琐。我们的写法如下:

(a|b|c)bcdef

3、 符号 []

上面的例子,我们如果不用 "|",可以实现么?

答案是可以的,写法如下:

ac[abc]bcdef

我们这里引入了一个新的字符 "[]"

这对中括号是字符集的意思,里面包含的字符,都是或("|")的关系。

我们还可以这样写:

ac[a-c]bcdef

"[a-c]" 表示的是匹配 a-c 之间的任意字符。

[] 除了可以放字符集合以及字符范围以外,还可以做取反操作。

比如 "[^a-c]" 表示的是匹配 a-c 范围以外其他的字符。

"[^0-8]" 表示的是匹配 0-8 范围以外的其他字符。

到了这里,我们已经可以写一些简单的匹配规则了。

4、 限制符号 * + ? {}

我们现在想匹配 类似 "fooooooooooooooooof" 这样子的字符串,中间的 "o" 个数我们不能确定,上面讲到的规则就有点无能为力了。

那我们该怎么写呢?

fo*f

这里的 "*" 表示匹配前面的子表达式零次或多次。

如果想匹配至少有一个 "o" 该怎么写呢?

fo+f

这里的 "+" 表示匹配前面的子表达式一次或多次。

如果想匹配 0 个或者者 1 个 该怎么写呢?

fo?f

这里的 "?" 表示匹配前面的子表达式零次或一次。

如果想匹配确定次数的 "o" 该怎么写呢?

fo{2}f

上面的规则,会匹配 foof,即匹配 2 个 "o"。

所以,"{n}" 表示匹配前面子表达式 n 次。

那如果想匹配 最少 n 个,最多 m 个呢?

"{n,m}" 表示匹配前面子表达式至少 n 次,至多 m 次。

所以 "*" 等价于 "{0,}","+" 等价于 "{1,}","?" 等价于 "{0,1}"。

5、 几个转义字符

在正则表达式里,也有一些转义字符,表示一些特殊的含义。

"\d" 表示匹配数字字符,等价于 "[0-9]"。

"\D" 表示匹配非数字字符,等价于 "[^0-9]"。

"\n" 表示匹配换行符。

"\s" 表示匹配任何空白字符,等价于 "[\f\n\r\t\v]"。

"\S" 表示匹配任何非空白字符,等价于 "[^\f\n\r\t\v]"。

6、 符号 .

符号 "." 会匹配 "\n" 以外的所有字符,也是比较常用的。

7、 符号 ?

不过使用正则表达式时,限制字符 "* + ? {}" 默认的匹配规则是贪婪的。什么意思呢?

比如说有一个字符串 "abbbbb",我们使用正则表达式 "ab+" 去匹配,匹配的结果是 "abbbbbbb",会尽可能多的去匹配满足的字符。

我们如果想尽可能少的匹配满足的字符,那么就在限定符后面加 "?"。

"ab+?" 匹配的结果就是 ab。

"?" 符号的非贪婪匹配,在某些情况下也是很有用的。

8、 符号 ()

最后我们再来看一个符号 "()",小括号的含义是分组,可以理解为被小括号包含的子表达式就是一个分组,在前面介绍 "|" 的时候我们使用过。

不过分组也分很多类型。

"(pattern)" 小括号单纯的包含了子表达式,表示此分组可以被捕获。捕获的意思就是此分组的内容会被保存下来,后续可以从匹配的集合中获取到。

"(?:pattern)" 小括号中增加了 ?: 前缀,表示此分组不可以被捕获。后续匹配的集合中就不会包含此分组信息。

"(?=pattern)" 小括号中增加了 ?= 前缀,表示的是匹配此分组,但是此分组里的内容不需要被获取。

"(?!pattern)" 小括号中增加了 ?! 前缀,表示的是匹配此分组之外的内容,但是此分组里的内容不需要被获取。

举个栗子:

正则表达式 "Windows(?=95|98|NT|2000)" 可以匹配字符串 "Windows2000" 中的 "Windows",但是不能匹配 "Windows3.1" 中的 Windowds。

正则表达式 "Windows(?!95|98|NT|2000)" 可以匹配字符串 "Windows3.1" 中的 "Windows",但是不能匹配 "Windows2000" 中的 Windowds。

以上就是正则表达式里一些常见的规则解析。

3、达式中的优先级

普通拼接abc
括号(abc)
量词a*b
多选结构a|b

正则表达式的优先级

优先级组合说明
1(regex)整个括号内的字表达式称为单个元素
2* ? +限定之前紧邻的元素
3abc普通拼接,元素相继出现
4abc多选结构

注:数字越小,优先级越高。

4、分组

1、概念

正则表达式可以通过”()”来进行分组,更专业的表达就是捕获组,每个完整的”()”可以分为一组,同时,”()”中还可以嵌套”()”,即组之间还可以存在更小的组,以此类推。而编号为0的组,则是正则表达式匹配到的整体,这个规则只要支持正则表达式中捕获组的语言基本上都适用。

2、分组编号

普通捕获组编号规则

如果没有显式为捕获组命名,即没有使用命名捕获组,那么需要按数字顺序来访问所有捕获组。在只有普通捕获组的情况下,捕获组的编号是按照“(”出现的顺序,从左到右,从1开始进行编号的 。

正则表达式分组举例:(\d{4})-(\d{2}-(\d\d))

image.png

3、分组捕获

分类代码/语法说明
捕获(exp)匹配exp,并捕获文本到自动命名的组里
(?<name>exp)匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
(?:exp)匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言(?=exp)匹配exp前面的位置
(?<=exp)匹配exp后面的位置
(?!exp)匹配后面跟的不是exp的位置
(?<!exp)匹配前面不是exp的位置
注释(?#comment)这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

4、后向引用

当我们使用小括号指定一个子表达式之后,就要对这个子表达式的文本进行匹配,即此分组捕获的内容,可以在表达式或其它程序中作进一步的处理。一般情况下,每个分组都会自动拥有一个组号,它的规则是:从左到右以分组的左括号作为标志,把第一次出现的分组的组号定为1,第二个即2,以此类推下去。

后向引用用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本。我们根据示例来深刻理解:

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

5、匹配示例分析

1、匹配电子邮箱

"([a-z0-9_.-]+)@([\da-z.-]+).([a-z.]{2,6})"

也是分段来看。

第一段 "([a-z0-9_.-]+)",表示匹配 a-z 范围内的字母,0-9 范围的数字,以及 "_",".","-" 三个字符,"+" 表示至少有一个字符。

第二段 "@" 表示匹配 "@" 字符。

第三段 "([\da-z.-]+)","\d" 表示匹配任意数字,a-z 范围的字母,".","-" 两个字符,"+" 至少有一个字符。

第四段 "." 表示匹配 "." 字符。

第五段 "([a-z.]{2,6})",表示匹配 a-z 范围的字母,"." 字符,"{2,6}" 表示至少 2 个字符,至多 6 个字符。

6、代码中使用

1、使用java原生api

在 Java 中,关于正则表达式的主要有两个类,PatternMatcher

import java.util.regex.Matcher;

import java.util.regex.Pattern;



public class ReTest {     

    /**

     * 如果pattern是固定的,尽量写成静态的,编译一次,\

       不要每次调用都重新编译

 */

    static Pattern r = Pattern.compile( "boy ([ 0-9 ]) " ) ;

    

    public static void main(String[] args) {

        String str = "grilboy1girlboy2boy3girl";

        // 创建 matcher 对象

        Matcher m = r.matcher(str);

        while (m.find()) {

            // 第0个分组代表匹配的整体

            System.out.println("match boy: " + m.group(0));

            System.out.println("match boy number: " + m.group(1));

        }

    }

}

java代码里写的正则表达式,如果pattern是固定的,尽量写成静态的,编译一次,不要每次调用都重新编译

上面代码运行的结果如下:

match boy: boy1

match boy number: 1

match boy: boy2

match boy number: 2

match boy: boy3

match boynumber: 3

2、hutool 工具包中的使用

String str = "grilboy1girlboy2boy3girl";

String pattern = "boy([0-9])";



List < String > group0 = ReUtil.findAll( pattern, str, 0 ) ;

List < String > group1 = ReUtil.findAll( pattern, str, 1 ) ;

System.out.println ( group0 ) ;

System.out.println ( group1 ) ;

输出如下:

[boy1, boy2, boy3]

[1, 2, 3]

7、应用小案例

1、字符串提取

1.1 提取fileId

https://download.dameng.com/eco-file-server/file/eco/preview/20210714101131T7ADFZE3BGPQL0K4QB 

这个字符串中 fileId 为 20210714101131T7ADFZE3BGPQL0K4QB

 String str = "https://download.dameng.com/eco-file-
 server/file/eco/preview/20210714101131T7ADFZE3BGPQL0K4QB";
 
 // 添加?是保证如果是http请求,同样可以获取
 
 // (.*)这所以没有改为(.*?)是如果不是贪婪匹配,则只匹配
 
 // https://download.dameng.com/eco-file-server/file/eco/preview/
 String reg = "https?://download.dameng.com/eco-file-server/file/eco/preview/(.*)";

 System.*out*.println *(* ReUtil.*getGroup1(* reg, str *))* ;

输出如下:

1.2 提取id值

输入字符串 end.jpg

提取 20220210140807XWG90B 值

String str = "<download id=20220210140807XWG90B>end.jpg</download>";

String reg = "<download id=(.*)>(.*)</download>";

// 分组1获取的是 20220210140807XWG90B

System.out.println ( ReUtil.getGroup1( reg, str )) ;

// 分组2 获取的是 end.jpg

System.out.println ( ReUtil.get( reg, str,2 )) ;

输出如下:

2、字符串替换

2.1 原字符串是:中文1234,把1234换成(1234)

String str = "中文1234";



// $1 代表第一个分组匹配上的内容

String s = ReUtil.replaceAll( str, "(\d+)", "($1)" ) ;



System.out.println ( s ) ;

输出如下:

3、notepad++ 中应用

3.1 给所有的字符串添加双引号

原字符串如下:

替换后如下:

3.2 多行合并为一行

原字符串如下:

替换后如下:

3.3 给一行的字符添加单引号

原字符 123,566,789

目标字符 '123','566','789'

7、通用校验总结

1、校验数字的表达式

1 数字:^[0-9]*$

2 n位的数字:^\d{n}$

3 至少n位的数字:^\d{n,}$

4 m-n位的数字:^\d{m,n}$

5 零和非零开头的数字:^(0|[1-9][0-9]*)$

6 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$

7 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$

8 正数、负数、和小数:^(-|+)?\d+(.\d+)?$

9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$

10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$

11 非零的正整数:^[1-9]\d*([19][09])1,3 或 ^([1-9][0-9]*){1,3} 或 ^+?[1-9][0-9]*$

12 非零的负整数:^-[1-9][]0-9"*[19]\d 或 ^-[1-9]\d*

13 非负整数:^\d+[19]\d0 或 ^[1-9]\d*|0

14 非正整数:^-[1-9]\d*|0((\d+)(0+)) 或 ^((-\d+)|(0+))

15 非负浮点数:^\d+(.\d+)?[19]\d\d˙0\d˙[19]\d0?0˙+0 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0

16 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))(([19]\d\d˙0\d˙[19]\d))0?0˙+0 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0

17 正浮点数:^[1-9]\d*.\d*|0.\d*[1-9]\d*(([09]+[˙09][19][09])([09][19][09][˙09]+)([09][19][09])) 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))

18 负浮点数:^-([1-9]\d*.\d*|0.\d*[1-9]\d*)((([09]+[˙09][19][09])([09][19][09][˙09]+)([09][19][09]))) 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))

19 浮点数:^(-?\d+)(.\d+)??([19]\d\d˙0\d˙[19]\d0?0˙+0) 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)

2、校验字符的表达式

1 汉字:^[\u4e00-\u9fa5]{0,}$

2 英文和数字:^[A-Za-z0-9]+[AZaz09]4,40 或 ^[A-Za-z0-9]{4,40}

3 长度为3-20的所有字符:^.{3,20}$

4 由26个英文字母组成的字符串:^[A-Za-z]+$

5 由26个大写英文字母组成的字符串:^[A-Z]+$

6 由26个小写英文字母组成的字符串:^[a-z]+$

7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$

8 由数字、26个英文字母或者下划线组成的字符串:^\w+\w3,20 或 ^\w{3,20}

9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$

10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+[4˘E009˘FA5AZaz09]2,20 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}

11 可以输入含有^%&',;=?\"等字符:[^%&',;=?\x22]+

12 禁止输入含有的字符:[^\x22]+

3、特殊需求表达式

1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$

2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?

3 InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$

4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$

5 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$

6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}

7 身份证号(15位、18位数字):^\d{15}|\d{18}$

8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?\d8,18[09x]8,18[09X]8,18? 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?

9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$

11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,10}$

12 日期格式:^\d{4}-\d{1,2}-\d{1,2}

13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$

14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$

15 钱的输入格式:

16 1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$

17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$

18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$

19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$

20 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$

21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$

22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$

23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$

24 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里

25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$

26 中文字符的正则表达式:[\u4e00-\u9fa5]

27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))

28 空白行的正则表达式:\n\s*\r (可以用来删除空白行)

29 HTML标记的正则表达式:<(\S*?)[^>]>.?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)

30 首尾空白字符的正则表达式:^\s*|\s*(\s)(\s或(^\s*)|(\s*) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)

31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)

32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)

33 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)

34 IP地址:((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))