如果您对正则表达式不了解,请阅读此内容

学习作为程序员最难的事情之一的基础知识

正则表达式是对文本进行模式匹配的一种方法。 它们经常被忽略,大多数程序员认为它们是很难学习的。

尽管正则表达式非常强大,但很多程序员对正则表达式的了解并不多。

切入正则表达式可能会非常困难,因为它肯定具有学习曲线。 尽管一旦您对正则表达式有了更多的了解,您就会发现它们可以完成很多工作。

最明显的用例之一是在大型代码库中搜索某些文本。 如果您是网络开发人员,则可能在职业生涯中的某个时候使用了正则表达式来验证用户数据。 而且正则表达式甚至可以用来禁止提交某些字符串。

如您所见,正则表达式有很多用例。 正则表达式的用例用途广泛,您不能简单地忽略它们。

这就是为什么对正则表达式更加熟悉是一件好事的原因。 为了成为一名更好的程序员,至关重要的是,您必须对正则表达式有更多的了解,并且至少要了解基础知识。

在本文中,我们将介绍正则表达式的基础知识。 与该理论一起,将使用一些示例阐明正则表达式的基础知识,这些示例阐明了正则表达式的工作方式。

> Photo by Štefan Štefančík on Unsplash

正则表达式的最基本示例是当您尝试在文本中查找某个字符串时。 在此示例中,我们将使用字母。

为了在文本中搜索字符串hij,我们使用以下正则表达式:/ hij /。 请注意,该字符串被正斜杠包围。 这些指示正则表达式的开始和结束。

正如您在下图中看到的,我们有一个匹配项。 很高兴知道此搜索区分大小写,并且仅返回第一个匹配项。

> The result of our first regex: /hij/

但是大多数时候,您想要做的事情比上一个示例中的要复杂一些。 您可能想做的一件事就是寻找以某些字符开头的字符串。

因此,让我们这样做。 让我们制作一个与字符串开头的abc匹配的正则表达式。 为此,您必须在正则表达式前加上^前缀-这将导致正则表达式/ ^ abc /。

> The result of the /^abc/ regex

如果我们想在字符串的末尾查找与某些字符匹配的字符串,我们可以做同样的事情,但是我们必须使用$而不是^并加后缀。 结果为regex / xyz $ /。

> The result of the /xyz$/ regex

也可以将这两个锚点结合使用,从而实现完全匹配的字符串。

bar 匹配包含 bar 的字符串

^bar 匹配以 bar 开始的字符串

bar$ 匹配以 bar 为结束的字符串

^foo bar$ 匹配以 foo 开始 bar 结束的字符串

有一些特殊字符在正则表达式中具有特殊含义。 这些字符需要转义以对它们进行文字搜索。

以下是所有特殊字符的列表:

