正则表达式篇

一、正则表达式概述

借鉴:https://www.jb51.net/program/306582p1f.htm

正则表达式是一组由字母和符号组成的特殊文本,它可以用来从文本中找出满足你想要的格式的句子。

通俗的讲就是按照某种规则去匹配符合条件的字符串。

一个正则表达式是一种从左到右匹配主体字符串的模式。

“Regular expression”这个词比较拗口,我们常使用缩写的术语“regex”或“regexp”。

正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等

正则表达式在线练习网站:正则表达式在线练习网站

从下面图中可以看出,不同语言的正则表达式执行的效果可能是会有一些不一样的,并且正则表达式不止是可以用来匹配的,也可以替换等等操作,应该还有其他的一些操作。当然哈,主要是匹配。修饰符可以修饰,比如控制正则表达式是不是全局匹配,不是全局匹配,那么就是只匹配第一个的。

image-20240726231741356

例子:

image-20240726232950004

image-20240726233035624

二、基础语法表格

首先是最基础的匹配规则

single char(单字符)quantifiers(数量)position(位置)
\d 匹配数字* 0个或者更多^一行的开头
\w 匹配word(数字、字母)+ 1个或更多,至少1个$一行的结尾
\W 匹配非word(数字、字母)? 0个或1个,一个Optional\b 单词"结界"(word bounds)
\s 匹配white space(包括空格、tab等){min,max}出现次数在一个范围内
\S 匹配非white space(包括空格、tab等){n}匹配出现n次的
. 匹配任何,任何的字符

三、元字符

正则表达式主要依赖于元字符。元字符不代表他们本身的字面意思,他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思

1.元字符举例

