ZB-047-01正则表达式

Java正则表达式

学习一次终生受益的正则表达式

1
2
3
4
5
6
7
8
[01:52:31] fdasfsdafasdfasf
[02:42:32] fdasfdsafdsa
[03:32:53] gfdgfdgfd
[04:22:43] rewrewrw
[05:12:23] tfhfghfg
[06:02:29] daadfdcvcv
[07:12:56] 322143543
[08:22:11] hgjhjhhj

把上面时间部分替换

以上文本我在 vscode 或者 sublime 里 搜索切换到正则表达式

1
2
3
4
5
6
7
8
# 这样就能匹配
\[\d\d:\d\d:\d\d\]

# 上面写的有点傻
\[\d{2}:\d{2}:\d{2}\]

# 再次简化
\[[\d:]+\]

为什么要学正则表达式

  • 实用、强大、程序员必备
    • 在支持正则表达式的文本编辑器中提高工作效率
    • 用很少代码完成复杂的文本替换工作
  • 难学、难懂、反人类
    • 一开始觉得难是正常的
    • 需要长期的实际才能掌握
  • 如何学习和读懂正则表达式
    • 耐心分析,一点点分析
    • 多用在线工具验证自己的想法

回忆一下通配符…

  • ls *.txt
  • windows资源管理器 *.txt

Java转义字符

  • 为什么需要转义字符?
    • 因为有的字符不可见,无法用一个键来代表
  • 最常用的转义字符
    • \n
    • \r
    • \t
    • \"\'
    • \\
    • \uXXXX
1
2
3
4
5
String a = "abcdef\"\'\n";
// 双引号 单引号 换行
System.out.println(a);
// 两个斜杠
System.out.println("\\\\");

常用元字符

  • ^ 开始位置
  • $ 结束位置
  • . 单个任意字符(不一定包含换行符 java里)
  • \w 单个”word”字符 字母、数字、下划线、汉字
  • \s 单个空白字符
  • \d 单个数字字符
  • \b 单词的开始或结束

这些都是匹配单个的字符

重复

1
2
3
4
5
6
7
8
{n} 正好出现n次
{n,m} 最少n次,最多m次
{n,} 最少n次,最多不限

+ <===> {1,}
? <===> {0,1}

* <===> {0,} 可以可无 不推荐使用容易误导

选择

  • [aeiou] 匹配单个的 a/e/i/o/u
  • [0-9] 单个的数字匹配
  • [A-Z] 单个大写字母
  • [A-Z0-9_] 大写字母或者数字或者下划线
  • Hi|hi等价于[Hh]i Hi 或 hi
1
Pattern p = Pattern.compile("[aeiou\\]]]"); // 匹配 a/e/i/o/u/] 由于在 java世界里 所以 ']'要添加转义 '\]'才可以

元字符/重复/选择 是最重要的三个规则

反义

  • [^aeiou] 匹配单个除了 a/e/i/o/u 之外的字符
  • [^x] 单个非x的字符
  • \W 单个非\w(字母/数字/下划线/汉字)
  • \S 单个非\s(空白)
  • \D 单个非\d(数字)字符
  • \B 非开头/结束位置

一些例子

  • ^\d{5,12}$ 5~12位的数字
  • ^(0|[1-9][0-9]*)$ 0或者非零开头的数字
  • ^(-?\d+)(\.\d+)?$ 小数
  • \d{3}-\d{8}|\d{4}-\d{7} 国内的电话号码(带区号)
  • ^\d{4}-\d{1,2}-\d{1,2} yyyy-MM-dd日期格式
  • \n\s*\r 空白行

Java中的正则表达式

  • String
    • split
    • replaceAll/replaceFirst
    • matches
  • matches
    • 尽量少用或者少编译,因为效率低
    • 每当你需要解析的时候,代码非常昂贵Pattern.compile() 5000行
    • 匹配过程非常昂贵

一些坑,匹配版本号的例子

你非常容易写成这样 a2.split(".") 实际得到的是 []

