java如何判断一个字符串是不是纯数字正则表达式_哈哈哈哈这个好玩!正则表达式王国奇遇记......

公众号关注 “GitHubDaily”
设为 “星标”,带你了解技术圈内新鲜事!
e75ce80616ef20b22858908d5acb2bf5.png 01 初来乍到 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

02 牛刀小试 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

4469e40a64dfee3e1b13175617cab5f0.png
public static boolean isValidPhoneNumber(String number) {    // 判断是否是 11 位    if (number.length() != 11) return false;    // 判断每一位是否全为数字    for (int i = 0; i < number.length(); i++) {        if (number.charAt(i) < '0' || number.charAt(i) > '9') return false;    }    return true;}
19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

啊?如此简洁的实现,正则强者竟恐怖如斯!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

03 初窥门径 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

那么,00\d 可以匹配 0066 吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

不能,\d 只能匹配单个数字。

那我要怎么才能匹配多个数字呢?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

原来如此,现在我能完全看懂刚才写的匹配电话号码的例子了!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

趁热打铁,如果要匹配 n ~ m 次,用 {n,m} 即可,如果要匹配至少 n 次,用 {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

按照这个写法,如果要匹配最多 m 次,是不是用 {,m} ?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

啊,原来如此,我想多了。

4469e40a64dfee3e1b13175617cab5f0.png 04 小有所成 19b6aa2a055a8e695c7f49678a3f5c46.png

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

  • 空格键打出来的空格

  • Tab 键打出来的空格

  • 回车键打出来的空格

Tab 键打出来的空格和回车键打出来的空格?是指 \t\n 吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

完全正确。

我明白了,我来测试一下。

4469e40a64dfee3e1b13175617cab5f0.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}")); // 输出为 true
19b6aa2a055a8e695c7f49678a3f5c46.png

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

05 更进一步 19b6aa2a055a8e695c7f49678a3f5c46.png

记住上面三个规则之后,你还可以顺带获得几个新的规则。因为正则国度规定:将字母换成大写,就表示相反的意思。用 \d 你可以匹配一个数字,\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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

06 渐入佳境 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

那是不是也可以理解为:. 可以匹配任意字符。

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

我有疑问,为什么第三个表达式也会输出 true 呢?明明没有出现数字啊?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

那就可以用 + 匹配,+ 表示 至少匹配一次。它等价于 {1,}

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

哈哈,看来设计者也发现了这个需求更常用。平时 + 号比 * 号用得多吧!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

. 匹配任意字符;* 匹配任意次,包括 0 次;+ 号匹配至少 1 次,? 匹配 0 次或 1 次。我记住了!

4469e40a64dfee3e1b13175617cab5f0.png 07 心浮气躁

我感觉我已经掌握了够多的匹配规则,足以应付所有的字符串匹配场景了!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

不能以 0 开头,那就不能用 \d{11}了,这......

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

呃,还差一点......快别取笑我了,快告诉我这个要用什么新的规则吧!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

只能用于数字吗?可以用在字母身上吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

当然可以,比如 [a-g] 表示 [abcdefg][U-Z] 表示 [UVWXYZ]

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

不必,你还可以这么写:[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

这可真是太方便了!如果是 0~1,8~9 可以这样组合吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

那样的话,你写 [0189] 不是更简洁吗?

我想学习(装 X)。

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

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

所以说范围就是 的简写,对吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

原来如此,我懂了。现在还有什么规则我没有学到的吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

高手秘籍!听着都让人激动啊,快讲讲!

4469e40a64dfee3e1b13175617cab5f0.png 08 探囊取物 19b6aa2a055a8e695c7f49678a3f5c46.png

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

  • Name:Aurora        Age:18

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

  • Name:Bob            Age:20

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

  • Name:Cassin        Age:22

  • ...

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

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

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

19b6aa2a055a8e695c7f49678a3f5c46.png

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

这里是不是写错了,为什么 group 是从下标 1 开始取值的,计算机不都从 0 开始数吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

源码中调用了 Pattern.matches 方法,我们再跟进去。

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

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

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

4469e40a64dfee3e1b13175617cab5f0.png
  • 二分,回溯,递归,分治

  • 搜索;查找;旋转;遍历

  • 数论 图论 逻辑 概率

19b6aa2a055a8e695c7f49678a3f5c46.png

那你是怎么解决的呢?

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

4469e40a64dfee3e1b13175617cab5f0.png
public static String[] splitTabs(String tabs) {    if(tabs.split(",").length == 4) return tabs.split(",");    if(tabs.split(";").length == 4) return tabs.split(";");    if(tabs.split(" ").length == 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("数论 图论 逻辑 概率")));}

输出为:

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

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

String.split 函数不就是用来分割字符串的吗?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

啊?但我之前没写过正则表达式,分割出来也没出错啊!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

原来如此。那么我应该怎么做呢?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

输出为:

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

原来 split 函数这么强大,我以后不会犯这种错误了!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

用的是 replaceAll 函数,这个函数不会也是传的正则表达式吧!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

输出为:

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

果然是 移花接木,模糊匹配比精确匹配效率高多了!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

输出为:

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

完全正确。

10 蓦然回首 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

什么?还有学问?我感觉我已经学完了啊!还有什么学问,一并给我讲了吧!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

那你看这样一道题:给你一些字符串,统计其末尾 e 的个数

  • LeetCode

  • LeetCodeeee

  • LeetCodeee

看起来并不难,用 (\w+)(e*) 匹配,再取 group(2) 判断即可。

4469e40a64dfee3e1b13175617cab5f0.png
Pattern pattern = Pattern.compile("(\\w+)(e*)");Matcher matcher = pattern.matcher("LeetCode");if (matcher.matches()) {    String group1 = matcher.group(1);    String group2 = matcher.group(2);    System.out.println("group1 = " + group1 + ", length = " + group1.length());    System.out.println("group2 = " + group2 + ", length = " + group2.length());}
19b6aa2a055a8e695c7f49678a3f5c46.png

你运行一下试试看。

group1 = LeetCode, length = 8group2 = , length = 0

怎么会这样?我期望的结果是 group1 等于 LeetCod,group2 等于 e 才对啊!

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

贪婪匹配,听起来和贪心算法有异曲同工之妙。

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

那么,我要怎样指定匹配方式为非贪婪匹配呢?

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

Pattern pattern = Pattern.compile("(\\w+?)(e*)");Matcher matcher = pattern.matcher("LeetCode");if (matcher.matches()) {    String group1 = matcher.group(1);    String group2 = matcher.group(2);    System.out.println("group1 = " + group1 + ", length = " + group1.length());    System.out.println("group2 = " + group2 + ", length = " + group2.length());}
19b6aa2a055a8e695c7f49678a3f5c46.png

运行程序,输出如下:

group1 = LeetCod, length = 7group2 = e, length = 1

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

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

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

就是这个原理,看来你是真的完全明白了。

11 最终考验 19b6aa2a055a8e695c7f49678a3f5c46.png

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

这么快就要告别了吗?不知道为什么,竟然还有点舍不得......

4469e40a64dfee3e1b13175617cab5f0.png 19b6aa2a055a8e695c7f49678a3f5c46.png

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

ez,只需要用 str.replaceAll(__, __) 就可以解决了!

4469e40a64dfee3e1b13175617cab5f0.png 互动话题: 嘿,说你呢!在留言区写下你的答案吧!     0f3a80e5458b60275f453b5f3df56fbb.png 本文作者:Alpinist Wang 编辑&版式:霍霍 声明:本文归 “力扣” 版权所有,如需转载请联系。 9c1cf89704c9a185909b5b6f006038cc.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值