正则总结

December 20th, 2019 by JasonLe's Tech Leave a reply »

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

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

\d\d 就能匹配两个数字,\d\d\d 能匹配三个数字,需要匹配几个数字就写几次就行了。

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

在 \d 后面打上花括号 {},{n} 表示匹配 n 次。\d{10000} 就表示匹配 10000 个数字。如果要匹配 n ~ m 次,用 {n,m} 即可,如果要匹配至少 n 次,用 {n,} 即可。需要注意 , 后不能有空格。

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

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

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

将字母换成大写,就表示相反的意思。用 \d 你可以匹配一个数字,\D 则表示匹配一个非数字。类似地,\W 可以匹配 \w 不能匹配的字符,\S 可以匹配 \s 不能匹配的字符。

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

我们对某些位置的字符没有要求,仅需要占个位置即可。这时候我们就可以用 . 字符。我们对匹配的次数没有要求,匹配任意次均可,这时,我们就可以用 * 字符。出现了 0 次,* 是指 可以匹配任意次,包括 0 次。也就是说,* 等价于 {0,}

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

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

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

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

如果我们规定电话号码不能以 0 开头,正则匹配规则是 [123456789]\d{10}。

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

考虑一个实际需求,有许许多多以下格式的字符串,你需要用正则表达式匹配出其姓名和年龄。
Name:Aurora Age:18
其中还夹杂着一些无关紧要的数据
Name:Bob Age:20
错误的数据有着各种各样错误的格式
Name:Cassin Age:22

观察字符串的规则,只需要用 Name:\w+\s*Age:\d{1,3} 就能匹配了。

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

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
}

只要用 () 将需要取值的地方括起来,传给 Pattern 对象,再用 Pattern 对象匹配后获得的 Matcher 对象来取值就行了。每个匹配的值将会按照顺序保存在 Matcher 对象的 group 中。用 () 把 \\w+ 和 \\d{1,3} 分别括起来了,判断 Pattern 对象与字符串是否匹配的方法是 Matcher.matches(),如果匹配成功,这个函数将返回 true,如果匹配失败,则返回 false。

group(0) 被用来保存整个匹配的字符串了。

考虑一个实际场景:你有一个让用户输入标签的输入框,用户可以输入多个标签。可是你并没有提示用户,标签之前用什么间隔符号隔开。
二分,回溯,递归,分治
搜索;查找;旋转;遍历
数论 图论 逻辑 概率

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

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

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

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

输出为:

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

贪婪匹配和贪心算法原理是一致的。与之对应的匹配方式叫做 非贪婪匹配,非贪婪匹配 会在能匹配目标字符串的前提下,尽可能少的向后匹配。
在需要非贪婪匹配的正则表达式后面加个 ? 即可表示非贪婪匹配。

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());
}

str.replaceAll(“[\\.。]+”, “”) 可以匹配所有的. 和 。

消除str.replaceAll(“[^0-9a-zA-Z]”, “”)消除所有的非大小写字母和数字的非法字符。