热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

正则表达式matches_哈哈哈哈这个好玩!正则表达式王国奇遇记...

公众号关注“GitHubDaily”设为“星标”,带你了解技术圈内新鲜事!01初来乍到欢迎来到正则表达式的国度,勇士!这里的
公众号关注 “GitHubDaily”

设为 “星标”,带你了解技术圈内新鲜事!9477af7e8f5c4d127a73ff6e319b9735.png01初来乍到14f2dd10ef9bf9ab8c4baca172554fce.png

欢迎来到正则表达式的国度,勇士!这里的每一个人都使用正则表达式,我是这里的 NPC,每一个来到这里的人都将由我代为介绍正则世界的规则,至于能领悟到何种境界,就看你的造化了。祝你好运,勇士!

啊,好的,正则表达式......有点奇怪的名字,它是什么呢?

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

什么?你还没有听过正则表达式,真是一个莽撞的小伙子。看来你也和外面世界的人一样,每次只有用到 字符串匹配 时,才会通过「谷鸽」来我们的国度寻找答案。一群知其然不知其所以然的家伙。

说着,NPC 身前浮现出几个鎏金大字:正则表达式用来匹配一系列符合某个规则的字符串的表达式14f2dd10ef9bf9ab8c4baca172554fce.png

正则的意思是正规、规则。正则表达式的英文名是 Regular Expression,可以直译为描述某种规则的表达式,一般缩写为 regex。

02牛刀小试14f2dd10ef9bf9ab8c4baca172554fce.png

我先来考考你吧:你如何判断一个字符串是不是有效的电话号码?这可是一个非常常见的需求。

没问题,我以前确实写过一份类似的代码。首先判断字符串是否是 11 位,再判断每一位是否都是数字就可以了。

ea334c81200a15c3f2fd50b2e0865d80.png