1
2
3
4
String a2 = "1.2.3";
System.out.println(Arrays.toString(a2.split(".")));
// System.out.println(a2.split("\.")); 转义 \ 需要在此转义
System.out.println(Arrays.toString(a2.split("\\.")));
  • 比如 split() 只要一些函数的形参为 regex 你就要小心点 代表它是一个正则字符串

小心 split / replace / replaceFirst / replaceAll

它们的形参名 也都叫做 regex,非常一不留神就踩坑

为什么说匹配过程非常昂贵

回溯

  • 比如你走迷宫,一条路走不通就要回到起点,走另一条路

推荐这样使用正则表达式

1
2
3
4
public class Demo1 {
// 这样就不会每次都编译
public static final Pattern p = Pattern.compile("你要匹配的正则表达式");
}

将文本的空白字符全部替换

1
"你的字符串".replaceAll("\\s","")

坑爹的 斜杠\

1
2
3
4
5
String s1 = "\\";
System.out.println(s1.matches("\\\\")); // 匹配 \

String s = "\\\\N";
System.out.println(s.matches("\\\\\\\\N")); // 匹配 \\N

匹配一个多行字符串

一些用了正则反而慢的地方

分组和捕获

  • 想要将所有符合正则表达式的文本抓出来处理
    • 实用括号来指定一个被捕获的分组
    • 分组的编号从1开始
    • 分组的编号计算只看左括号
    • (?:)不捕获和分配编号,括号只用于分组或标记优先级

匹配电话号码

\d{3}-\d{8}|\d{4}-\d{7}

现在只能判断符合不符合

1
2
3
Pattern telPattern = Pattern.compile("\\d{3}-\\d{8}|\\d{4}-\\d{7}");
System.out.println(telPattern.matcher("010-12345678").find()); // true
System.out.println(telPattern.matcher("010-1234567").find()); // false

抓去符合结果的分组

1
2
3
4
5
6
7
Pattern telPattern2 = Pattern.compile("(\\d{3})-\\d{8}|\\d{4}-\\d{7}");
Matcher matcher = telPattern2.matcher("010-12345678");

while(matcher.find()){
System.out.println(matcher.group(0)); // 0 代表整个被匹配的文本 结果是 010-12345678
System.out.println(matcher.group(1)); // 1 代表整个被匹配的文本中第一组我们感兴趣的信息 结果是 010
}

多个分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Pattern telPattern3 = Pattern.compile("(\\d{3})-(\\d{8})|(\\d{4})-(\\d{7})");
Matcher matcher = telPattern3.matcher("010-12345678\n5730-1234567\nfff-dfadsfds");

while(matcher.find()){
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
System.out.println(matcher.group(3));
System.out.println(matcher.group(4));
}

运行结果:
010-12345678
010
12345678
null
null
5730-1234567
null
null
5730
1234567

分组编号只看左括号

  • 注意现在的左括号位置
  • 注意现在的左括号位置
  • 注意现在的左括号位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Pattern telPattern3 = Pattern.compile("(\\d{3})-(\\d{8})|(\\d{4})-(\\d{7})"); 
Pattern telPattern3 = Pattern.compile("(\\d{3})-(\\d{8})|(\\d{4}-(\\d{7}))");
Matcher matcher = telPattern3.matcher("010-12345678\n5730-1234567\nfff-dfadsfds");

while(matcher.find()){
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
System.out.println(matcher.group(3));
System.out.println(matcher.group(4));
}

运行结果:
010-12345678
010
12345678
null
null
5730-1234567
null
null
5730-1234567
1234567

写了分组,但是我不想要这个分组 (?:)

  • (\\d{4}) 本来是第三组 matcher.group(3)
  • 如果你这样 (?:\\d{4}) 意思是 别看我有() 但是请不要甩它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Pattern telPattern3 = Pattern.compile("(\\d{3})-(\\d{8})|(?:\\d{4})-(\\d{7})");
Matcher matcher = telPattern3.matcher("010-12345678\n5730-1234567\nfff-dfadsfds");
while(matcher.find()){
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
System.out.println(matcher.group(3));
// System.out.println(matcher.group(4)); 限制忽略了一个分组 所以它没有第四个分组
}

运行结果:
010-12345678
010
12345678
null
5730-1234567
null
null
1234567

实战处理 GCLog