Java程序设计(高级及专题)- 正则表达式(1)

以 (?) 开头的组是纯的非捕获 组,它不捕获文本,也不针对组合计进行计数。

后向引用用于重复搜索前面某个分组匹配的文本。例如:

\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者kitty kitty。

也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>\w+)(或者把尖括号换成’也行:(?'Word'\w+)),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s+\k<Word>\b

零宽断言

(?=exp)也叫零宽度正预测先行断言,它断言被匹配的字符串以表达式exp结尾但除了结尾以外的部分。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I’m singing while you’re dancing.时,它会匹配sing和danc。

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

| 代码/语法 | 说明 |

| — | — |

| (?=exp) | 匹配exp前面的位置 |

| (?<=exp) | 匹配exp后面的位置 |

| (?!exp) | 匹配后面跟的不是exp的位置 |

| (?<!exp) | 匹配前面不是exp的位置 |

注释

小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d \d?(?#0-199)

贪婪与懒惰

| 语法 | 说明 |

| — | — |

| *? | 重复任意次,但尽可能少重复 |

| +? | 重复1次或更多次,但尽可能少重复 |

| ?? | 重复0次或1次,但尽可能少重复 |

| {n,m}? | 重复n到m次,但尽可能少重复 |

| {n,}? | 重复n次以上,但尽可能少重复 |

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可

能多的字符。考虑这个表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的

话,它会匹配整个字符串aabab。这被称为贪婪匹配。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,

只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用

最少的重复。现在看看懒惰版的例子吧:

a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字

符)和ab(第四到第五个字符)。

POSIX 字符类(仅 US-ASCII)

| 语法 | 说明 |

| — | — |

| \p{Lower} | 小写字母字符:[a-z] |

| \p{Upper} | 大写字母字符:[A-Z] |

| \p{ASCII} | 所有 ASCII:[\x00-\x7F] |

| \p{Alpha} | 字母字符:[\p{Lower}\p{Upper}] |

| \p{Digit} | 十进制数字:[0-9] |

| \p{Alnum} | 字母数字字符:[\p{Alpha}\p{Digit}] |

| \p{Punct} | 标点符号:!"#$%&'()*+,-./:;<=>?@[\]^_{|}~ |

| \p{Graph} | 可见字符:[\p{Alnum}\p{Punct}] |

| \p{Print} | 可打印字符:[\p{Graph}\x20] |

| \p{Blank} | 空格或制表符:[ \t] |

| \p{Cntrl} | 控制字符:[\x00-\x1F\x7F] |

| \p{XDigit} | 十六进制数字:[0-9a-fA-F] |

| \p{Space} | 空白字符:[ \t\n\x0B\f\r] |

引用

| 语法 | 说明 |

| — | — |

| \ | Nothing,但是引用以下字符 |

| \Q | Nothing,但是引用所有字符,直到 \E |

| \E | Nothing,但是结束从 \Q 开始的引用 |

如:\Q\w+\E表示字符串\w+而不是正则中的单词字符:[a-zA-Z_0-9]。

其他

| 语法 | 说明 |

| — | — |

| \xhh | 十六进制值为0xhh的字符 |

| \uhhhh | 十六进制表示为0xhhhh的Unicode字符 |

| \t | 制表符Tab |

| \n | 换行符 |

| \r | 回车 |

| \f | 换页 |

| \e | 转义(Escape) |

处理选项

上面介绍了几个选项如忽略大小写,处理多行等,这些选项能用来改变处理正则表达式的方式。下面是Java中常用的正则表达式选项:

| 名称 | 说明 |

| — | — |

| CASE_INSENSITIVE | 匹配时区分大小写 |

| MULTILINE | 更改^$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) |

| DOTALL | 在 dotall 模式中,表达式 . 可以匹配任何字符,包括行结束符。默认情况下,此表达式不匹配行结束符。 |

| UNICODE_CASE | 指定此标志后,由 CASE_INSENSITIVE 标志启用时,不区分大小写的匹配将以符合 Unicode Standard 的方式完成。默认情况下,不区分大小写的匹配假定仅匹配 US-ASCII 字符集中的字符。通过嵌入式标志表达式 (?u) 也可以启用 Unicode 感知的大小写折叠。指定此标志可能对性能产生影响。 |

| CANON_EQ | 启用规范等价。指定此标志后,当且仅当其完整规范分解匹配时,两个字符才可视为匹配。例如,当指定此标志时,表达式 “a\u030A” 将与字符串 “\u00E5” 匹配。默认情况下,匹配不考虑采用规范等价。不存在可以启用规范等价的嵌入式标志字符。指定此标志可能对性能产生影响。 |