元字符描述
.句号匹配任意单个字符除了换行符。
[ ]字符种类。匹配方括号内的任意字符。整个[……]只表示一个字符。
[^ ]否定的字符种类。匹配除了方括号里的任意字符。??这个是一个还是多个???
*匹配>=0个重复的在*号之前的字符。
+匹配>=1个重复的+号前的字符。
?标记?之前的字符为可选。
{n,m}匹配num个大括号之前的字符或字符集 (n <= num <= m)。
(xyz)字符集,匹配与 xyz 完全相等的字符串。
|或运算符,匹配符号前或后的字符。???一个还是多个字符???
\转义字符,用于匹配一些保留的字符 `[ ] ( ) { } . * + ? ^ $
^从开始行开始匹配。??写一个^表示这个东西就是最开始的地方吗????
$从末端开始匹配。??写一个^表示这个东西就是最末尾的地方吗????

2.点运算符–> .

.是元字符中最简单的例子。 .匹配任意单个字符,但不匹配换行符。 例如,表达式 [...o ] 匹配3个(几个点就几位)任意字符后面跟着是 [ o ] 的字符串。一个字符就是精确匹配的。

'...o' =>  //此处给出三个点 就是前三位为任意
努力学习的汪 hongjilin   //其中的 [ 汪 ho ] 高亮
努力学习的汪 Hongjilin   //其中的 [ 汪 Ho ] 高亮

img

看到自己测试的截图借鉴作者的截图一样,所以,下面的内容中,如果自己测试的效果和借鉴复制的截图一样,那么就不自己再抛一张截图放在笔记里面了。

image-20240726224240476

3.字符集–>[]

字符集也叫做字符类。 方括号用来指定一个字符集。 在方括号中使用连字符来指定字符集的范围。 在方括号中的字符集不关心顺序。 例如,表达式 [ 学习的汪 [Hh] ] 匹配 [ 学习的汪 h ] 和 [ 学习的汪 H ] 。

"学习的汪 [Hh]" =>
努力学习的汪 hongjilin   //其中的 [ 学习的汪 h ] 高亮
努力学习的汪 Hongjilin   //其中的 [ 学习的汪 H ] 高亮

img

注意:括号内只匹配其中一个不会匹配多个字符的,相当于一个[……]中,不管其中内容有多少,这个[]只是表示一个字符。

image-20240726224749116

Ⅰ- 字符集中匹配句号 --> [.]

前面我们说过点运算符,那同学们是否会有个疑惑, . 被用来匹配任意字符,那么作为字符串中的句号.,又该用什么匹配呢?

方括号的句号就表示句号。 比如:表达式 lin[.] 匹配 lin.字符串

"lin[.]" =>
努力学习的汪 hongjilins 
努力学习的汪 Hongjilin.

img

注意:[……]整体只表示一个字符,[.]表示.

例子:

image-20240726225412623

Ⅱ - 否定字符集 --> [^]

一般来说 ^ 表示一个字符串的开头,但它用在一个方括号的开头的时候,它表示这个字符集是否定的。 例如,表达式[^地]学习的[^帅] 匹配一个字符串为 [ 学习的 ]的, 同时前面一位字符串不能为,后面一位字符串不能为

"[^地]学习的[^帅]" => 
努力学习的汪 hongjilins  //只有此处高亮
努力学习的帅汪 Hongjilin. 
帅气地学习的

img

a) 一个特殊用法

正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。即这两个不能被点号匹配。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(line terminator character)

所谓行终止符,就是该字符表示一行的终结。以下四个字符属于“行终止符”。

  1. U+000A 换行符(\n)
  2. U+000D 回车符(\r)
  3. U+2028 行分隔符(line separator)
  4. U+2029 段分隔符(paragraph separator)
/foo.bar/.test('foo\nbar') // false

image-20240726225946133

上面代码中,因为.不匹配\n,所以正则表达式返回false

但是,很多时候我们希望匹配的是任意单个字符,这时有一种变通的写法,如下:

/foo[^]bar/.test('foo\nbar') // true

即,写一个[^],其中[^] 是一个特殊的语法,他表示任意单个字符。

当然哈,上面这种解决方案毕竟不太符合直觉,ES2018引入s修饰符,使得.可以匹配任意单个字符。注意:s是一种修饰符,可以让这个正则表达式的点,可以匹配任意的当个字符,普通的点是会排除行终止符和四个字节的 UTF-16 字符的,但是加s就可以匹配了。

科普:ES2018,也称为ECMAScript 2018,是JavaScript语言的一个版本。ECMAScript是JavaScript的官方标准化规范,由ECMA国际(European Computer Manufacturers Association)制定和管理。

ES2018是ECMAScript 2018年的版本,它引入了一些新的语言特性、语法和API,以改进JavaScript的功能和性能。

例子:

/foo.bar/s.test('foo\nbar') // true

img

img

例子:

image-20240726232602338

image-20240726232738162

Ⅲ - 重复次数 --> *、+、?

后面跟着元字符 +* or ? 的,用来指定匹配子模式的次数。 这些元字符在不同的情况下有着不同的意思。

a) *

*号匹配 在*之前的一个字符出现大于等于0次(一个[……]*,表示这个[……]整体可以出现0或者多次,当然哈,这个[……]其实就是表示一个字符,所以就可以理解为*就是表示*前的一个字符可以出现0或者多次。并且这个*是尽量多的匹配的)。 例如,表达式 a* 匹配0或更多个以a开头的字符。表达式[a-z]* 匹配一个行中所有以小写字母开头的字符串。

"[a-z]*" =>
Hong ji lin VERY shuai //部分高亮
HONGJILINHAOSHUAI	  //全部不亮
hongjilinhaoshuai	  //全部高亮

img

更多例子:

image-20240728224308092

image-20240728224716658

感受一下m修饰符:

image-20240728230646281

image-20240728230732144

*字符和 .字符搭配的话,可以匹配所有的字符,如:.*

image-20240728224843448

*和表示匹配空格的符号\s连起来用,如表达式\s*学习\s*匹配0或更多个空格开头和0或更多个空格结尾的cat字符串。

"\s*学习\s*" => 					  //0~无限次,所以只要有[ 学习 ]都会被匹配,同时会被匹配的还有其紧靠的无限次的空格
努力 学习的汪 hongjilins  		    //[ 学习 ]前一个空格,后面无空格
努力      学习       的汪hongjilins    //[ 学习 ]前后多个空格
努力学习的帅汪 Hongjilin. 			   //[ 学习 ] 前后无空格

img

b) +

+号匹配+号之前的字符出现 >=1 次。 例如,表达式学习.+汪 :匹配以学习开头,中间跟着至少一个字符,然后后面再跟着的字符串。

"学习.+汪" => 
努力学习的汪 hongjilins   
努力学习的帅汪 Hongjilin. 
努力学习 66 汪 Hongjilin. 
努力的学习汪  			 //此行无匹配结果

img

c) ?

在正则表达式中元字符 ? 标记在符号前面的一个字符为可选,即出现 0 或 1 次。 例如,表达式 学习的[帅]?汪 匹配字符串 学习的汪学习的帅汪

"学习的[帅]?汪" => 
努力学习的汪 hongjilins
努力学习的帅汪 Hongjilin. 
努力的学习汪 				//无匹配结果
努力学习的帅气汪 Hongjilin. //无匹配结果

img

image-20240728225512398

Ⅳ - 量词 --> {}
a) 正常使用示例

在正则表达式中 {} 是一个量词,常用来限定前面的一个字符可以重复出现的次数。 例如, 表达式 [0-9]{2,3} 匹配最少 2 位最多 3 位 0~9 的数字。

"[0-9]{2,3}" => 
努力学习的11
努力学习的233汪
努力学习的44444汪
努力学习的555555

img

image-20240728230114623

b) 省略第二个参数,带逗号

我们可以省略第二个参数。 例如,[0-9]{2,} 匹配至少两位 0~9 的数字。

"[0-9]{2,}" => 
努力学习的11
努力学习的233汪
努力学习的44444汪
努力学习的555555

img

c) 逗号也省略

如果逗号也省略掉则表示重复固定的次数。 例如,[0-9]{2} 匹配2位数字

"[0-9]{2}" => 
努力学习的11
努力学习的233汪
努力学习的44444汪
努力学习的555555

img

Ⅴ- 特征标群 --> (...)

特征标群是一组写在 (…) 中的子模式。(…) 中包含的内容将会被看成一个整体,和数学中小括号( )的作用相同。例如, 表达式 (ab)* 匹配连续出现 0 或更多个 ab。如果没有使用 (…) ,那么表达式 ab* 将匹配连续出现 0 或更多个 b 。再比如之前说的 {} 是用来表示前面一个字符出现指定次数。但如果在 {} 前加上特征标群 (…) 则表示整个标群内的字符重复 N 次。
我们还可以在 () 中用或字符 | 表示或。例如,(学习|打工)的汪 匹配 学习的汪打工的汪

"(学习|打工)的汪 (hong){2}" => 
努力学习的汪 hongjilins
努力学习打工的汪 hongjilins
努力打工的汪 honghongjilins
努力学习的汪 honghongjilins

img

注意:括号内的或不只对前后的一个字符起作用。他对一个词组起作用。

image-20240729214048202

注意:如果是[]中的或,那么就是,取前后组中一个字符都行。

image-20240729230547157

Ⅵ - 或运算符 --> |

或运算符就表示或,用作判断条件。

举个栗子:(学习|打工)的(汪|打工人) 进行匹配

"(学习|打工)的(汪|打工人)" =>
努力打工的汪 hongjilins
努力学习的打工人 honghongjilins
努力学习打工的打工人汪 hongjilins

img

image-20240729214424814

Ⅶ - 转义特殊字符 --> \

反斜线 \ 在表达式中用于转码紧跟其后的字符。用于指定 { } [ ] / \ + * . $ ^ | ? 这些特殊字符。如果想要匹配这些特殊字符则要在其前面加上反斜线 \。

例如 . 是用来匹配除换行符外的所有字符的。如果想要匹配句子中的 . 则要写成 \.。 以下这个例子中 \.?是选择性匹配,即,0个或者1个都行。

"(学习|打工)的汪\.? hong\??" =>
努力打工的汪. hong
努力学习的汪? hong
努力学习的汪 hong?

img

image-20240729214816555

Ⅷ - 锚点(边界) --> ^、$、\b、\B

在正则表达式中,想要匹配指定开头或结尾的字符串就要使用到锚点。^ 指定开头,$ 指定结尾。

通常也会搭配标志(即,修饰符)的相关知识点一起使用

由于还未说到标志相关知识,此处例子仍使用 [ /g ]全局搜索,如果对此有疑惑的可以留着疑问看下方的 [六、标志](# 六、标志(修饰符))

a) ^

^ 用来检查匹配的字符串是否在所匹配字符串的开头。

例如,在 abc 中使用表达式 ^a 会得到结果 a。但如果使用 ^b 将匹配不到任何结果。因为在字符串 abc 中并不是以 b 开头。

例如,^(学习|打工)的汪 进行匹配

"^(学习|打工)的汪" =>  //注意:下列字符串要分四次匹配,因为即使换行了,后三行字符串本质上都不在字符串开头
//或者标志换成 /m 而不是 /g  因为此处还未说到标志,所以默认大家使用/g全局搜索
学习的汪 hong
打工的汪 hong
努力打工的汪 hong
努力学习的汪

image-20240729215256193

image-20240729215404181

img

b) $

同理于 ^ 号,$ 号用来匹配字符是否是最后一个。

例如,学习的(汪|打工人)$ 匹配以 [ 汪 ] 或者 [ 打工人 ] 结尾的字符串。

"学习的(汪|打工人)$" =>  //注意:下列字符串要分四次匹配,因为即使换行了,前三行字符串本质上都不在字符串结尾
//或者标志换成 /m 而不是 /g  因为此处还未说到标志,所以默认大家使用/g全局搜索
努力学习的汪
努力学习的打工人
努力学习的打工人 hongjilins
努力学习的汪 

image-20240729215723052

image-20240729215814409

img

c) 单词边界 \b

\b : 单词边界([a-zA-z0-9]之外的字符),举个栗子:\bis\b。就是说是:[a-zA-z0-9]之外的字符is[a-zA-z0-9]之外的字符

'\bis\b'=>
My name is hongjilin
my name@is@hong jilin
myname学is习hongjilin
mynameishongjilin //只有此处不被匹配

img

d) 非单词边界 \B
'\Bis\B'=>
My name is hongjilin
my name@is@hong jilin
myname学is习hongjilin
mynameishongjilin //只有此处被匹配,与单词边界切好相反

img

四、简写字符集

这些简写字符集,简洁明了且非常常用,建议背下来

正则表达式提供一些常用的字符集简写。如下:

简写描述
.除换行符外的所有字符
\w匹配所有字母数字,等同于 [a-zA-Z0-9_]
\W匹配所有非字母数字,即符号,等同于: [^\w]
\d匹配数字: [0-9]
\D匹配非数字: [^\d]
\s匹配所有空格字符,等同于: [\t\n\f\r\p{Z}]
\S匹配所有非空格字符: [^\s]
\f匹配一个换页符
\n匹配一个换行符
\r匹配一个回车符
\t匹配一个制表符
\v匹配一个垂直制表符
\p匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符

规律:简写中,大写的意义就是指小写的相反。

五、零宽度断言 (前后预查)

定义:零宽度断言(Zero-width assertions)是正则表达式中的一种特殊语法,用于指定一个位置,而不是实际匹配字符。它们不会消耗任何字符,也不会将匹配的内容作为结果返回,只是用来指定匹配的条件。

在正则表达式中,通常用来断言当前位置的内容是否满足某种条件。常见的零宽度断言有四种:

  1. 正向前瞻断言(Positive lookahead)(?=...)

    • 这种断言表示在当前位置的右侧,后面必须是与 ... 匹配的内容。例如,foo(?=bar) 表示 foo 后面必须跟着 bar 才算匹配成功,但 bar 不会被包括在匹配结果中。

      image-20240729224217945

  2. 负向前瞻断言(Negative lookahead)(?!...)

    • 这种断言表示在当前位置的右侧,后面必须不是与 ... 匹配的内容。例如,foo(?!bar) 表示 foo 后面不能跟着 bar 才算匹配成功。匹配的是foo。

      image-20240729223811058

  3. 正向后顾断言(Positive lookbehind)(?<=...)

    • 这种断言表示在当前位置的左侧,前面必须是与 ... 匹配的内容。例如,(?<=foo)bar 表示 bar 前面必须是 foo 才算匹配成功,但 foo 不会被包括在匹配结果中。

      image-20240729224128466

  4. 负向后顾断言(Negative lookbehind)(?<!...)

    • 这种断言表示在当前位置的左侧,前面必须不是与 ... 匹配的内容。例如,(?<!foo)bar 表示 bar 前面不能是 foo 才算匹配成功。匹配的是bar。

      image-20240729224040972

零宽度断言在实际应用中非常有用,可以用来构建复杂的匹配规则,特别是在需要精确控制匹配位置但又不想包含特定内容时

例如,我们想要获得所有跟在 $ 符号后的数字,但是,我们要匹配的内容不包括 $ 符合。我们可以使用正后发断言 (?<=\$)[0-9\.\?]*来完成这个需求。

解释:

(?<=\$)[0-9\.\?]*这个正则表达式的意思是匹配一个字符串,这个字符串满足以下条件:

  • 匹配的内容位于一个以美元符号 $ 开头的字符串的后面(但不包括 $ 符号本身)。
  • $ 符号后面的内容是零个或多个数字 0-9、小数点 . 或问号 ? 的组合。

具体解释如下:

  • (?<=\$):这是一个正向后行断言,表示匹配的内容前面必须是 $ 符号。它用来限定匹配条件,确保匹配的内容在 $ 符号之后。
  • [0-9\.\?]*:这是一个字符类,匹配其中任意字符。具体含义是匹配零个或多个以下字符:
    • 0-9: 数字 0 到 9。
    • \.: 小数点,需要使用反斜杠转义。
    • \?: 问号,同样需要使用反斜杠转义。

因此,该正则表达式会匹配像 $123.45?$0.99$3.14159$? 这样的字符串,但不会匹配像 123$ 这样的字符串,因为后面的内容不是以 $ 符号开头的。

"(?<=\$)[0-9\.\?]*" =>
$0.,1,2,3,$4,5,6,$?7,8,$..9.9?

img

1、正先行断言 --> ?=...

例子: 学习的汪(?=\shong)

表达式 学习的汪(?=\shong) 在正则表达式中的意思是匹配以下内容:

  • 学习的汪: 这部分直接匹配字符序列 学习的汪
  • (?=\shong): 这是一个正向前瞻断言,表示所匹配的位置的后面必须紧跟着一个空白字符 \s,然后跟着字符串 hong

因此,整个表达式 学习的汪(?=\shong) 的效果是匹配 学习的汪,但仅当它后面紧跟着一个空白字符(例如空格、制表符等),然后跟着字符串 hong 时才算匹配成功。这个空白字符和 hong 不会包含在最终的匹配结果中。

例如,它可以匹配以下内容:

  • 学习的汪 hong

但不能匹配:

  • 学习的汪hong (因为没有空白字符)
  • 学习的汪xhong (因为 x 不是空白字符)

这种断言可以用来确保匹配的字符串满足特定的后续条件,而不必包含这些条件本身在匹配结果中。

"学习的汪(?=\shong)" =>  //此处断言中的可以再加如`+` 、`*` ......,此处举其中一个栗子说明
努力学习的汪 hong //只有此处被匹配到    返回: [学习的汪]  -->断言中的匹配项作为约束不会返回
努力学习的汪 帅
努力学习的汪hong  //此处后面没有空格

image-20240729230823146

注意:这些零宽度断言连空也能匹配的。

image-20240806233341877

但是注意:

image-20240806231146752

因为零宽度断言不会消耗字符串。所以应该下面这样写才会被匹配,(?=\shong)消耗了一个空字符串:

image-20240807012835384

image-20240807013104048

零度断言后面也可以跟点号的。

比如:

image-20240806233654585

上面例子相当于是,匹配消耗完学习的汪后,然后消耗匹配任意的2或3个字符,如果能消耗3个字符就消耗匹配三个字符。

2、负先行断言 --> ?!...

例子: 学习的汪(?!\s+hong)

表达式 学习的汪(?!\s+hong) 在正则表达式中的意思是匹配以下内容:

  • 学习的汪: 这部分直接匹配字符序列 学习的汪
  • (?!\s+hong): 这是一个负向前瞻断言,表示所匹配的位置的后面不能紧跟着一个或多个空白字符 \s,然后跟着字符串 hong

因此,整个表达式 学习的汪(?!\s+hong) 的效果是匹配 学习的汪,但在它后面的文本不是以一个或多个空白字符后跟 hong 的情况下才算匹配成功。

这个断言的作用是排除那些后面紧跟着空白字符和 hong 的情况,确保所匹配的 学习的汪 后面没有符合条件的内容。

"学习的汪(?!\s+hong)" => 
努力学习的汪 hong  //不被匹配
努力学习的汪  hong //不被匹配
努力学习的汪 帅
努力学习的汪hong

image-20240729231038915

注意:\s可以匹配尽可能多的空格或者换行的。

image-20240729231326326

3、正后发断言 --> ?<= ...

例子: (?<=[打工|学习])的(汪|打工人)

表达式 (?<=[打工|学习])的(汪|打工人) 在正则表达式中的意思是匹配以下内容:

  1. (?<=[打工|学习]): 这是一个正向后顾断言,表示所匹配的位置的前面必须是 打工学习 中的任意一个字符序列。但是要注意,[打工|学习] 在正则表达式中的意思是匹配单个字符集合,即匹配 |、或 中的任何一个字符。
  2. : 直接匹配字符
  3. (汪|打工人): 这是一个捕获组,匹配 打工人 中的任意一个词组。

因此,整个表达式 (?<=[打工|学习])的(汪|打工人) 的效果是匹配这样的结构:

  • 打工的汪
  • 学习的打工人
  • 学的打工人
  • 等等

image-20240729225746614

如果它要求前面必须是 打工学习 中的一个词,并且紧跟着 ,然后后面紧跟着 打工人 中的一个词。

那么可以这样:

"(?<=(打工|学习))的(汪|打工人)" => 
努力学习的汪
努力打工的打工人
努力学习的打工人
努力的打工人
学的打工人

image-20240729225911946

4、负后发断言 --> ?<!...

例子: (?<![学习|打工])的(汪|打工人)

表达式 (?<![学习|打工])的(汪|打工人) 在正则表达式中的意思是匹配以下内容:

  • (?<![学习|打工]): 这是一个负向后顾断言,表示所匹配的位置的前面不能是 中的任何一个词。
  • : 直接匹配字符
  • (汪|打工人): 这是一个捕获组,匹配 打工人 中的任意一个词组。

因此,整个表达式 (?<![学习|打工])的(汪|打工人) 的效果是匹配这样的结构:

  • 的汪
  • 的打工人

但要求在 前面的单词不是 学习打工。这种断言确保了在匹配 的汪的打工人 时,前面的文本不是 学习打工 这两个词。

"(?<![学习|打工])的(汪|打工人)" => 
努力学习的汪
努力打工的打工人
努力学习汪
努力的打工人
努力的汪
努力学的汪
努力习的汪
aa的汪

image-20240729231828311

六、标志(修饰符)

标志也叫模式修正符,因为它可以用来修改表达式的搜索结果。 这些标志可以任意的组合使用,它也是整个正则表达式的一部分。

标志描述
i忽略大小写。
g全局搜索。
m多行修饰符:锚点元字符 ^ $ 工作范围在每行的起始。

1、全局搜索 (Global search) --> \g

修饰符 g 常用于执行一个全局搜索匹配,即(不仅仅返回第一个匹配的,而是返回全部)。 例如,表达式 /学习的汪/g 表示搜索 任意字符+ 学习的汪,并返回全部结果。

"/学习的汪/g" => 
努力学习的汪
努力学习的汪
非常努力学习的汪

不全局搜索

img

全局搜索

img

image-20240731204758112

2、忽略大小写 (Case Insensitive) --> /i

修饰语 i 用于忽略大小写。 例如,表达式 /Hong/gi 表示在全局搜索 Hong,在后面的 i 将其条件修改为忽略大小写,所以效果是搜索 [hong(忽略大小写)],g 表示全局搜索。

"/Hong/gi" =>  //默认情况下是大小写敏感的,但此处这样标志后,就成为忽略大小写
hongjilin
Hongjilin
HONGJILIN
hOngjilin

img

3、多行注释符

多行修饰符 m 常用于执行一个多行匹配。

像之前介绍的 (^,$) 用于检查格式是否是在待检测字符串的开头或结尾。但我们如果想要它在每行的开头和结尾生效,我们需要用到多行修饰符 m。

例如,表达式 /学习的(汪|打工人)$/gm 表示匹配以 “学习的汪” 或者 “学习的打工人” 结尾的字符串。根据 m 修饰符,现在表达式可以匹配每行的结尾。/gm:正则表达式的结束标志,其中 g 表示全局匹配(即找到所有匹配的部分),m 表示多行模式(使得 ^$ 匹配每一行的开头和结尾)。

"/学习的(汪|打工人)$/gm" =>  //在之前说到 锚点 时提到,如果是 /g 只能一行一行匹配,而如果换成这个就可以直接匹配
努力学习的汪
努力学习的打工人
努力学习的打工人 hongjilins
努力学习的汪 

不开多行注释符:

img

开多行注释符:

img

七 、贪婪匹配与惰性匹配 (Greedy vs lazy matching)

1、贪婪匹配

正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。

注意不开启全局搜索

"/(.*汪)/" => 
努力学习的汪 非常认真读书的汪  的汪  的汪

img

2、惰性匹配

我们可以使用 ? 将贪婪匹配模式转化为惰性匹配模式。

注意不开启全局搜索

"/(.*?汪)/" => 
努力学习的汪 非常认真读书的汪  的汪  的汪

img

补充

正则表达式中的贪婪匹配和惰性匹配是描述正则表达式引擎在匹配过程中的两种不同行为方式:

  1. 贪婪匹配(Greedy matching)
    • 默认情况下,正则表达式的量词(如 *+?{n,m} 等)是贪婪的,它们会尽可能匹配更多的字符。
    • 例如,对于表达式 a+,它会匹配一个或多个连续的 a,直到无法再匹配更多的 a 为止。
    • 又如,表达式 .* 会匹配任意数量的任意字符,直到遇到无法匹配的字符或者达到了输入的结尾。
  2. 惰性匹配(Lazy matching,也称作非贪婪或最小匹配)
    • 在量词后加上 ? 可以将其变为惰性匹配。例如,*?+???{n,m}? 等。
    • 惰性匹配会尽可能少地匹配字符,只满足匹配的最小条件。
    • 例如,对于表达式 a+?,它会尽可能少地匹配 a,即只匹配一个 a 就满足条件,而不是尽可能多地匹配。
    • 类似地,表达式 .*? 会匹配尽可能少的任意字符,直到遇到无法匹配的字符或者达到了输入的结尾。

在实际使用中,贪婪匹配是默认的行为,因为它效率更高,通常能够更快地找到整体匹配。而惰性匹配通常用于需要精确控制匹配范围的场合,尤其是在处理复杂或者嵌套结构的文本时,可以避免匹配过多的字符。

总结起来,正则表达式的贪婪匹配和惰性匹配主要是描述量词如何匹配文本的方式,选择合适的方式取决于具体的匹配需求和效率考量。

八、关于数字的常见正则表达式

数字:^[0-9]*$

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

上面的n和m指的是任意一个数字哈。比如,上面的第二个^\d{n}$

image-20240731210154576

九、常用正则表达式

1、检验密码强度

如果密码的强度必须是包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间:

^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$

image-20240731212949408

详细解释:

  1. ^: 匹配字符串的开始。
  2. (?=.*\d): 此部分是一个正向预测先行断言,表示后面必须包含至少一个数字 \d
  3. (?=.*[a-z]): 此部分是另一个正向预测先行断言,表示后面必须包含至少一个小写字母 [a-z]
  4. (?=.*[A-Z]): 这是第三个正向预测先行断言,表示后面必须包含至少一个大写字母 [A-Z]
  5. .{8,10}: 匹配任意字符(除换行符外),长度为 8 到 10 个字符。
  6. $: 匹配字符串的结尾。

综合起来,这个正则表达式的作用是匹配符合以下条件的字符串:

  • 必须包含至少一个数字、一个小写字母和一个大写字母;
  • 字符串的总长度必须在 8 到 10 个字符之间。

这个正则表达式通常用于验证密码复杂度的要求,确保密码强度足够,包含数字、大小写字母,并且长度在一定范围内

解释一下(?=.*\d)

(?= ... ):这是正向前瞻的语法。它的意思是,要求在当前位置之后的字符序列必须匹配括号内的模式,但这些字符本身不包括在最终匹配的结果中。

.*:这是一个贪婪的量词,表示任意数量的任意字符(除了换行符)。在前瞻中,.* 表示可以匹配任意数量的字符,或者不匹配任何字符。

\d:这是一个预定义字符类,匹配任何一个数字字符,相当于 [0-9]\d表示的是一个0~9的数字,所以说明至少有一个是数字的。

(?=.*\d):表示从当前位置开始,后面必须有任意数量的任意字符,之后再跟一个数字字符。这意味着目标字符串中必须包含至少一个数字字符,但这个数字字符本身不被包括在最终的匹配结果中。

所以,因为在对应的字符前可以有任意个数的其他字符,所以上面 ^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$ 可以匹配任意顺序的一个字符串(没有要求先数字,再小写,再大写字符),其中字符串必须包含至少一个数字、一个小写字母和一个大写字母;并且字符串总长度必须在 8 到 10 个字符之间。

我的一点理解:^表示开头。一开始(?=.*\d)匹配,看到被匹配的字符串中,只要存在“任意数量的任意字符+数字”就匹配成功了,然后去消耗(?=.*\d)之前的字符,但是这个之前没有写字符,所以没有消耗,或者你可以理解为消耗了一个空字符串。然后继续,匹配(?=.*[a-z]),即在没有被消耗的字符串中进行匹配,因为上一个消耗了空,所以等于没有消耗,这里(?=.*[a-z])开始匹配,也是同理,只要在剩余没有被消耗的字符串中存在“任意数量的任意字符+小写字符”就匹配成功了,也是同理没有消耗,零宽度断言是不会消耗字符的,第三个也是同理,只要在剩余没有被消耗的字符串中存在“任意数量的任意字符+大写字符”就匹配成功了,也是消耗空。然后就是看.{8,10}这个是匹配任意字符串,最少8位,最大10位,能多尽量多匹配,这个也是会消耗字符的,最后,就是看这个.{8,10}消耗后的字符串是否到结尾了,如果到结尾了,那么这个字符就符合$符号,就符合上面这个正则表达式。

2、校验身份证号码

在中国,身份证号码有两种格式:15位和18位。较新的身份证号码是18位,前17位是数字,第18位可以是数字或字母X。

^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$

详细解释:

  1. ^[1-9]:匹配以1-9开头的地址码的第一位(避免开头为0)。
  2. \d{5}:匹配接下来的五位数字,地址码的后五位。
  3. [1-9]\d{3}:匹配出生年份,年份的第一位不能为0。
  4. ((0\d)|(1[0-2])):匹配出生月份,范围是01到12。
    • 0\d:匹配01到09月。
    • 1[0-2]:匹配10到12月。
  5. (([0|1|2]\d)|3[0-1]):匹配出生日期,范围是01到31日。
    • [0|1|2]\d:匹配01到29日。
    • 3[0-1]:匹配30和31日。
  6. \d{3}:匹配顺序码(3位数字)。
  7. ([0-9]|X)$:匹配校验码,可以是0-9的数字或大写字母X。

image-20240731214448699

十、不同语言对正则表达式的实现结果还是有一些不一样的

image-20240806233840844

例子:

image-20240806235503538

因为大部分内容各语言的规则都是一样的,所以不讨论那些少数的不同了。

十一、java的使用例子

package com.example.springbootdemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SpringBootTest
public class RegexExample {
    // 查找
    @Test
    public void test() {
        String text = "Hello, world! How are you today?Hello World2222Loaaa";
        String pattern = "lo";

        Pattern compiledPattern = Pattern.compile(pattern);
        Matcher matcher = compiledPattern.matcher(text);

        while (matcher.find()) {
            System.out.println("找到了: " + matcher.group()+" 索引是在: " + matcher.start());
            /**
             输出:
             找到了: lo 索引是在: 3
             找到了: lo 索引是在: 35
            **/
        }
    }
    // 使用修饰符
    @Test
    public void test2() {
        String text = "Hello, world! How are you today?Hello World2222";
        String patternString = "(?i)hello"; // 匹配 "hello" 忽略大小写

        // 创建 Pattern 对象,并指定 CASE_INSENSITIVE 修饰符
        Pattern pattern = Pattern.compile(patternString);

        // 创建 Matcher 对象
        Matcher matcher = pattern.matcher(text);

        // 查找匹配
        while (matcher.find()) {
            System.out.println("找到了: " + matcher.group()+" 索引是在: " + matcher.start());
            /**
             输出:
             找到了: Hello 索引是在: 0
             找到了: Hello 索引是在: 32
             **/
        }
    }

    // 使用多行匹配修饰符
    @Test
    public void test3() {
        String text = "Line 1\nLine 2\nLine 3";
        String patternString = "^Line"; // 匹配以 "Line" 开始的行

        // 创建 Pattern 对象,并指定 MULTILINE 修饰符
        Pattern pattern = Pattern.compile(patternString, Pattern.MULTILINE);

        // 创建 Matcher 对象
        Matcher matcher = pattern.matcher(text);

        // 查找匹配
        while (matcher.find()) {
            System.out.println("找到了: " + matcher.group());
            /**
             输出:
             找到了: Line
             找到了: Line
             找到了: Line
             **/
        }
    }
    // 不使用多行匹配修饰符
    @Test
    public void test4() {
        String text = "Line 1\nLine 2\nLine 3";
        String patternString = "^Line"; // 匹配以 "Line" 开头的每行

        // 创建 Pattern 对象
        Pattern pattern = Pattern.compile(patternString);

        // 创建 Matcher 对象
        Matcher matcher = pattern.matcher(text);

        // 查找匹配
        while (matcher.find()) {
            System.out.println("找到了: " + matcher.group());
            /**
             输出:
             找到了: Line
             **/
        }
    }
    // 替换
    @Test
    public void test5() {
        String text = "Hello, world! How are you today?Hello2222";
        String pattern = "Hello";
        String replacement = "Hi";

        String newText = text.replaceAll(pattern, replacement);
        System.out.println(newText); // 输出: Hi, world! How are you today?Hi2222
    }

    // 特殊符号的使用
    @Test
    public void test6() {
        String text = "abc123xyz456def78911zzz111423fas";
        Pattern pattern = Pattern.compile("z\\d+"); // 匹配连续的数字序列
        Matcher matcher = pattern.matcher(text);

        while (matcher.find()) {
            System.out.println("找到了: " + matcher.group());
            /**
             输出:
             找到了: z456
             找到了: z111423
             **/
        }
    }
}

结果:

image-20240807000037669

image-20240807000121912

image-20240807000145846

image-20240807000209321

image-20240807000717195

image-20240807000808409

对于上面的替换操作,如果是要替换全部或者替换第一个匹配的字符串,那么使用正则表达式很简单,但是如果要替换第n个匹配的字符串,那么会麻烦点。例子:

package com.example.springbootdemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SpringBootTest
public class RegexExample {
    // 替换第一个
    @Test
    public void test5() {
        String text = "Hello, world! How are you today?Hello2222";
        String pattern = "Hello";
        String replacement = "Hi";

        // 只替换第一个匹配项
        String newText = text.replaceFirst(pattern, replacement);
        System.out.println(newText); // 输出: Hi, world! How are you today?Hello2222
    }
    // 替换第二个
    @Test
    public void testReplaceSecond() {
        String text = "Hello, world! How are you today?Hello2222";
        String pattern = "Hello";

        // 找到第二个匹配项的位置
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(text);
        int count = 0;
        int position = 0;
        while (m.find()) {
            count++;
            if (count == 2) {
                position = m.start();
                break;
            }
        }

        // 如果找到第二个匹配项,则进行替换
        if (count >= 2) {
            String replacement = "Hi";
            String newText = text.substring(0, position) + replacement + text.substring(position + pattern.length());
            System.out.println(newText); // 输出: Hello, world! How are you today?Hi2222
        } else {
            System.out.println("No second match found.");
        }
    }
}

结果:

image-20240807004517125

image-20240807004549676

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值