public static boolean isValidPhoneNumber(String number) { // 判断是否是 11 位 if (number.length() !&#61; 11) return false; // 判断每一位是否全为数字 for (int i &#61; 0; i if (number.charAt(i) <&#39;0&#39; || number.charAt(i) > &#39;9&#39;) return false; } return true;}14f2dd10ef9bf9ab8c4baca172554fce.png

好了好了&#xff0c;快把你这份代码藏好&#xff0c;这份代码放到我们正则的国度是会被笑掉大牙的。看看我们国度的人是怎么实现这份需求的吧&#xff01;

public static boolean isValidPhoneNumber(String number) { return number.matches("\\d{11}");}

啊&#xff1f;如此简洁的实现&#xff0c;正则强者竟恐怖如斯&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

这可不是什么强者写的代码&#xff0c;充其量算是牛刀小试罢了。

03初窥门径14f2dd10ef9bf9ab8c4baca172554fce.png

我先给你讲讲正则表达式的精确匹配。一个普通的字符串&#xff0c;比如 abc&#xff0c;它如果用来做正则表达式匹配的话&#xff0c;只能匹配自己。也就是说它只能匹配字符串 abc&#xff0c;不能匹配 ab&#xff0c;Abc 等其他任何字符串。

System.out.println("abc".matches("abc")); // 输出为 trueSystem.out.println("ab".matches("abc")); // 输出为 falseSystem.out.println("Abc".matches("abc")); // 输出为 false

这好像没什么用&#xff0c;需要精确匹配的话&#xff0c;我们可以用 String.equals() 函数&#xff0c;不需要用正则吧&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

没错&#xff0c;正则表达式的精确匹配很少用到。我只是在给你介绍正则表达式的一条基本规则而已。

14f2dd10ef9bf9ab8c4baca172554fce.png

如果需要匹配的字符串含有特殊字符&#xff0c;那就需要用 \转义。比如 a&b&#xff0c;在用正则表达式匹配时&#xff0c;需要使用 a\&b&#xff0c;又由于在 Java 字符串中&#xff0c;\ 也是特殊字符&#xff0c;它也需要转义&#xff0c;所以 a\&b 对应的 Java 字符串是 a\\&b&#xff0c;它是用来匹配 a&b 的。

System.out.println("a&b".matches("a\\&b")); // 输出为 true

这么说来&#xff0c;这两个反斜杠的意义竟然还不一样&#xff1a;一个是正则的转义&#xff0c;一个是 Java 字符串的转义。那么我们之前那个匹配电话号码的例子里面&#xff0c; \\d 的本意也是 \d 吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

不错不错&#xff0c;算你还有点悟性。\d 在正则表达式中表示匹配任意数字&#xff0c;d 是 digital 的简写。比如 00\d 就可以匹配 000&#xff0c; 007&#xff0c;008 等等。

那么&#xff0c;00\d 可以匹配 0066 吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

不能&#xff0c;\d 只能匹配单个数字。

那我要怎么才能匹配多个数字呢&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

你可以写多次&#xff0c;比如 \d\d 就能匹配两个数字&#xff0c;\d\d\d 能匹配三个数字&#xff0c;需要匹配几个数字就写几次就行了。

System.out.println("1".matches("\\d\\d")); // 输出为 falseSystem.out.println("11".matches("\\d\\d")); // 输出为 trueSystem.out.println("111".matches("\\d\\d")); // 输出为 false

那我如果要匹配 10000 个数字呢&#xff1f;总不能写一万次吧&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

那就像我们刚才匹配电话号码的例子一样&#xff0c;在 \d 后面打上花括号 {}&#xff0c;{n} 表示匹配 n 次。\d{10000} 就表示匹配 10000 个数字。

原来如此&#xff0c;现在我能完全看懂刚才写的匹配电话号码的例子了&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

趁热打铁&#xff0c;如果要匹配 n ~ m 次&#xff0c;用 {n,m} 即可&#xff0c;如果要匹配至少 n 次&#xff0c;用 {n,} 即可。需要注意 , 后不能有空格。

System.out.println("1".matches("\\d{1,2}")); // 输出为 trueSystem.out.println("12".matches("\\d{1,2}")); // 输出为 trueSystem.out.println("123".matches("\\d{1,2}")); // 输出为 falseSystem.out.println("123".matches("\\d{2,}")); // 输出为 true

按照这个写法&#xff0c;如果要匹配最多 m 次&#xff0c;是不是用 {,m} &#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

刚夸了你有点悟性又被你蠢哭了&#xff0c;最多 m 次需要这么写吗&#xff1f;直接用 {0,m} 不就行了吗&#xff1f;只是因为正无穷不好表示我们才用的 {n,}&#xff0c;在正则国度根本没有 {,m} 这样的写法。

啊&#xff0c;原来如此&#xff0c;我想多了。

ea334c81200a15c3f2fd50b2e0865d80.png04小有所成14f2dd10ef9bf9ab8c4baca172554fce.png

正则的基础规则中&#xff0c;除了 \d&#xff0c;还有 \w\s&#xff0c;w 是 word 的简写&#xff0c;表示匹配一个常用字符&#xff0c;包括字母、数字、下划线。s 是 space 的简写&#xff0c;表示匹配一个空格&#xff0c;包括三种&#xff1a;

  • 空格键打出来的空格

  • Tab 键打出来的空格

  • 回车键打出来的空格

Tab 键打出来的空格和回车键打出来的空格&#xff1f;是指 \t\n 吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

完全正确。

我明白了&#xff0c;我来测试一下。

ea334c81200a15c3f2fd50b2e0865d80.png

System.out.println("LeetCode_666".matches("\\w{12}")); // 输出为 trueSystem.out.println("\t \n".matches("\\s{3}")); // 输出为 trueSystem.out.println("Leet\tCode 666".matches("\\w{4}\\s\\w{4}\\s\\d{3}")); // 输出为 true14f2dd10ef9bf9ab8c4baca172554fce.png

非常棒&#xff0c;我的勇士&#xff01;希望这三个基本规则还不至于让你记昏了头。不过请放心&#xff0c;没有其他字母需要记忆了&#xff0c;只有这三个而已。

05更进一步14f2dd10ef9bf9ab8c4baca172554fce.png

记住上面三个规则之后&#xff0c;你还可以顺带获得几个新的规则。因为正则国度规定&#xff1a;将字母换成大写&#xff0c;就表示相反的意思。用 \d 你可以匹配一个数字&#xff0c;\D 则表示匹配一个非数字。

System.out.println("a".matches("\\d")); // 输出为 falseSystem.out.println("1".matches("\\d")); // 输出为 trueSystem.out.println("a".matches("\\D")); // 输出为 trueSystem.out.println("1".matches("\\D")); // 输出为 false

哈&#xff0c;设计者真是太机智了&#xff0c;大大减少了我这种新手的学习成本。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

是的&#xff0c;这非常好记。类似地&#xff0c;\W 可以匹配 \w 不能匹配的字符&#xff0c;\S 可以匹配 \s 不能匹配的字符。

06渐入佳境14f2dd10ef9bf9ab8c4baca172554fce.png

有时候&#xff0c;我们对某些位置的字符没有要求&#xff0c;仅需要占个位置即可。这时候我们就可以用 . 字符。

System.out.println("a0b".matches("a.b")); // 输出为 trueSystem.out.println("a_b".matches("a.b")); // 输出为 trueSystem.out.println("a b".matches("a.b")); // 输出为 true

那是不是也可以理解为&#xff1a;. 可以匹配任意字符。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

是的&#xff0c;可以这么理解。还记得之前说的 {n} 表示匹配 n 次吗&#xff1f;有时候&#xff0c;我们对匹配的次数没有要求&#xff0c;匹配任意次均可&#xff0c;这时&#xff0c;我们就可以用 * 字符。

System.out.println("1".matches("\\d*")); // 输出为 trueSystem.out.println("123".matches("\\d*")); // 输出为 trueSystem.out.println("".matches("\\d*")); // 输出为 true

我有疑问&#xff0c;为什么第三个表达式也会输出 true 呢&#xff1f;明明没有出现数字啊&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

那意味着出现了 0 次&#xff0c;* 是指 可以匹配任意次&#xff0c;包括 0 次。也就是说&#xff0c;* 等价于 {0,}

我感觉比较常见的需求应该是某个字符至少出现一次吧&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

那就可以用 &#43; 匹配&#xff0c;&#43; 表示 至少匹配一次。它等价于 {1,}

System.out.println("1".matches("\\d&#43;")); // 输出为 trueSystem.out.println("123".matches("\\d&#43;")); // 输出为 trueSystem.out.println("".matches("\\d&#43;")); // 输出为 false

哈哈&#xff0c;看来设计者也发现了这个需求更常用。平时 &#43; 号比 * 号用得多吧&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

这倒没人统计过&#xff0c;在我们正则的国度&#xff0c;常常是一个场景一个正则&#xff0c;不存在谁比谁更常用的对比&#xff0c;按照实际场景使用就行了。

14f2dd10ef9bf9ab8c4baca172554fce.png

还有一种场景&#xff0c;如果某个字符要么匹配 0 次&#xff0c;要么匹配 1 次&#xff0c;我们就可以用 ? 匹配。它等价于 {0,1}

System.out.println("".matches("\\d?")); // 输出为 trueSystem.out.println("1".matches("\\d?")); // 输出为 trueSystem.out.println("123".matches("\\d?")); // 输出为 false

. 匹配任意字符&#xff1b;* 匹配任意次&#xff0c;包括 0 次&#xff1b;&#43; 号匹配至少 1 次&#xff0c;? 匹配 0 次或 1 次。我记住了&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png07心浮气躁

我感觉我已经掌握了够多的匹配规则&#xff0c;足以应付所有的字符串匹配场景了&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

是的&#xff0c;你已经掌握了足够多的规则&#xff0c;勇士。可先别得意得太早&#xff0c;我再考考你吧。看看匹配电话号码的程序&#xff0c;如果我们规定电话号码不能以 0 开头&#xff0c;应该怎么写正则表达式呢&#xff1f;

不能以 0 开头&#xff0c;那就不能用 \d{11}了&#xff0c;这......

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

刚刚谁还说「我已经掌握了足够多的匹配规则&#xff0c;足以应付所有的字符串匹配场景了&#xff01;」

呃&#xff0c;还差一点......快别取笑我了&#xff0c;快告诉我这个要用什么新的规则吧&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

年轻人啊&#xff0c;总是心浮气躁&#xff0c;这样的场景需要用 [] 来匹配&#xff0c;[] 用于匹配指定范围内的字符&#xff0c;比如[123456789] 可以匹配 1~9。

啊哈&#xff0c;那我就知道怎么写了&#xff0c; 这个问题的正则匹配规则是 [123456789]\d{10}

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

就是这样。这里还有一个语法糖&#xff0c;[123456789] 写起来太麻烦&#xff0c;可以写作 [1-9]

只能用于数字吗&#xff1f;可以用在字母身上吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

当然可以&#xff0c;比如 [a-g] 表示 [abcdefg]&#xff0c;[U-Z] 表示 [UVWXYZ]

但如果既可以是数字 1~9&#xff0c;又可以是字母 a~g&#xff0c;还可以是字母 U~Z&#xff0c;还是得把所有范围列出来。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

不必&#xff0c;你还可以这么写&#xff1a;[1-9a-gU-Z]

System.out.println("1".matches("[1-9a-gU-Z]")); // 输出为 trueSystem.out.println("b".matches("[1-9a-gU-Z]")); // 输出为 trueSystem.out.println("X".matches("[1-9a-gU-Z]")); // 输出为 trueSystem.out.println("A".matches("[1-9a-gU-Z]")); // 输出为 false

这可真是太方便了&#xff01;如果是 0~1&#xff0c;8~9 可以这样组合吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

那样的话&#xff0c;你写 [0189] 不是更简洁吗&#xff1f;

我想学习(装 X)。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

那当然也是可以的&#xff0c;[0-18-9] 正是你想要的。由于正则一次只匹配一个字符&#xff0c;所以这样写并不会有歧义&#xff0c;也就是说计算机不会把这种写法误解成要匹配 0~18 之类的。

System.out.println("1".matches("[0-18-9]")); // 输出为 trueSystem.out.println("5".matches("[0-18-9]")); // 输出为 false14f2dd10ef9bf9ab8c4baca172554fce.png

还有一种写法可以实现这一点&#xff0c;那就是用 运算符&#xff0c;正则的 运算符是 |&#xff0c;[0189] 也可以写作 0|1|8|9

System.out.println("1".matches("0|1|8|9")); // 输出为 trueSystem.out.println("5".matches("0|1|8|9")); // 输出为 false

所以说范围就是 的简写&#xff0c;对吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

不对&#xff0c; 可以实现更多的功能&#xff0c;它并不局限于单个字符。

System.out.println("abc".matches("abc|ABC")); // 输出为 trueSystem.out.println("ABC".matches("abc|ABC")); // 输出为 trueSystem.out.println("123".matches("abc|ABC")); // 输出为 false

如果我想排除某些字符呢&#xff1f;比如这个位置不能是 [123]。我记得你之前说正则王国以大写表示取反&#xff0c;[] 要怎么大写呢&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

[] 可没有大写之说&#xff0c;[] 取反的方式是&#xff1a;[^]&#xff0c;比如不能是 [123] 的表示方法为 [^123] 或者 [^1-3]

原来如此&#xff0c;我懂了。现在还有什么规则我没有学到的吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

新手教程到这里就结束了&#xff0c;这已经足够你应付许多应用场景了。但我这还有两本高手秘籍&#xff0c;你想不想学呢&#xff1f;

高手秘籍&#xff01;听着都让人激动啊&#xff0c;快讲讲&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png08探囊取物14f2dd10ef9bf9ab8c4baca172554fce.png

这第一本秘籍的名字叫 探囊取物。考虑一个实际需求&#xff0c;有许许多多以下格式的字符串&#xff0c;你需要用正则表达式匹配出其姓名和年龄。

  • Name&#xff1a;Aurora        Age&#xff1a;18

  • 其中还夹杂着一些无关紧要的数据

  • Name&#xff1a;Bob            Age&#xff1a;20

  • 错误的数据有着各种各样错误的格式

  • Name&#xff1a;Cassin        Age&#xff1a;22

  • ...

没问题&#xff0c;这已经难不倒我了。让我想想......观察字符串的规则&#xff0c;只需要用 Name:\w&#43;\s*Age:\d{1,3} 就能匹配了。

ea334c81200a15c3f2fd50b2e0865d80.png

System.out.println("Name:Aurora Age:18".matches("Name:\\w&#43;\\s*Age:\\d{1,3}")); // 输出为 trueSystem.out.println("其中还夹杂着一些无关紧要的数据".matches("Name:\\w&#43;\\s*Age:\\d{1,3}")); // 输出为 falseSystem.out.println("Name:Bob Age:20".matches("Name:\\w&#43;\\s*Age:\\d{1,3}")); // 输出为 trueSystem.out.println("错误的数据有着各种各样错误的格式".matches("Name:\\w&#43;\\s*Age:\\d{1,3}")); // 输出为 falseSystem.out.println("Name:Cassin Age:22".matches("Name:\\w&#43;\\s*Age:\\d{1,3}")); // 输出为 true14f2dd10ef9bf9ab8c4baca172554fce.png

很好&#xff01;一般来说&#xff0c;下一步你要做的就是取出这些表达式中的姓名和年龄&#xff0c;以便把它们存到数据库中。

那我可以用 indexOfsubString 函数来取这些值。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

的确可行&#xff0c;但你现在不需要那个蠢办法了&#xff0c;我的勇士。你已经掌握了正则的力量&#xff0c;在我们正则国度有更简洁的取值方式。

Pattern pattern &#61; Pattern.compile("Name:(\\w&#43;)\\s*Age:(\\d{1,3})");Matcher matcher &#61; pattern.matcher("Name:Aurora Age:18");if(matcher.matches()) { String group1 &#61; matcher.group(1); String group2 &#61; matcher.group(2); System.out.println(group1); // 输出为 Aurora System.out.println(group2); // 输出为 18}14f2dd10ef9bf9ab8c4baca172554fce.png

看吧&#xff0c;只要用 () 将需要取值的地方括起来&#xff0c;传给 Pattern 对象&#xff0c;再用 Pattern 对象匹配后获得的 Matcher 对象来取值就行了。每个匹配的值将会按照顺序保存在 Matcher 对象的 group 中。

14f2dd10ef9bf9ab8c4baca172554fce.png

你可以看到我用 ()  把 \\w&#43;  和 \\d{1,3} 分别括起来了&#xff0c;判断 Pattern 对象与字符串是否匹配的方法是  Matcher.matches()&#xff0c;如果匹配成功&#xff0c;这个函数将返回 true&#xff0c;如果匹配失败&#xff0c;则返回 false。

这里是不是写错了&#xff0c;为什么 group 是从下标 1 开始取值的&#xff0c;计算机不都从 0 开始数吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

并没有写错&#xff0c;这是因为 group(0) 被用来保存整个匹配的字符串了。

System.out.println(matcher.group(0)); // 输出为 Name:Aurora Age:18

原来是这样&#xff0c;分组可真是太方便了。但我们之前都是用的 String.matches 方法来匹配的正则表达式&#xff0c;这里用的 Pattern 又是什么呢&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

想知道这个问题的答案的话&#xff0c;我们不妨来看一下 String.matches 方法的源码。

public boolean matches(String regex) { return Pattern.matches(regex, this);}14f2dd10ef9bf9ab8c4baca172554fce.png

源码中调用了 Pattern.matches 方法&#xff0c;我们再跟进去。

public static boolean matches(String regex, CharSequence input) { Pattern p &#61; Pattern.compile(regex); Matcher m &#61; p.matcher(input); return m.matches();}

啊&#xff0c;我明白了&#xff01;原来 Pattern 并不是什么新鲜东西&#xff0c;String.matches  内部就是调用的 Pattern&#xff0c;两种写法的原理是一模一样的&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

没错&#xff0c;并且阅读源码之后&#xff0c;你可以发现&#xff0c;每次调用 String.matches 函数&#xff0c;都会新建出一个 Pattern 对象。所以如果要用同一个正则表达式多次匹配字符串的话&#xff0c;最佳的做法不是直接调用 String.matches 方法&#xff0c;而应该先用正则表达式新建一个 Pattern 对象&#xff0c;然后反复使用&#xff0c;以提高程序运行效率。

// 错误的做法&#xff0c;每次都会新建一个 Pattern&#xff0c;效率低boolean result1 &#61; "Name:Aurora Age:18".matches("Name:(\\w&#43;)\\s*Age:(\\d{1,3})"); boolean result2 &#61; "Name:Bob Age:20".matches("Name:(\\w&#43;)\\s*Age:(\\d{1,3})");boolean result3 &#61; "Name:Cassin Age:22".matches("Name:(\\w&#43;)\\s*Age:(\\d{1,3})"); // 正确的做法&#xff0c;复用同一个 Pattern&#xff0c;效率高Pattern pattern &#61; Pattern.compile("Name:(\\w&#43;)\\s*Age:(\\d{1,3})");boolean result4 &#61; pattern.matcher("Name:Aurora Age:18").matches();boolean result5 &#61; pattern.matcher("Name:Bob Age:20").matches();boolean result6 &#61; pattern.matcher("Name:Cassin Age:22").matches();09移花接木14f2dd10ef9bf9ab8c4baca172554fce.png

我这第二本秘籍名为 移花接木。再考虑一个实际场景&#xff1a;你有一个让用户输入标签的输入框&#xff0c;用户可以输入多个标签。可是你并没有提示用户&#xff0c;标签之前用什么间隔符号隔开。

你还别说&#xff0c;我之前真遇到过这个问题。结果用户的输入五花八门&#xff0c;有用逗号的&#xff0c;有用分号的&#xff0c;有用空格的&#xff0c;还有用制表符的......

ea334c81200a15c3f2fd50b2e0865d80.png
  • 二分&#xff0c;回溯&#xff0c;递归&#xff0c;分治

  • 搜索&#xff1b;查找&#xff1b;旋转&#xff1b;遍历

  • 数论 图论 逻辑 概率

14f2dd10ef9bf9ab8c4baca172554fce.png

那你是怎么解决的呢&#xff1f;

String.split 函数呗&#xff0c;这个函数我已经用得很熟练了。将各种分隔符号依次传入尝试&#xff0c;最后总算是解决了。

ea334c81200a15c3f2fd50b2e0865d80.png

public static String[] splitTabs(String tabs) { if(tabs.split(",").length &#61;&#61; 4) return tabs.split(","); if(tabs.split(";").length &#61;&#61; 4) return tabs.split(";"); if(tabs.split(" ").length &#61;&#61; 4) return tabs.split(" "); return new String[0];}public static void main(final String[] args){ System.out.println(Arrays.toString(splitTabs("二分,回溯,递归,分治"))); System.out.println(Arrays.toString(splitTabs("搜索;查找;旋转;遍历"))); System.out.println(Arrays.toString(splitTabs("数论 图论 逻辑 概率")));}

输出为&#xff1a;

ea334c81200a15c3f2fd50b2e0865d80.png

[二分, 回溯, 递归, 分治][搜索, 查找, 旋转, 遍历][数论, 图论, 逻辑, 概率]14f2dd10ef9bf9ab8c4baca172554fce.png

暴殄天物啊&#xff01;你这种行为就好比拿着精心打磨的钻石当电钻头&#xff0c;这样的代码在我们正则王国是会遭人唾骂的。

String.split 函数不就是用来分割字符串的吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

当然是&#xff0c;但 split 函数可不是你这样用的&#xff0c;不知你是否看过 split 函数的源码&#xff0c;这个函数传入的参数实际上是一个正则表达式。

啊&#xff1f;但我之前没写过正则表达式&#xff0c;分割出来也没出错啊&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

当然&#xff0c;你忘了我最开始给你讲的了吗&#xff1f;你直接使用字符串&#xff0c;在正则王国属于精确匹配&#xff0c;只能匹配你写死的那个字符串。

原来如此。那么我应该怎么做呢&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

当然是用正则表达式模糊匹配&#xff0c;只要能匹配成功&#xff0c;就以其分割。

System.out.println(Arrays.toString("二分,回溯,递归,分治".split("[,;\\s]&#43;")));System.out.println(Arrays.toString("搜索;查找;旋转;遍历".split("[,;\\s]&#43;")));System.out.println(Arrays.toString("数论 图论 逻辑 概率".split("[,;\\s]&#43;")));14f2dd10ef9bf9ab8c4baca172554fce.png

输出为&#xff1a;

[二分, 回溯, 递归, 分治][搜索, 查找, 旋转, 遍历][数论, 图论, 逻辑, 概率]

原来 split 函数这么强大&#xff0c;我以后不会犯这种错误了&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

字符串中&#xff0c;可不止这一个函数是传入的正则表达式&#xff0c;你还记得替换所有匹配字符串用的什么函数吗&#xff1f;

用的是 replaceAll 函数&#xff0c;这个函数不会也是传的正则表达式吧&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

正是这样&#xff0c;所以我们可以用正则表达式模糊匹配&#xff0c;将符合规则的字符串全部替换掉。比如就现在这个例子&#xff0c;我们可以把用户输入的所有数据统一规范为使用 ; 分隔&#xff0c;那我们就可以这样写。

System.out.println("二分,回溯,递归,分治".replaceAll("[,;\\s]&#43;", ";"));System.out.println("搜索;查找;旋转;遍历".replaceAll("[,;\\s]&#43;", ";"));System.out.println("数论 图论 逻辑 概率".replaceAll("[,;\\s]&#43;", ";"));14f2dd10ef9bf9ab8c4baca172554fce.png

输出为&#xff1a;

二分;回溯;递归;分治搜索;查找;旋转;遍历数论;图论;逻辑;概率

果然是 移花接木&#xff0c;模糊匹配比精确匹配效率高多了&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

还不止这一点&#xff0c;在 replaceAll 的第二个参数中&#xff0c;我们可以通过 $1&#xff0c;$2&#xff0c;...来反向引用匹配到的子串。只要将需要引用的部分用 () 括起来就可以了。

System.out.println("二分,回溯,递归,分治".replaceAll("([,;\\s]&#43;)", "---$1---"));System.out.println("搜索;查找;旋转;遍历".replaceAll("([,;\\s]&#43;)", "---$1---"));System.out.println("数论 图论 逻辑 概率".replaceAll("([,;\\s]&#43;)", "---$1---"));14f2dd10ef9bf9ab8c4baca172554fce.png

输出为&#xff1a;

二分---,---回溯---,---递归---,---分治搜索---;---查找---;---旋转---;---遍历数论--- ---图论--- ---逻辑--- ---概率

哈&#xff0c;有时候我们不需要替换&#xff0c;只需要将正则匹配出来的部分添加一些前缀或后缀&#xff0c;就可以用这种方式&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

完全正确。

10蓦然回首14f2dd10ef9bf9ab8c4baca172554fce.png

恭喜你学完了所有的正则教程&#xff0c;现在你知道正则表达式是什么了吧。

没错&#xff0c;以前总感觉正则表达式晦涩难懂&#xff0c;每次用到时就去网上搜索答案&#xff0c;现在看来也不过如此。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

不过如此 倒是有些托大了&#xff0c;虽然我给你介绍了正则表达式的基本规则&#xff0c;但正则表达式里面还有不少的学问可以去挖掘的。每种技术都有一个熟能生巧的过程。

什么&#xff1f;还有学问&#xff1f;我感觉我已经学完了啊&#xff01;还有什么学问&#xff0c;一并给我讲了吧&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

那你看这样一道题&#xff1a;给你一些字符串&#xff0c;统计其末尾 e 的个数

  • LeetCode

  • LeetCodeeee

  • LeetCodeee

看起来并不难&#xff0c;用 (\w&#43;)(e*) 匹配&#xff0c;再取 group(2) 判断即可。

ea334c81200a15c3f2fd50b2e0865d80.png

Pattern pattern &#61; Pattern.compile("(\\w&#43;)(e*)");Matcher matcher &#61; pattern.matcher("LeetCode");if (matcher.matches()) { String group1 &#61; matcher.group(1); String group2 &#61; matcher.group(2); System.out.println("group1 &#61; " &#43; group1 &#43; ", length &#61; " &#43; group1.length()); System.out.println("group2 &#61; " &#43; group2 &#43; ", length &#61; " &#43; group2.length());}14f2dd10ef9bf9ab8c4baca172554fce.png

你运行一下试试看。

group1 &#61; LeetCode, length &#61; 8group2 &#61; , length &#61; 0

怎么会这样&#xff1f;我期望的结果是 group1 等于 LeetCod&#xff0c;group2 等于 e 才对啊&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

这是因为 e 仍然属于 \w 能匹配的范畴&#xff0c;正则表达式默认会尽可能多地向后匹配&#xff0c;我们王国将其称之为 贪婪匹配

贪婪匹配&#xff0c;听起来和贪心算法有异曲同工之妙。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

没错&#xff0c;贪婪匹配和贪心算法原理是一致的。与之对应的匹配方式叫做 非贪婪匹配&#xff0c;非贪婪匹配 会在能匹配目标字符串的前提下&#xff0c;尽可能少的向后匹配。

那么&#xff0c;我要怎样指定匹配方式为非贪婪匹配呢&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

也很简单&#xff0c;在需要非贪婪匹配的正则表达式后面加个 ? 即可表示非贪婪匹配。

Pattern pattern &#61; Pattern.compile("(\\w&#43;?)(e*)");Matcher matcher &#61; pattern.matcher("LeetCode");if (matcher.matches()) { String group1 &#61; matcher.group(1); String group2 &#61; matcher.group(2); System.out.println("group1 &#61; " &#43; group1 &#43; ", length &#61; " &#43; group1.length()); System.out.println("group2 &#61; " &#43; group2 &#43; ", length &#61; " &#43; group2.length());}14f2dd10ef9bf9ab8c4baca172554fce.png

运行程序&#xff0c;输出如下&#xff1a;

group1 &#61; LeetCod, length &#61; 7group2 &#61; e, length &#61; 1

这里也用的是 ?&#xff0c;我记得之前 ? 表示的是匹配 0 次或者 1 次&#xff0c;两个符号不会混淆吗&#xff1f;

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

不会混淆的&#xff0c;你仔细想一想就能明白了&#xff0c;如果只有一个字符&#xff0c;那就不存在贪婪不贪婪的问题&#xff0c;如果匹配多次&#xff0c;那么表示非贪婪匹配的 ? 前面必有一个标志匹配次数的符号。所以不会出现混淆。

最后一个问题&#xff0c;为什么这里没有匹配成 group1 等于 L&#xff0c;group2 等于 ee...... 哦我明白了&#xff0c;如果这样匹配的话&#xff0c;字符串 LeetCode 就无法和正则表达式匹配起来。怪不得非贪婪匹配的定义是 在能匹配目标字符串的前提下&#xff0c;尽可能少的向后匹配。

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

就是这个原理&#xff0c;看来你是真的完全明白了。

11最终考验14f2dd10ef9bf9ab8c4baca172554fce.png

天下没有不散的宴席&#xff0c;是时候说再见了。虽然我能教你的&#xff0c;或是说想与你探讨的&#xff0c;还不止这些内容&#xff0c;但授人以鱼不如授人以渔&#xff0c;以后遇到正则相关的问题&#xff0c;还是要靠你自己动脑思考。

这么快就要告别了吗&#xff1f;不知道为什么&#xff0c;竟然还有点舍不得......

ea334c81200a15c3f2fd50b2e0865d80.png14f2dd10ef9bf9ab8c4baca172554fce.png

我最后再出一道题考考你&#xff0c;你就可以从正则王国顺利毕业了。来看下你的题目吧&#xff1a;我们王国有一个人口吃&#xff0c;请你帮忙矫正他。他今天说&#xff1a;肚...子。。好饿........&#xff0c;....早知道.....当.....初...。。。多.....刷.....点。。。力.....扣了.........&#xff01;

ez&#xff0c;只需要用 str.replaceAll(__, __) 就可以解决了&#xff01;

ea334c81200a15c3f2fd50b2e0865d80.png互动话题&#xff1a;嘿&#xff0c;说你呢&#xff01;在留言区写下你的答案吧&#xff01;    a64b1fc1b172a32442a0fd9efeedfbc8.png本文作者&#xff1a;Alpinist Wang编辑&版式&#xff1a;霍霍声明&#xff1a;本文归 “力扣” 版权所有&#xff0c;如需转载请联系。f2dc56cd54637e823dbca4e313946cdb.png



推荐阅读
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 本文介绍如何使用MFC和ADO技术调用SQL Server中的存储过程,以查询指定小区在特定时间段内的通话统计数据。通过用户界面选择小区ID、开始时间和结束时间,系统将计算并展示小时级的通话量、拥塞率及半速率通话比例。 ... [详细]
  • 本文将探讨Java编程语言中对象和类的核心概念,帮助读者更好地理解和应用面向对象编程的思想。通过实际例子和代码演示,我们将揭示如何在Java中定义、创建和使用对象。 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • Qt QTableView 内嵌控件的实现方法
    本文详细介绍了在 Qt QTableView 中嵌入控件的多种方法,包括使用 QItemDelegate、setIndexWidget 和 setIndexWidget 结合布局管理器。每种方法都有其适用场景和优缺点。 ... [详细]
  • 本题要求实现一个函数,用于检查给定的字符串是否为回文。回文是指正向和反向读取都相同的字符串。例如,“XYZYX”和“xyzzyx”都是回文。 ... [详细]
  • 本文深入探讨了SQL数据库中常见的面试问题,包括如何获取自增字段的当前值、防止SQL注入的方法、游标的作用与使用、索引的形式及其优缺点,以及事务和存储过程的概念。通过详细的解答和示例,帮助读者更好地理解和应对这些技术问题。 ... [详细]
  • 本教程详细介绍了如何使用 TensorFlow 2.0 构建和训练多层感知机(MLP)网络,涵盖回归和分类任务。通过具体示例和代码实现,帮助初学者快速掌握 TensorFlow 的核心概念和操作。 ... [详细]
  • 深入理解 .NET 中的中间件
    中间件是插入到应用程序请求处理管道中的组件,用于处理传入的HTTP请求和响应。它在ASP.NET Core中扮演着至关重要的角色,能够灵活地扩展和自定义应用程序的行为。 ... [详细]
  • Python 内存管理机制详解
    本文深入探讨了Python的内存管理机制,涵盖了垃圾回收、引用计数和内存池机制。通过具体示例和专业解释,帮助读者理解Python如何高效地管理和释放内存资源。 ... [详细]
  • 在进行QT交叉编译时,可能会遇到与目标架构不匹配的宏定义问题。例如,当为ARM或MIPS架构编译时,需要确保使用正确的宏(如QT_ARCH_ARM或QT_ARCH_MIPS),而不是默认的QT_ARCH_I386。本文将详细介绍如何正确配置编译环境以避免此类错误。 ... [详细]
  • 目录一、salt-job管理#job存放数据目录#缓存时间设置#Others二、returns模块配置job数据入库#配置returns返回值信息#mysql安全设置#创建模块相关 ... [详细]
  • 本文详细探讨了 org.apache.hadoop.ha.HAServiceTarget 类中的 checkFencingConfigured 方法,包括其功能、应用场景及代码示例。通过实际代码片段,帮助开发者更好地理解和使用该方法。 ... [详细]
author-avatar
歼鸡队队长_512
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有