| UNIX_LINES | 启用 Unix 行模式。在此模式中,.、^ 和 $ 的行为中仅识别 ‘\n’ 行结束符。通过嵌入式标志表达式 (?d) 也可以启用 Unix 行模式。 |

| LITERAL | 指定此标志后,指定模式的输入字符串就会作为字面值字符序列来对待。输入序列中的元字符或转义序列不具有任何特殊意义。标志 CASE_INSENSITIVE 和 UNICODE_CASE 在与此标志一起使用时将对匹配产生影响。其他标志都变得多余了。不存在可以启用字面值解析的嵌入式标志字符。 |

| UNICODE_CHARACTER_CLASS |   |

| COMMENTS | 模式中允许空白和注释。此模式将忽略空白和在结束行之前以 # 开头的嵌入式注释。通过嵌入式标志表达式 (?x) 也可以启用注释模式。 |

JAVA


基本用法

Pattern pattern = Pattern.compile(“\ba\w*\b”);

Matcher matcher = pattern.matcher(“abcdab cccabcd aaacd”);

int index = 0;

while (matcher.find()) {

String res = matcher.group();

System.out.println(index + “:” + res);

index++;

}

\\ba\\w*\\b表示匹配以字母a为开头的单词。

Pattern.compile(regex)表示将给定的正则表达式编译到具有给定标志的模式中。

matcher(str)创建匹配给定输入与此模式的匹配器。

mather.find()尝试查找与该模式匹配的输入序列的下一个子序列。

此方法从匹配器区域的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,则从以前匹配操作没有匹配的第一个字符开始。

如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。

group() 返回由以前匹配操作所匹配的输入子序列。

打印结果:

不同

从上例中可以看出,Java中的正则表达式与之前所说不一致(多了一个),在其他语言中\\表示我想在正则表达式中插入一个普通的反斜线,请不要给它任何特殊的意义,而在Java中,\\的意思是我想要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。

如果想表示数字,则是\\d。如果要插入普通的反斜线,则是\\\\

String

String类中有几种方法可以使用正则表达式:

| 方法 | 返回类型 | 功能 | 示例 |

| — | — | — | — |

| matches() | boolean | 告知此字符串是否匹配给定的正则表达式。 | "-1234".matches("^-?\\d+$") => true |

| replaceAll(String regex, String replacement) | String | 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。 | "a1b2c3".replaceAll("[a-zA-z]", "") => 123 |

| replaceFirst(String regex, String replacement) | String | 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 | "Hello World! Hello Everyone!".replaceFirst("\\s", "") => HelloWorld! Hello Everyone! |

| split(String regex) | String[] | 根据给定正则表达式的匹配拆分此字符串。 | "boo:and:foo".split(":") => { "boo", "and", "foo" } |

| split(String regex, int limit) | String[] | 根据给定正则表达式的匹配拆分此字符串。 | "boo:and:foo".split(":", 5) => { "boo", "and", "foo" } |

split(String regex, int limit)方法中limit 参数控制模式应用的次数,因此影响所得数组的长度。如果该限制 n 大于 0,则模式将被最多应用 n - 1 次,数组的长度将不会大于 n,而且数组的最后一项将包含所有超出最后匹配的定界符的输入。如果 n 为非正,那么模式将被应用尽可能多的次数,而且数组可以是任何长度。如果 n 为 0,那么模式将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃。

例如,字符串 "boo:and:foo" 使用这些参数可生成以下结果:

| Regex | Limit | 结果 |

| — | — | — |

| : | 2 | { "boo", "and:foo" } |

| : | 5 | { "boo", "and", "foo" } |

| : | -2 | { "boo", "and", "foo" } |

| o | 5 | { "b", "", ":and:f", "", "" } |

| o | -2 | { "b", "", ":and:f", "", "" } |

| o | 0 | { "b", "", ":and:f" } |

调用此方法的 str.split(regex, n) 形式与以下表达式产生的结果完全相同:Pattern.compile(regex).split(str, n)

java.util.regex

在regex包中,包括了两个类,Pattern(模式类)和Matcher(匹配器类)。Pattern类是用来表达和陈述所要搜索模式的对象,Matcher类是真正影响搜索的对象。另加一个新的例外类,PatternSyntaxException,当遇到不合法的搜索模式时,会抛出例外。

Pattern

  • 简介

正则表达式的编译表示形式。

指定为字符串的正则表达式必须首先被编译为此类的实例。然后,可将得到的模式用于创建 Matcher 对象,依照正则表达式,该对象可以与任意字符序列匹配。执行匹配所涉及的所有状态都驻留在匹配器中,所以多个匹配器可以共享同一模式。