[ \\ ^ $ . | ? * + ( )

假设我们要搜索问号。 在正则表达式中,问号具有特殊含义。 因此,为了对问号进行文字搜索,我们需要对其进行转义。

您可以通过在字符前面加上反斜杠\\来对字符进行转义。 这使您可以将特殊字符用作常规字符。

> The result of the /?/ regex

为了对特殊字符进行文字搜索,请使用[ ^ $。 |? * +(),您需要使用反斜杠将其转义。

现在,您对如何构造正则表达式有了更好的了解,现在该介绍正则表达式的另一个基本部分,即标志。

如您现在所知,正则表达式通常以一种形式出现,该形式中的搜索模式由两个正斜杠字符分隔。 要指定一个标志,可以在最后一个斜杠字符之后添加它。 也可以合并标志。

尽管有很多不同的标志,但我们将介绍最常用的三个标志,它们是全局标志,不区分大小写和多行标志。

全局标志将接受所有比赛,而不是在第一场比赛后返回。

> The result of the /e/g regex

> The result of the /e/i regex

如前所述,我们还可以组合以下标志:

> The result of the /e/gi regex

如果您想搜索多行文字,则必须使用多行标志。

> The result of the /are/m regex

字符类是一种特殊的符号,它与特定集中的任何符号匹配。 假设我们有一个电话号码+(903)123-4567,我们只想将其变成数字。

为此,我们必须查找并删除所有非数字的内容。 这就是角色类可以提供帮助的东西。 例如, d字符类与任何数字匹配。

这对于我们的用例来说是完美的:

> The result of the /g regex

d字符类与一个数字匹配,该数字是从0到9的字符。但是 d不是唯一的字符类。

w字符类与\”单词\”字符匹配,该字符可以是拉丁字母,数字或下划线。 非拉丁字母不属于 w字符类别。

s字符类与空格符号匹配。 这包括空格,制表符和换行符。

尽管角色类别更多,但我们最常用的类别是。

可以得到字符类的逆矩阵。 假设我们需要一个非数字-基本上可以归结为 d以外的任何字符。 为了做到这一点,我们可以使用逆函数。

每个字符类都有一个反向,该反向用相同的字母表示,但大写。 d的倒数是 D。

本文中要涉及的最后一个主题是量词。 正则表达式量词指定前面的正则表达式应匹配的频率。

我们将介绍一些不同的量词。

我们要讨论的第一个量词是? 表示零或一。 在下面的示例中,我们使用/ ba?/ g正则表达式。 此正则表达式将检查是否有任何b字符,后跟零或一个a字符。

> The result of the /ba?/g regex

第二个量词是,表示零或更大。

> The result of the /ba/g regex

还有一个或多个用于+的量词。

> The result of the /ba+/g regex

您还可以处理必须在量词中匹配的特定数量的事件。 / ba {2} / g regex匹配任何b字符,后跟两个连续的a字符。

> The result of the /ba{2}/g regex

如果要限制连续字符的数量,可以使用第二个数字,以逗号分隔。 / ba {2,4} /正则表达式匹配任何b字符,后跟2到4个连续的a字符。

> The result of the /ba{2,4}/g regex

我们还可以对至少跟着两个a字符的任何b字符执行此操作。 为此,您必须在出现次数的后面加上一个逗号-结果为/ ba {2,} / g。

> The result of the /ba{2,}/g regex

现在,我们已经了解了正则表达式的基础知识,该是您自己进行一些练习的时候了。 当然,关于正则表达式还有很多东西要学习。 由于我们已经讲过基础知识,因此我们没有涉及更高级的主题。

不过,明智的做法是先对基础知识有一个很好的了解。

编码愉快!

(本文翻译自Daan的文章《Read This If You Don\’t Know Enough About Regex》,参考:https://levelup.gitconnected.com/read-this-if-you-dont-know-enough-about-regex-73141bb0e1a7)

正则表达式(java 版)的理解

在 java 中有一个特殊的字符,那就是使用 \\ (反斜线)后面再添加一个字符,我们叫转义字符(escape character),比如 \\n 表示的是换号符号,并不是单纯的一个 n 字符了。

那 \\ (反斜线)用来做转义字符了,那么程序就是要输出一个 \\ (反斜线)怎么处理呢?

那就是再使用一个 \\ (下划线) ,用来说明告诉程序,接下来的 \\ 并不是用来转义字符的。

下面我们看看 String 类中关于 \\ (反斜线)的一些使用;

我们先来看看下面的代码:

你能回到以上 8 个示例的输出结果吗?如果可以,那么你对 java 当中转义字符 \\ 和反转义 \\\\ 应该很清楚了,结果如下:

在 java 语言当中 ‘ (单引号)用来表示字符,“ (双引号)用来表示字符串,所以如何输出一个单引号字符和一个双引号呢?所以在 java 当中就有了转义字符 \\ 来帮忙了。

如何反转义呢?那就使用两个 \\\\ ,上面示例 4 中两个 \\\\ 可以输出一个 \\ 。

在示例 5 中大家熟知的 windows 系统中的文件路径通常就需要一个 \\ ,那么使用两个 \\\\ 就可以防止发生转义,正常的输出一个 \\ 。

在示例 7 中使用了三个 \\\\\\ 怎么理解呢? 前两个会输出一个 \\ , 最后一个 \\是转义字符,\\“ 是输出一个 “ 。

**在示例 8 中的四个 \\\\\\\\ 表示一个 \\ 怎么理解呢?尝试解释一下,查询了解一下……**

在 java 中有这样一个包,java.util.regex 这个包提供给我们方便使用正则表达式, Pattern 类是干嘛用的呢?

正则表达式就是一类字符串,这类字符串符合特定的规则而已,比如:**[0-9]\\d{5}** 就是匹配六位数字。

java 中 Pattern 就是正则表达式,通过 Pattern.compile(String regex) 来创建一个正则表达式实例,正则表达式是一个有规则的字符串,在 java 中定义一个有规则的字符串(正则表达式)用 Pattern 来表示。

Pattern 类文档中列举了许多正则表达式的规则,正则表达式忘记了也可以查询该类的 API 文档:

[https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html)

上文的一个问题 :**在上文的示例 8 中的四个 \\\\\\\\ 表示一个 \\ 怎么理解呢?**

我们看一下 String 类的 replaceAll 方法,第一个参数是 regex 正则表达式,我们就大致可以明白:

1. 在 java 中两个反斜线 \\\\ 输出一个反斜线 \\ ,四个反斜线就会处理成两个反斜线 \\\\ 。

2. 再由正则表达式处理上述结果的两个反斜线 \\\\ ,这里最终结果两个反斜线又解释成了一个反斜线 \\ 。

所以在 java 当中如果需要通过正则表达式匹配一个反斜线,那么就需要四个反斜线 \\\\\\\\ 。

下面来研究一下 Pattern 这个类:

结果如下:

以上方法中 compile 方法用来编译正则表达式(创建 Pattern 示例),compile 方法提供了两个方法,带有两个参数的 int flag 可以实现不同需求,比如上面示例中的Pattern.CASE_INSENSITIVE 就是表示正则表达式匹配字符串的时候不用区分大小写;flags()用来输出 Pattern 的 flag 。

pattern() 和 toString() 方法效果相同,就是输出一个正则表达式。

macher 方法是用来生成一个匹配对象 Matcher ,该类提供了一些获取匹配到的字符串的一些方法,matches 方法是一个静态方法,提供正则表达式和需要匹配的字符串直接获取匹配结果:true 匹配,false 不匹配。

quote 方法直接输出一个文本字符串,这个方法的用处是什么呢?

有时候我们需要正则表达式失效,就是想匹配原字符串,我们可以使用该方法,具体可以参考上面的示例。

asPredicate() 方法获取一个断言,可以判断正则表达式和断言提供的字符串是否有匹配结果。

在 Pattern 类中有这样一个方法 split(CharSequence,int) 这个方法,这个方法很难理解,现在通过示例代码说明:

参考以上示例,分析 split 方法 limit 的参数一下几种情况:

1. limit > 0

split 方法返回的是数组,数组的长度不可以超过 limit 的值,limit ≥ 数组的长度才可以,为了满足这个条件因此切割字符串的次数最多是 limit – 1 次。

2. limit < 0

当 limit < 0 ,如果匹配正则表达式就会进行切割切分,匹配多少次切割多少次,数组长度没有限制。

3. limit = 0

如果匹配结果就会进行切割,切割拆分后的结果会舍弃尾部的空字符串,尾部空字符串会被丢弃,数组长度没有限制

特殊案例分析:

提供的 limit = 1, 也就是数组长度最大是 1, 因此不会进行切割拆分。

从上面的结果可以看出,少了两个 0 ,因此进行切割了两次,因为不间断连续切割,因此中间会有一个空字符串,这样数组的总长度已经等于提供的 limit = 3 ,无法在继续进行切割。

limit = 5 , 最多只能进行切割拆分 4 次 ,中间不间断连续切割两次补了一个空字符串,尾部也是不间断连续切割补了一个字符串,最后因为在字符串尾部切割了一次,没有剩余子串,尾部会追加一个空字符串。

当 limit = 0 的时候,同一个参数的 split 方法效果相同:

如果切割拆分的尾部有空字符串将会被舍弃。

splitAsStream 方法提供了生成 Stream 流的方法,这样方便我们把拆分后的结果就行流式处理(切割拆分结果同 limit = 0 ,使用一个参数的 split 方法相同),方便集合操作等等。

Matcher 类用来获取匹配结果,Matcher 类中有几个高频常用方法,下面加以说明:

在解释说明 Matcher 类常用方法之前,我们需要理解一个概念,那就是正则表达式中组的概念,正则表达式如何分组,组号怎么理解的,组名又是什么呢?

正则表达式中使用括号来分组,我们可以统计有几个左括号就是有几组,从左边开始算起,组号分别是 1,2,3…..n 。

ABC 是第一组,组号是 1 。

BC 是第二组,组号是 2 。

C 是第三组,组号是 3 。

组号 0 表示正则表达式的一个整体,上面也是就是 ABC 。

上面一段代码正则表达式有几组,每组匹配的是什么?

上面的正则表达式中分 3 组,第一组 \\d{3}. 在匹配的结果中第一组是 3个数组和任意一个字符;第二组是 \\d{3} 是 3 个数字;第三组 \\w{2} 匹配两个字母。

这个方法用来统计改正则表达式有几组,上面的示例中 groupCount 输出是 3 。

find 方法是找到给定字符串匹配到的正则表达式的结果,上面示例中 m.find() 结果是 true,它的运行机制是从给定的字符串 hello987xworld456yhltest 从头开始匹配正则表达式,找到 9 开始进行匹配,然后 987xwo 中到一个匹配,程序会记录下字符串匹配到的开始索引和结束索引 ;

继续匹配,一直匹配到字符串的结束为止,当匹配到 456yhl 匹配到第二个结果,程序依然会记录第二次匹配到的开始索引和结束索引。

综上所述,上面的示例 m.find () 在 while 循环到中会找到两次匹配结果,第一次匹配结果是:987xwo 返回 true ; 第二次匹配结果是:456yhl 返回 true,findCount 的输出结果是 2 。

start 方法返回值是匹配到结果在字符串中的开始索引,end 方法返回的是匹配到结果在字符串中的结束索引,上面示例中匹配到的第一个结果 987xwo 在给定的字符串中 hello987xworld456yhltest 开始索引是 5,结束索引是 11; 第二个结果 456yhl 开始索引是 14,结束索引是 20 。

group() 方法返回的是正则表达式匹配到的结果,示例中 group()的输出结果分别是 987xwo 和 456yhl , group (0) 和 group() 结果相同都是输出完全匹配结果。

group(int) 方法中的参数是组号,上面示例中正则表达式有三组,第一组匹配的是3个数组和任意一个字符 ,因此在第一次匹配的结果 987xwo 中第一组是 987x,第二组是是 3 个数字 987,第三组是两个字母 wo;同理可分析第二次匹配结果 456yhl 中各组的匹配结果。

依据综上分析,可知上述示例的输出结果:

Matcher 类中 reset() 相关方法

上述示例说明了 reset 方法的使用规则:

reset 中文意思是重置,在这里是重置匹配的位置,从头开始。

1. 示例中的 m1.find() 会往下寻找匹配,找到匹配后就录匹配的结束位置,当下一次 m1.find() 的时候,会从上次的匹配的结束位置,继续往下匹配。

2. m2.reset() 后会从头开始匹配,在 m2 的第一个例子中,两次的匹配结果相同。

3. 在示例 m2.reset(\”123testdemo345\”) 当中,m2 重置了要匹配的字符串对象,结果也会从头开始进行结果的匹配。

4. lookingAt() 方法的效果就是从头开始匹配,因此最后一个 lookingAt() 的示例,两次输出的结果是相同的。

Matcher 类的 matches() 方法分析:

matches() 匹配的是整个字符串是否匹配正则表达式,比如上面示例当中的正则表达式是要匹配的是 3 个数字,m1 给定的字符串是 1345 四个数字不匹配,m2 符合要求返回true,m3 给定的 134345 给定六个数字依然不匹配,返回 false , matches() 方法需要同 find() 方法区分开来:

1. matches 比配的是整体字符串,整体匹配正则表达式才会返回 true 。

2. find() 是在给定的字符串中从头开始找子串,子串有符合就会返回 true ;如果继续调用 find() 会从匹配的前一个子串的结尾处,继续寻找下去。

更新给定字符串的方法:

replaceAll 方法是从给定的字符串中找匹配正则表达式的子串,找到后使用replaceAll 方法提供的字符串替换匹配到的子串,上述示示例中正则表达式的意思是匹配 b 在尾部,b 前面有 0 个或者多个 a 的子串,匹配到子串后使用 replaceAll 方法提供的参数字符串替换掉匹配到的子串,示例中 s 的输出结果是 foo-foo-foo- 。

appendReplacement 方法把给定的字符串 one cat two cats in the yard 从头开始匹配正则表达式,如果匹配成功的话,就把匹配到的子串之前的子串追加到 StringBuffer 当中,并把匹配到的子串替换成 appendReplacement 给定的第二个参数字符串继续追加到 StringBuffer 当中 ,该方法的逻辑大致总结如下:**匹配成功,StringBuffer = 目标子串前面的子串 + 替换参数**

因此上面示例 while 循环结束以后,输出结果是 one dog two dog 。

appendTail(StringBuffer) 追加尾部没匹配到的子串到 StringBuffer 当中,最终的输出是 one dog two dogs in the yard 。

综上所述关于 Matcher 类的主要功能大致列举完毕…

能写出一个好的正则表达式是正则表达式最难的地方,有时候看着别人写的正则表达式就是看不懂,这时候我们就要寻求万能的谷歌帮忙了,其实正则表达式的各种表现形式,在 Java 的 Pattern API 也有列举说明:

[https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html)

下面列举几个不常用的一些写法:

为了理解上面这个表达式,我们来看看下面的例子:

前文提到了正则表达式的分组,但是正则表达式的命名没有提到?参考示例 “(?<hello>\\\\d3)\\\\w{2}” 表示的是匹配三个数字和两个字母的字符串,其中 “(?<hello>\\\\3) ” 表示的一个分组,组号是 1, 组名是 hello ,所以参考以上示例可知 m.group(”hello”) 是得到组名为 hello 的字符串。

(?=X) 可以这样理解:

1. 寻找匹配到的正则表达式字符串,其中包含匹配到的 X 子串,X 子串在尾部 ;

2. 去掉尾部匹配到的字符串中是 X 的子串,然后得到结果。

上面示例中的正则表达式是 “zempty(?=handsome|cool)” 表示的的是在含有 zemptyhandsome 或者 zemptycool 的字符串中获取 zempty 这个结果。

(?!X) 可以这样理解:

1. 寻找匹配到的正则表达式字符串其中不包含的 X 子串,不包含的 X 子串在尾部;

2. 去掉尾部匹配字符串中不是 X 的子串,然后得到结果。

上面的示例中 \”zempty(?!handsome|cool)\” 表示的在含有 zempty 且后面不能包含 handsome 或者 cool 的子字符串中获取 zempty 整个结果。

通常 (?=X) 和 (?!X) 的正则表达式写法就形如 xxxx(?=X) 获取尾部是 X 的 xxxx, 比如示例中 zempty(?=handsome|cool) 就是获取 zempty 这个子字符串,但是条件是只能获取zempty 后面是 handsome 或者 cool 中的 zempty 。

(?<=X) 可以这样理解:

1. 寻找匹配到的正则表达式字符串其中包含匹配到的 X ,X 子串在头部;

2. 去掉头部匹配到的字符串中是 X 的子串,然后得到结果。

上面的示例中的正则表达式 \”(?<=zempty|boys)handsome\” 表示的是在含有zemptyhandsome 或者 boyshandsome字符串中获取 handsome 这个结果。

(?<!X) 可以这样理解:

1. 寻找匹配到的正则表达式字符串其中不包含的 X 子串 ,不包含的 X 子串在尾部;

2. 去掉头部匹配到的字符串中不是X 的子串,然后得到结果。

上面的示例中的正则表达式 \”(?<!zempty|boys)handsome\” 表示的是在获取 handsome 这个结果,但是 handsome 前面不能有 zempty 或者 boys 。

通常 (?<=X) 和 (?<!X) 的正则表达式写法是 (?<=X)xxxx 获取头部是 X 的 xxxx,比如示例中的 \”(?<=zempty|boys)handsome\” 就是获取 handsome 这个子字符串,但是条件是只能获取 handsome 前面是 zempty 或者 boys 的 handsome 。

。。。。

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。