正则表达式
1. 正则是啥?
正则表达式: 定义一个搜索模式的字符串. 正则表达式可以用于搜索, 编辑和操作文本.
正则对文本的分析或修改过程为: 首先正则表达式应用的是文本字符串(text/string), 它会以定义的模式从左到右匹配文本, 每个源字符只匹配一次.
请说人话:
正则就是用有限的符号, 表达无限的序列!
说白了人就是懒, 想少写多做!
正则表达式 | 匹配 |
---|---|
this is text | 精确匹配字符串 "this is text" |
this\s+is\s+text | 匹配单词 "this" 后跟一个或多个空格字符,后跟词 "is" 后跟一个或多个空格字符,后跟词 "text" |
^\d+(\.\d+)? | ^ 定义模式必须匹配字符串的开始,d+ 匹配一个或多个数字,? 表明小括号内的语句是可选的,\. 匹配 ".",小括号表示分组。例如匹配:"5"、"1.5" 和 "2.21" |
2. 正则表达式的规则
2.1 常见匹配符号
正则表达式 | 描述 |
---|---|
. | 匹配所有单个字符,除了换行符(Linux 中换行是 n,Windows 中换行是 rn) |
^regex | 正则必须匹配字符串开头 |
regex$ | 正则必须匹配字符串结尾 |
[abc] | 复选集定义,匹配字母 a 或 b 或 c |
[abc][vz] | 复选集定义,匹配字母 a 或 b 或 c,后面跟着 v 或 z |
[^abc] | 当插入符 ^ 在中括号中以第一个字符开始显示,则表示否定模式。此模式匹配所有字符,除了 a 或 b 或 c |
[a-d1-7] | 范围匹配,匹配字母 a 到 d 和数字从 1 到 7 之间,但不匹配 d1 |
XZ | 匹配 X 后直接跟着 Z |
X|Z | 匹配 X 或 Z |
这些符号必须要记住
2.2 元字符
元字符是一个预定义的字符.
正则表达式 | 描述 |
---|---|
\d | 匹配一个数字,是 [0-9] 的简写 |
\D | 匹配一个非数字,是 [^0-9] 的简写 |
\s | 匹配一个空格,是 [ \t\n\x0b\r\f] 的简写 |
\S | 匹配一个非空格 |
\w | 匹配一个单字字符(大小写字母、数字、下划线),是 [a-zA-Z_0-9] 的简写 |
\W | 匹配一个非单字字符(除了大小写字母、数字、下划线之外的字符),等同于 [^\w] |
2.3 限定字符
限定符定义了一个元素可以发生的频率.
正则表达式 | 描述 | 举例 |
* | 匹配 >=0 个,是 {0,} 的简写 | X* 表示匹配零个或多个字母 X,.* 表示匹配任何字符串 |
+ | 匹配 >=1 个,是 {1,} 的简写 | X+ 表示匹配一个或多个字母 X |
? | 匹配 1 个或 0 个,是 {0,1} 的简写 | X? 表示匹配 0 个或 1 个字母 X |
{X} | 只匹配 X 个字符 | \d{3} 表示匹配 3 个数字,.{10} 表示匹配任何长度是 10 的字符串 |
{X,Y} | 匹配 >=X 且 <=Y 个 | \d{1,4} 表示匹配至少 1 个最多 4 个数字 |
*? | 如果 ? 是限定符 * 或 + 或 ? 或 {} 后面的第一个字符,那么表示 < strong ow="60" oh="16">非贪婪模式 (尽可能少的匹配字符),而不是默认的 < strong ow="312" oh="40"> 贪婪模式 |
2.4 分组和反向引用
小括号 () 可以达到对正则表达式进行分组的效果.
模式分组后会在正则表达式中创建反向引用. 反向引用会保存匹配模式分组的字符串片断, 这使得我们可以获取并使用这个字符串片断.
在以正则表达式替换字符串的语法中, 是通过 $ 来引用分组的反向引用,$0 是匹配完整模式的字符串(注意在 JavaScript 中是用 $& 表示);$1 是第一个分组的反向引用;$2 是第二个分组的反向引用, 以此类推.
- package com.wuxianjiezh.demo.regex;
- public class RegexTest {
- public static void main(String[] args) {
- // 去除单词与 , 和 . 之间的空格
- String Str = "Hello , World .";
- String pattern = "(\\w)(\\s+)([.,])";
- // $0 匹配 `(\w)(\s+)([.,])` 结果为 `o 空格,` 和 `d 空格.`
- // $1 匹配 `(\w)` 结果为 `o` 和 `d`
- // $2 匹配 `(\s+)` 结果为 ` 空格 ` 和 ` 空格 `
- // $3 匹配 `([.,])` 结果为 `,` 和 `.`
- System.out.println(Str.replaceAll(pattern, "$1$3")); // Hello, World.
- }
- }
上面的例子中, 我们使用了 [.] 来匹配普通字符 . 而不需要使用 [\\.]. 因为正则对于 [] 中的 ., 会自动处理为 [\.], 即普通字符 . 进行匹配.
2.4.1 仅分组但无反向引用
当我们在小括号 () 内的模式开头加入 ?:, 那么表示这个模式仅分组, 但不创建反向引用.
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String str = "img.jpg";
- // 分组且创建反向引用
- Pattern pattern = Pattern.compile("(jpg|png)");
- Matcher matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group());//$0
- System.out.println(matcher.group(1));//$1
- }
- }
- }
运行结果为:
jpg
jpg
若源码改为:
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String str = "img.jpg";
- // 分组但不创建反向引用
- Pattern pattern = Pattern.compile("(?:jpg|png)");
- Matcher matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group());
- System.out.println(matcher.group(1));
- }
- }
- }
运行结果为:
jpg Exception in thread "main" java.lang.IndexOutOfBoundsException: No group 1 at java.util.regex.Matcher.group(Matcher.java:538) at com.wuxianjiezh.regex.RegexTest.main(RegexTest.java:15)
2.4.2 分组的反向引用副本
Java 中可以在小括号中使用 ? 将小括号中匹配的内容保存为一个名字为 name 的副本.
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String str = "@wxj 你好啊";
- Pattern pattern = Pattern.compile("@(?<first>\\w+\\s)"); // 保存一个副本
- Matcher matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group());
- System.out.println(matcher.group(1));
- System.out.println(matcher.group("first"));
- }
- }
- }
运行结果为:
@wxj wxj wxj
2.5 否定先行断言(Negative lookahead)
我们可以创建否定先行断言模式的匹配, 即某个字符串后面不包含另一个字符串的匹配模式.
否定先行断言模式通过 (?!pattern) 定义. 比如, 我们匹配后面不是跟着 "b" 的 "a":
a(?!b)
2.6 指定正则表达式的模式
可以在正则的开头指定模式修饰符.
(?i) 使正则忽略大小写.
(?s) 表示单行模式 ("single line mode") 使正则的 . 匹配所有字符, 包括换行符.
(?m) 表示多行模式("multi-line mode"), 使正则的 ^ 和 $ 匹配字符串中每行的开始和结束.
2.7 Java 中的反斜杠
反斜杠 \ 在 Java 中表示转义字符, 这意味着 \ 在 Java 拥有预定义的含义.
这里例举两个特别重要的用法:
在匹配 . 或 { 或 [ 或 ( 或 ? 或 $ 或 ^ 或 * 这些特殊字符时, 需要在前面加上 \\, 比如匹配 . 时, Java 中要写为 \\., 但对于正则表达式来说就是 \..
在匹配 \ 时, Java 中要写为 \\\\, 但对于正则表达式来说就是 \\.
注意: Java 中的正则表达式字符串有两层含义, 首先 Java 字符串转义出符合正则表达式语法的字符串, 然后再由转义后的正则表达式进行模式匹配.
2.8 易错点示例
[jpg|png] 代表匹配 j 或 p 或 g 或 p 或 n 或 g 中的任意一个字符.
(jpg|png) 代表匹配 jpg 或 png.
3. 在字符串中使用正则表达式
3.1 内置的字符串正则处理方法
在 Java 中有四个内置的运行正则表达式的方法, 分别是 matches(),split()),replaceFirst(),replaceAll(). 注意 replace() 方法不支持正则表达式.
方法 | 描述 |
---|---|
s.matches("regex") | 当仅且当正则匹配整个字符串时返回 true |
s.split("regex") | 按匹配的正则表达式切片字符串 |
s.replaceFirst("regex", "replacement") | 替换首次匹配的字符串片段 |
s.replaceAll("regex", "replacement") | 替换所有匹配的字符 |
- package com.wuxianjiezh.regex;
- public class RegexTest {
- public static void main(String[] args) {
- System.out.println("wxj".matches("wxj"));
- System.out.println("----------");
- String[] array = "w x j".split("\\s");
- for (String item : array) {
- System.out.println(item);
- }
- System.out.println("----------");
- System.out.println("w x j".replaceFirst("\\s", "-"));
- System.out.println("----------");
- System.out.println("w x j".replaceAll("\\s", "-"));
- }
- }
运行结果
- true
- ----------
- w
- x
- j
- ----------
- w-x j
- ----------
w-x-j
4. 模式和匹配
Java 中使用正则表达式需要用到两个类, 分别为
java.util.regex.Pattern
和
- java.util.regex.Matcher
- .
第一步, 通过正则表达式创建模式对象 Pattern.
第二步, 通过模式对象 Pattern, 根据指定字符串创建匹配对象 Matcher.
第三步, 通过匹配对象 Matcher, 根据正则表达式操作字符串.
非常好的示例
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String text = "Hello Regex!";
- Pattern pattern = Pattern.compile("\\w+");
- // Java 中忽略大小写, 有两种写法:
- // Pattern pattern = Pattern.compile("\\w+", Pattern.CASE_INSENSITIVE);
- // Pattern pattern = Pattern.compile("(?i)\\w+"); // 推荐写法
- Matcher matcher = pattern.matcher(text);
- // 遍例所有匹配的序列
- while (matcher.find()) {
- System.out.print("Start index:" + matcher.start());
- System.out.print("End index:" + matcher.end() + " ");
- System.out.println(matcher.group());
- }
- // 创建第两个模式, 将空格替换为 tab
- Pattern replace = Pattern.compile("\\s+");
- Matcher matcher2 = replace.matcher(text);
- System.out.println(matcher2.replaceAll("\t"));
- }
- }
运行结果:
- Start index: 0 End index: 5 Hello
- Start index: 6 End index: 11 Regex
Hello Regex!
5. 若干个常用例子
5.1 中文的匹配
[\u4e00-\u9fa5]+ 代表匹配中文字.
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String str = "閑人到人间";
- Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]+");
- Matcher matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group());
- }
- }
- }
运行结果:
閑人到人间
5.2 数字范围的匹配
比如, 匹配 1990 到 2017.
** 注意:** 这里有个新手易范的错误, 就是正则 [1990-2017], 实际这个正则只匹配 0 或 1 或 2 或
7
或
9
中的任一个字符.
正则表达式匹配数字范围时, 首先要确定最大值与最小值, 最后写中间值.
正确的匹配方式:
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String str = "1990\n2010\n2017";
- // 这里应用了 (?m) 的多行匹配模式, 只为方便我们测试输出
- // "^1990$|^199[1-9]$|^20[0-1][0-6]$|^2017$" 为判断 1990-2017 正确的正则表达式
- Pattern pattern = Pattern.compile("(?m)^1990$|^199[1-9]$|^20[0-1][0-6]$|^2017$");
- Matcher matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group());
- }
- }
- }
运行结果:
1990 2010 2017
5.3 img 标签的匹配
比如, 获取图片文件内容, 这里我们考虑了一些不规范的 img 标签写法:
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String str = "<img src='aaa.jpg'/><img src=bbb.png/><img src=\"ccc.png\"/>" +
- "<img src='ddd.exe'/><img src='eee.jpn'/>";
- // 这里我们考虑了一些不规范的 img 标签写法, 比如: 空格, 引号
- Pattern pattern = Pattern.compile("<img\\s+src=(?:['\"])?(?<src>\\w+.(jpg|png))(?:['\"])?\\s*/>");
- Matcher matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group("src"));
- }
- }
- }
运行结果:
aaa.jpg
bbb.png
ccc.png
5.4 贪婪与非贪婪模式的匹配
比如, 获取 div 标签中的文本内容:
- package com.wuxianjiezh.regex;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String str = "<div > 文章标题</div><div > 发布时间</div>";
- // 贪婪模式
- Pattern pattern = Pattern.compile("<div>(?<title>.+)</div>");
- Matcher matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group("title"));
- }
- System.out.println("--------------");
- // 非贪婪模式
- pattern = Pattern.compile("<div>(?<title>.+?)</div>");
- matcher = pattern.matcher(str);
- while (matcher.find()) {
- System.out.println(matcher.group("title"));
- }
- }
- }
运行结果:
文章标题</div><div > 发布时间
--------------
文章标题
发布时间
6. 推荐详细的自学教程
正则表达式 30 分钟入门教程 https://deerchao.net/tutorials/regex/regex.htm
Java 的正则表达式工具 http://www.regexplanet.com/advanced/java/index.html
正则表达式语法篇 https://yanhaijing.com/javascript/2017/08/06/regexp-syntax/
正则表达式语法详解篇 https://blog.csdn.net/yaerfeng/article/details/28855587#reg
JavaScript 正则表达式 https://developer.mozilla.org/zh-CN/docs/web/JavaScript/Guide/Regular_Expressions
7. 附录
问题 1: 关于空格
针对 tab 键带来的多个空格问题, 有时候我们针对带空格的一行数据要进行切割, 如果有多个空格就会出现就会切割空格出现, 我们想把空格都去掉, 所以需要用到某些方法.
解决方案: 利用正则表达式来匹配空格 \\s+
首先利用 split("\s+"); 方法来对字符串切割, 尽可能的匹配空格, 这里也挺有意思, 因为空格数目不一样, 可以动态变换匹配的空格数量, 这个实现原理可以看看底层原理, 挺有意思.
- String string="a b a a";
- for(String a:string.split("\\s+")){
- System.out.println(a);
- }
问题 2:[] {} () 的使用区别
() 是为了提取匹配的字符串. 表达式中有几个 () 就有几个相应的匹配字符串.(\s*)表示连续空格的字符串.
[]是定义匹配的字符范围. 比如 [a-zA-Z0-9] 表示相应位置的字符要匹配英文字符和数字.[\s*]表示空格或者 * 号.
{}一般用来表示匹配的长度, 比如 \s{3} 表示匹配三个空格,\s{1,3}表示匹配一到三个空格.
(0-9) 匹配 '0-9 本身.
[0-9]* 匹配数字(注意后面有 *, 可以为空)
[0-9]+ 匹配数字(注意后面有 +, 不可以为空){1-9} 写法错误.
[0-9]{0,9} 表示长度为 0 到 9 的数字字符串
问题 3: ()和 [] 有本质的区别
()内的内容表示的是一个子表达式,()本身不匹配任何东西, 也不限制匹配任何东西, 只是把括号内的内容作为同一个表达式来处理!
例如:(ab){1,3}, 就表示 ab 一起连续出现最少 1 次, 最多 3 次. 如果没有括号的话, ab{1,3}, 就表示 a, 后面紧跟的 b 出现最少 1 次, 最多 3 次. 另外, 括号在匹配模式中也很重要. 查看前面的介绍 []表示匹配的字符在 [] 中, 并且只能出现一次, 并且特殊字符写在 [] 会被当成普通字符来匹配. 例如[(a)], 会匹配(,a,), 这三个字符. 所以() [] 无论是作用还是表示的含义, 都有天壤之别, 没什么联系
来源: https://juejin.im/post/5acb0c526fb9a028ba1fc602