因此,典型的调用顺序是

Pattern p = Pattern.compile(“a*b”);

Matcher m = p.matcher(“aaaaab”);

boolean b = m.matches();

在仅使用一次正则表达式时,可以方便地通过此类定义 matches 方法。此方法编译表达式并在单个调用中将输入序列与其匹配。语句

boolean b = Pattern.matches("a*b", "aaaaab");

等效于上面的三个语句,尽管对于重复的匹配而言它效率不高,因为它不允许重用已编译的模式。

此类的实例是不可变的,可供多个并发线程安全使用。Matcher 类的实例用于此目的则不安全。

  • 常用方法

  • Pattern类中最重要的方法便是compilematcher,上面已经给出示例。下面看看其他方法:

| 方法 | 返回类型 | 功能 | 示例 |

| — | — | — | — |

| flags() | int | 返回此模式的匹配标志。 | Pattern.compile("\\w*", Pattern.MULTILINE).flags() => 8 |

| pattern() | String | 返回在其中编译过此模式的正则表达式。 | Pattern.compile("\\w*").pattern() => \w* |

| static quote(String s) | String | 返回指定 String 的字面值模式 String。 | Pattern.quote("\\w+") => \Q\w+\E |

Matcher

  • 简介

通过解释 Pattern 对 character sequence 执行匹配操作的引擎。

通过调用模式的 matcher 方法从模式创建匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:

  • matches 方法尝试将整个输入序列与该模式匹配。

  • lookingAt 尝试将输入序列从头开始与该模式匹配。

  • find 方法扫描输入序列以查找与该模式匹配的下一个子序列。

每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的更多信息。

匹配器在其输入的子集(称为区域)中查找匹配项。默认情况下,此区域包含全部的匹配器输入。可通过 region 方法修改区域,通过 regionStart 和 regionEnd 方法查询区域。区域边界与某些模式构造交互的方式是可以更改的。

此类还定义使用新字符串替换匹配子序列的方法,需要时,可以从匹配结果计算出新字符串的内容。可以先后使用 appendReplacement 和 appendTail 方法将结果收集到现有的字符串缓冲区,或者使用更加便捷的 replaceAll 方法创建一个可以在其中替换输入序列中每个匹配子序列的字符串。

匹配器的显式状态包括最近成功匹配的开始和结束索引。它还包括模式中每个捕获组捕获的输入子序列的开始和结束索引以及该子序列的总数。出于方便的考虑,还提供了以字符串的形式返回这些已捕获子序列的方法。

匹配器的显式状态最初是未定义的;在成功匹配导致 IllegalStateException 抛出之前尝试查询其中的任何部分。每个匹配操作都将重新计算匹配器的显式状态。

匹配器的隐式状态包括输入字符序列和添加位置,添加位置最初是零,然后由 appendReplacement 方法更新。

可以通过调用匹配器的 reset() 方法来显式重置匹配器,如果需要新输入序列,则调用其 reset(CharSequence) 方法。重置匹配器将放弃其显式状态信息并将添加位置设置为零。

此类的实例用于多个并发线程是不安全的。

  • 常用方法

matches()

表示字符串完全符合给出的正则表达式所表示的范围。只要有一个字符不匹配则返回false。如:

Pattern.matches("[a-z]", "aAbBcC")

返回false,因为正则表达式表示的范围不包含大写字母。

find()

find()尝试查找与该模式匹配的输入序列的下一个子序列。

此方法从匹配器区域的开头开始,如果该方法的前一次调用成功了并且从那时开始匹配器没有被重置,则从以前匹配操作没有匹配的第一个字符开始。

如果匹配成功,则可以通过 start、end 和 group 方法获取更多信息。

Pattern pattern = Pattern.compile(“[a-z]”);

Matcher matcher = pattern.matcher(“aAbBcC”);

matcher.find();

返回true,因为可以匹配到小写字母。

需要注意的是在执行find()方法时,其内部指针会跟着变动,比如第一次调用完毕,此时的matcher.start()为0,因为第一个字母就匹配上了,而matcher.end()则返回2,因为它返回的是最后不匹配(A)的位置的下一个索引,因此可以通过如下方法看到指针移动轨迹:

Pattern p = Pattern.compile(“\d{3,5}”);

String s = “123-34345-234-00”;

while (m.find()) {

System.out.println(m.start() + “-” + m.end());

}

打印结果:

可以看到,find方法比较实诚,善始善终,但我们有时候需要人工插手而不总是从头到尾来一遍,这就涉及到find方法的另一个多态形式了。

find(int start)

从指定位置开始匹配,使用此方法模拟find():

int index = 0;

while (m.find(index)) {

System.out.println(m.start() + “-” + m.end());

index = m.end();

}

lookingAt()

此方法总是从头开始匹配,无论是否匹配上均立即返回相应结果,并且不再继续匹配,语言乏力,此处使用find方法模拟:

public static void main(String[] args) throws Exception {

Pattern p = Pattern.compile(“\d{3,5}”);

String s = “123-34345-234-00”;

Matcher m = p.matcher(s);

System.out.println(cosplayMethodLookingAt(m));

System.out.println(cosplayMethodLookingAt(m));

System.out.println(cosplayMethodLookingAt(m));

System.out.println(cosplayMethodLookingAt(m));

}

private static boolean cosplayMethodLookingAt(Matcher m) {

boolean res = m.find() && m.start() == 0;

m.reset();

return res;

}

显示效果与lookingAt方法相同,但内部实现不一样:

public static void main(String[] args) throws Exception {

Pattern p = Pattern.compile(“\d{3,5}”);

String s = “123-34345-234-00”;

Matcher m = p.matcher(s);

m.find();

m.lookingAt();

m.find();

// 4

System.out.println(m.start());

cosplayMethodLookingAt(m);

m.find();

System.out.println(m.start());

}

private static boolean cosplayMethodLookingAt(Matcher m) {

boolean res = m.find() && m.start() == 0;

m.reset();

return res;

}

打印结果:

由此可见,lookingAt方法并不会重置匹配使用的内部指针。

appendReplacement

public Matcher appendReplacement(StringBuffer sb, String replacement)

实现非终端添加和替换步骤。

此方法执行以下操作:

它从添加位置开始在输入序列读取字符,并将其添加到给定字符串缓冲区。在读取以前匹配之前的最后字符(即位于索引 start() - 1 处的字符)之后,它就会停止。

它将给定替换字符串添加到字符串缓冲区。

它将此匹配器的添加位置设置为最后匹配位置的索引加 1,即 end()。

替换字符串可能包含到以前匹配期间所捕获的子序列的引用:g每次出现时,都将被group(g)的计算结果替换。g每次出现时,都将被group(g)的计算结果替换。 之后的第一个数始终被视为组引用的一部分。如果后续的数可以形成合法组引用,则将被合并到 g 中。只有数字 ‘0’ 到 ‘9’ 被视为组引用的可能组件。例如,如果第二个组匹配字符串 “foo”,则传递替换字符串 "2bar"将导致"foobar"被添加到字符串缓冲区。可能将美元符号(2bar"将导致"foobar"被添加到字符串缓冲区。可能将美元符号() 作为替换字符串中的字面值(通过前面使用一个反斜线 ($))包括进来。

注意,在替换字符串中使用反斜线 () 和美元符号 ($) 可能导致与作为字面值替换字符串时所产生的结果不同。美元符号可视为到如上所述已捕获子序列的引用,反斜线可用于转义替换字符串中的字面值字符。

此方法设计用于循环以及 appendTail 和 find 方法中。例如,以下代码将 one dog two dogs in the yard 写入标准输出流中:

Pattern p = Pattern.compile(“cat”);

Matcher m = p.matcher(“one cat two cats in the yard”);

StringBuffer sb = new StringBuffer();

while (m.find()) {

m.appendReplacement(sb, “dog”);

}

m.appendTail(sb);

System.out.println(sb.toString());

输出结果:

appendTail

StringBuffer appendTail(StringBuffer sb)

此方法从添加位置开始从输入序列读取字符,并将其添加到给定字符串缓冲区。可以在一次或多次调用 appendReplacement 方法后调用它来复制剩余的输入序列。

以上例来说,当匹配到第二个cat时,while语句块中的代码就失效了,此时后面的字符串s in the yard就需要使用appendTail方法来补齐,否则输出结果就是:显然有所缺失。

group

String group()

返回由以前匹配操作所匹配的输入子序列。

对于具有输入序列 s 的匹配器 m,表达式 m.group() 和 s.substring(m.start(), m.end()) 是等效的。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

美团二面惜败,我的凉经复盘(附学习笔记+面试整理+进阶书籍)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-wME3DA5P-1713453133408)]

[外链图片转存中…(img-7L8V6LeD-1713453133410)]

[外链图片转存中…(img-aZGKL2MQ-1713453133411)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

[外链图片转存中…(img-V3A9xntd-1713453133413)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 30
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值