Java 正则表达式
正则表达式, 又称规则表达式. 英语为 Regular Expression, 在代码中常简写为regex、regexp或RE, 正则表达式通常被用来查找、替换、拆分那些符合某种模式(规则)的文本. 简单来说, 正则表达式可以处理文本中具备一定规则的文本, 比如: 电子邮件地址, 身份证号, 手机号码等都是有固定格式的, 我们就可以通过正则表达式来验证这些文本是否符合我们的要求, 在下文中我们也将这些待匹配的文本称之为 输入.
1. 正则表达式基础
既然正则表达式主要用来处理一些特殊的规则的文本, 那么正则表达式自身就要具备描述规则的能力, 一般的正则表达式都是由字符串构成的. 通常情况下正则表达式字符都匹配输入文本中对应的字母, 比如 a 就可以匹配输入文本中的 a, a 就可以匹配输入中的文本. 然而这样简单的规则显然无法满足复杂的匹配规则, 在实际使用中, 我们经常会用到由 "[" 和 "]" 构成的字符类表达式, 方括号内的字符可以构成一类字符用于在输入文本中匹配, 比如: [ab]
可以匹配 a 或者 b, [ab2]
可以匹配 a 或 b 或 2. 下面我们会详细说明这类表达式用法.
1.1 集合规则
在上面的表达式中无论 [ab]
还是 [ab2]
其实都是一种集合, 他把所有方括号内的字符都视为一个集合用合集来匹配字符中的输入.
1.2 区间规则
当我们想匹配全部字母的合集时要怎么写正则呢? 总不是把 a 到 z 全部写到方括号中去吧? 的确不用这样, 因为合集中是允许使用区间的, 如果我想匹配所有字母的话, 正则表达式就应该写作 [a-z]
, 但默认情况下正则是区分大小写再匹配的, 这样我们写表达式要写作 [a-zA-Z]
, 如果要加上所有数字那么就是 [a-zA-Z0-9]
. 那么范围只能匹配英文字母嘛? 不仅仅是这样, 范围也可以使用 \u
前缀支持十六进制字, 比如 [\u4e00-\u9fa5]
可以匹配中文字符.
1.3 否定集合规则
有些时候我们不仅仅是要匹配想要的字符, 还存在只要不匹配的都是想要的字符的情况, 这种时候就可以使用 ^
放在方括号的第一位的表示将匹配不出现在集合中的字符. 比如 [^a-z]
表示只要不是字母 a 到 z 中的都符合匹配条件.
1.4 交集规则
通过在规则中使用 &&
我们可以实现集合的交集, 比如 [a-z&&[def]]
会取 a 到 z 同时满足 [def]
的匹配结果, 即 d, e 或 f 才可以满足匹配结果.
1.5 排除规则
有了以上的这些规则 我们就能组合成排除规则了, [a-z&&[^mn]]
比如这个组合就可以匹配 a 到 z 中所有的字母, 除了 m 和 n.
1.6 通配符
.
在正则表达式中表示可以匹配任意字符, 但是如果出现在 [] 中那么就会匹配 "." 这个符号, 如果想在 [] 以外使用 . 的字面意义匹配, 可以使用 \.
来表示.
1.7 量化
对于重复的规则, 如果我们重复写很多次规则才能满足就未免有点太繁琐了, 正则表达也提供了量化语法. 常见的量化语法如下:
- X? 表示表达式X出现 0 次或 1次, 例如
a?b
可以匹配 b 和 ab. - X* 表示表达式X出现任意次包括 0 次, 例如
a*b
可以匹配 b 或 ab 或 aab 或 aaab 等等. - X+ 表示表达式X至少出现1次, 例如
a+b
可以匹配 ab 或 aab 或 aaab 等等. - X{n} 表示表达式 X 出现 n 次, 其中 n 是一个数字, 例如
[0-9]{3}
可以匹配 000 - 999 任意的三位数字字符, 这里需要注意, 这个表达式无法匹配非3位数字字符 比如 01 或者 1. - X{n,} 表示表达式X至少出现n次, 例如
a{2,}b
可以匹配 aab 或 aaab 或 aaaab 等等. - X{n,m} 表示表达式X至少出现n次, 但不会超过 m 次, 例如
a{2,3}b
可以匹配 aab 和 aaab.
1.8 常见的预定义字符类
正则表达式中预定义了很多字符类, 来简化表达式的书写, 常见的预定义表达式如下表:
. | 匹配任意字符(不一定会匹配行终止符) |
\d | 数字:[0-9] |
\D | 除了数字:[^0-9] |
\s | 空白字符:[ \t\n\x0B\f\r] |
\S | 非空白字符:[^\s] |
\w | 单词字符:[a-zA-Z_0-9] |
\W | 非单词字符:[^\w] |
正则表达式中还有很多其他的结构, 关于更多的匹配规则参见 Pattern 类的 正则表达式结构总结表部分.
2. Java 中正则表达式的实现.
很多编程语言都支持正则表达式, Java 当然也不例外. Java 中主要由两个类来实现对正则表达式的支持 Pattern 和 Matcher . Pattern
是正则的模式, 他是通过编译正则表达式字符串的来的 转换成模式的目的在于避免重复的解析源正则表达式, 提高使用效率. Matcher
则主要用于正则表达式的匹配, 它可以避免重复按表达式解析输入字符, 这样能实现更高效的匹配效率.
一般性的使用 Pattern 和 Matcher 的流程, 如以下代码所示:
Pattern pattern = Pattern.compile("\\d{2}[a-z]{2}"); Matcher matcher = pattern.matcher("21yi");
首先通过 Pattern 的静态方法 compile() 将正则表达式字符串编译成正则模式, 然后通过模式对象的 matcher()
方法返回一个 Macther 对象进行匹配操作, 当然模式对象自身也有很多操作方法比如拆分输入字符序列的 split() 方法等. 下面我们将会介绍 Pattern
和 Matcher
常见的方法和使用示例:
2.1 Pattern 的常用方法
2.1.1 compile()
编译正则表达式字符串为模式, 这是一个静态方法, 该方法返回一个 Pattern
对象. 后续的 Pattern
对象方法调用, 都要通过这个对象实现, 使用示例如下:
Pattern pattern = Pattern.compile("\\d{2}[a-z]{2}");
2.1.2 matcher()
使用当前模式成成一个正则表达式匹配器, 使用示例如下:
Pattern pattern = Pattern.compile("\\d{2}[a-z]{2}"); Matcher matcher = pattern.matcher("21yi");
2.1.3 matches()
用给定的正则表达式判断是否与输入的字符序列匹配, 这是一个静态方法, 因为无法重复使用编译过的模式, 所以性能不高, 适合一次性的匹配判断, 使用示例如下:
String regex = "(?i)[a-z0-9 ]+"; String input = "21YI haha"; System.out.println("正则表达式是否与输入匹配? " + Pattern.matches(regex, input));
2.1.4 split()
使用正则表达式拆分输入的字符序列成字符串数组, 这个方法会将输入的字符串序列拆分成字符串数组, 如果输入序列中没有匹配正则字符, 那么会返回包含完整输入字符序列的字符串形式, 使用示例如下:
String regex = "\\d"; String input = "Hello, I'm 21yi, 1 year old."; Pattern pattern = Pattern.compile(regex); String[] results = pattern.split(input);
2.1.5 splitAsStream()
使用正则表达式拆分输入的字符序列为字符串流, 这是Java 1.8中增加的方法, 此方法返回的流包含输入序列的每个子字符串, 使用示例如下:
String regex = "\\d"; String input = "Hello, I'm 21yi, 1 year old."; Pattern pattern = Pattern.compile(regex); Stream<String> stream = pattern.splitAsStream(input);
2.1.6 所有列表方法/属性列表
除了上述的方法外, 下表还提供了其他方法/属性的简介, 打开每个方法/属性的链接都有对应的详细说明和示例供参考:
序号 | 类型 | 名称 | 说明 |
---|---|---|---|
1 | 方法 | Pattern.asPredicate() |
通过当前正则表达式模式( |
2 | 方法 | Pattern.compile() |
使用给定的正则表达式编译成相应的模式( |
3 | 方法 | Pattern.flags() |
获取正则表达式模式匹配( |
4 | 方法 | Pattern.matcher() |
生成一个用给定输入与正则表达式的模式( |
5 | 方法 | Pattern.matches() |
判断使用给定的正则表达式判断是否与输入的字符序列匹配 |
6 | 方法 | Pattern.pattern() |
返回用于编译当前模式( |
7 | 方法 | Pattern.quote() |
将字符串里的正则表达式转换成字面意义的字符串 |
8 | 方法 | Pattern.split() |
用正则表达式将给定的输入拆分成字符串数组, 如果提供了 limit 参数, 则限定拆分的次数. |
9 | 方法 | Pattern.splitAsStream() |
用正则表达式将给定的输入拆分成字符串流. |
10 | 方法 | Pattern.toString() |
获取当前正则表达式模式( |
11 | 类 | Pattern |
Pattern, 正则表达式的编译表示. 也就是说在 Java 中 Pattern 是编译过的正则表达式或者正则表达式的编译产物, 而不是原始正则表达式. 下文中出现的加粗体模式均指代 Pattern, 未加粗的模式指代 mode. |
12 | 属性 | Pattern.CANON_EQ |
启用正则表达式规范等价模式 |
13 | 属性 | Pattern.CASE_INSENSITIVE |
开启正则表达式忽略大小写(大小写不敏感)匹配模式 |
14 | 属性 | Pattern.COMMENTS |
启用正则表达式的注释模式(mode). 允许在模式( |
15 | 属性 | Pattern.DOTALL |
启用正则表达式 dotall 模式 |
16 | 属性 | Pattern.LITERAL |
启用正则表达式模式(Pattern)的字面解析. |
17 | 属性 | Pattern.MULTILINE |
允许正则表达式多行模式 |
18 | 属性 | Pattern.UNICODE_CASE |
启用正则表达式 Unicode 大小写感知折叠. 配合 |
19 | 属性 | Pattern.UNICODE_CHARACTER_CLASS |
启用正则表达式 Unicode 版本的预定义字符类和 POSIX 字符类. |
20 | 属性 | Pattern.UNIX_LINES |
启用正则表达式 UNIX 行模式 |
2.2 Matcher 的常用方法
Matcher
通常用于匹配, 它常见的匹配方法有:
find()
尽可能在找到正则表达式的匹配项lookingAt()
只看输入的开头是否匹配正则表达式matches()
是否整个输入完全匹配正则表达式
这些方法自身会通过返回一个布尔值来表明是否匹配上了, 每次匹配都会记录匹配的位置, 匹配完成后不会自动重置匹配的位置, 因此需要调用 reset()
方法进行重置. 如果想进一步取得匹配的结果可以使用 group()
方法. 匹配是存在具体使用示例请参照以下:
2.2.1 find()
尝试找到与表达式匹配的输入序列的下一个子序列, 当提供 start 参数时会重置该匹配器, 然后尝试从指定的索引开始查找. find() 方法不需要输入序列完全匹配正则表达式, 而是尝试到序列中找到满足表达式的内容, find()
方法存在另一种带起始位置的形式, 使用这种形式的情况下会自动重置匹配的结果, 并从输入指定的位开始匹配, 使用示例如下:
Pattern pattern = Pattern.compile("\\d{2}[a-z]{2}"); Matcher matcher = pattern.matcher("21yi 31k 48aa"); //符合正则的只有两位 System.out.println("表达式是否找到符合? " + matcher.find() + ", 起始位置是: " + matcher.start());
//重置匹配并从指定索引位开始查找 System.out.println("表达式是否找到符合? " + matcher.find(5) + ", 起始位置是: " + matcher.start());
2.2.2 group()
按要求获取上一次中匹配项的输入的子序列, 这个序列可以是 find()
, lookingAt()
或 matches()
的匹配后的结果. 同时本方法允许提供以索引或分组名获取指定分组的用法. 使用示例如下:
Pattern emailPattern = Pattern.compile("(?<user>[^@ ]+)@(?<domain>[a-z0-9]+\\.[a-z]{2,})"); Matcher matcher = emailPattern.matcher("email xxx@21yi.com not match"); if(matcher.find()) { System.out.println("匹配到的字符串: " + matcher.group()); System.out.println("按捕获组 1 匹配到的字符串: " + matcher.group(1)); System.out.println("按捕获组 2 匹配到的字符串: " + matcher.group(2)); System.out.println("捕获组名 user 匹配到的字符串: " + matcher.group("user")); System.out.println("捕获组名 domain 匹配到的字符串: " + matcher.group("domain")); }
2.2.3 lookingAt()
尝试将输入序列(从区间的开头开始)与正则表达式进行匹配. 本方法仅会匹配输入的开头是否与正则表达式匹配. 使用示例如下:
Pattern pattern = Pattern.compile("\\d{2}[a-z]{2}"); Matcher matcher = pattern.matcher("21yi 31k 48aa"); System.out.println("字符串的开头是否符合表达式? " + matcher.lookingAt()); matcher = pattern.matcher("31k 21yi"); System.out.println("字符串的开头是否符合表达式? " + matcher.lookingAt());
2.2.4 matches()
尝试将整个区间与表达式匹配. 当然本方法也允许使用 group()
获取匹配的结果. 使用示例如下:
Pattern pattern = Pattern.compile("\\d{2}[a-z]{2}"); Matcher matcher = pattern.matcher("21yi 31k 48aa"); System.out.println("字符序列是否完全符合表达式? " + matcher.matches()); matcher = pattern.matcher("21yi 48aa"); System.out.println("字符序列是否完全符合表达式? " + matcher.matches()); matcher = pattern.matcher("21yi"); System.out.println("字符序列是否完全符合表达式? " + matcher.matches());
2.2.5 regoin()
设置当前匹配器的区间(region)限制. 所谓区间就是输入中参与正则匹配的区间, 区间以外的字符将在匹配时被忽略. 调用本方法会重置匹配器, 也就是恢复成未匹配的状态. 使用示例如下:
Pattern emailPattern = Pattern.compile("(?<user>[^@ ]+)@(?<domain>[a-z0-9]+\\.[a-z]{2,})"); String email = "xxx@21yi.com"; Matcher matcher = emailPattern.matcher(email); //region 选择的输入 x@21yi.co matcher.region(2, email.length() - 1); if(matcher.find()) { System.out.println("捕获组名 user 匹配到的字符串: " + matcher.group("user")); System.out.println("捕获组名 domain 匹配到的字符串: " + matcher.group("domain")); }
2.2.6 replaceAll()
用给定的替换字符串/替换函数替换与正则匹配的输入序列的所有子序列.使用示例如下:
Pattern pattern = Pattern.compile("(\\d{3,4}-)\\d{4}(\\d{4})"); String tel = "010-88888888"; Matcher telMasker = pattern.matcher(tel); System.out.println(tel + " 经过掩盖, 得到: " + telMasker.replaceAll("$1****$2"));
2.2.7 replaceFirst()
用给定的替换字符串/替换函数替换与正则匹配的输入序列的第一个子序列.使用示例如下:
Pattern pattern = Pattern.compile("(\\d{2})"); String no = "12345678"; Matcher noMask = pattern.matcher(no); System.out.println(no + " 经过字符串替换, 得到: " + noMask.replaceFirst("**"));
2.2.8 reset()
重置或使用新的字符序列重置当前匹配器(Matcher
). 本方法同时允许用于匹配的输入序列. 使用示例如下:
Pattern pattern = Pattern.compile("\\d{2}[a-z]{2}"); Matcher matcher = pattern.matcher("21yi 31k 48aa"); System.out.println("是否能找到匹配? " + matcher.find()); //跳过一次查找 matcher.find(); if(!matcher.find()) { System.out.println("已经找完了."); } matcher.reset(); if(matcher.find()) { System.out.println("经过 reset 又能找到了."); } matcher.reset("abcd efgh"); if(!matcher.find()) { System.out.println("更换了输入字符后, 彻底找不到匹配项了."); }
2.2.x 所有列表方法/属性列表
除了上述的方法外, 下表还提供了其他方法/属性的简介, 打开每个方法/属性的链接都有对应的详细说明和示例供参考:
序号 | 类型 | 名称 | 说明 |
---|---|---|---|
1 | 方法 | Matcher.appendReplacement() |
实现一个非终端的追替 (append-and-replace)步骤. |
2 | 方法 | Matcher.appendTail() |
实现一个非终端的追替 (append-and-replace)步骤. |
3 | 方法 | Matcher.end() |
按要求获取上一个匹配项的结束索引位. |
4 | 方法 | Matcher.find() |
尝试找到与表达式匹配的输入序列的下一个子序列, 当提供 start 参数时会重置该匹配器, 然后尝试从指定的索引开始查找. |
5 | 方法 | Matcher.group() |
按要求获取上一个匹配项的输入子序列. |
6 | 方法 | Matcher.groupCount() |
获取该匹配器表达式中的捕获组数. |
7 | 方法 | Matcher.hasAnchoringBounds() |
查询当前匹配器是使用区间边界的锚定 |
8 | 方法 | Matcher.hasTransparentBounds() |
查询当前匹配器是否使用区间透明边界. |
9 | 方法 | Matcher.hitEnd() |
判断当前匹配器是否已经在最后一次匹配操作中命中了输入的结尾 |
10 | 方法 | Matcher.lookingAt() |
尝试将输入序列(从区间的开头开始)与正则表达式进行匹配. |
11 | 方法 | Matcher.matches() |
尝试将整个区间与表达式匹配 |
12 | 方法 | Matcher.pattern() |
返回由此匹配器所解释的正则表达式 |
13 | 方法 | Matcher.quoteReplacement() |
获取指定字符串的字面替换字符串 |
14 | 方法 | Matcher.region() |
设置当前匹配器的区间(region)限制 |
15 | 方法 | Matcher.regionEnd() |
报告匹配器区间的结束索引位(不包含, 即区间结束索引为返回值 - 1) |
16 | 方法 | Matcher.regionStart() |
报告匹配器区间的索引起始位(不包含, 即区间起始索引为返回值) |
17 | 方法 | Matcher.replaceAll() |
用给定的替换字符串/替换函数替换与正则匹配的输入序列的所有子序列. |
18 | 方法 | Matcher.replaceFirst() |
用给定的替换字符串/替换函数替换与正则匹配的输入序列的第一个子序列. |
19 | 方法 | Matcher.requireEnd() |
判断能否将更多输入可以将正匹配更改为负匹配 |
20 | 方法 | Matcher.reset() |
重置或使用新的字符序列重置当前匹配器( |
21 | 方法 | Matcher.results() |
获取输入序列中匹配正则的每个子序列的的结果的顺序流. |
22 | 方法 | Matcher.start() |
按要求获取上一个匹配项的起始索引位. |
23 | 方法 | Matcher.toMatchResult() |
获取当前匹配器的匹配状态为结果的 |
24 | 方法 | Matcher.toString() |
获取匹配器的表征字符串 |
25 | 方法 | Matcher.useAnchoringBounds() |
开启/关闭此匹配器的区间边界的锚定. |
26 | 方法 | Matcher.usePattern() |
更改此匹配器用于查找匹配项的正则表达式. |
27 | 方法 | Matcher.useTransparentBounds() |
指示当前匹配器使用区间透明/不透明边界. |
28 | 类 | Matcher |
正则匹配器( |