一、正则表达式基础
1.1 基本概念
(1)定义:正则表达式是一种用于描述字符串模式的形式化表示法,通过特定字符和它们的组合来创建 “规则字符串”,用于匹配、查找和替换文本。
(2)用途:常用于文本处理任务,如验证用户输入格式(邮箱、电话号码等)、从文本中提取特定信息、替换文本中的某些部分等等。说白了,就是用于处理复杂的字符串。
1.2常用符号与元字符(方便查找)
符号 |
含义 |
示例及解释 |
普通字符 |
匹配自身 |
如a匹配字符a,5匹配字符5 |
. |
匹配除换行符外的任意单个字符 |
a.c可匹配abc、a1c等,但不匹配a\\nc |
^ |
匹配字符串的开头 |
^abc可匹配以abc开头的字符串,如abcdef,但不匹配dabc |
$ |
匹配字符串的结尾 |
abc$可匹配以abc结尾的字符串,如defabc,但不匹配abcdef |
* |
匹配前面的字符或子表达式零次或多次 |
a*可匹配空字符串、a、aa、aaa等; (ab)*可匹配空字符串、ab、abab等 |
+ |
匹配前面的字符或子表达式一次或多次 |
a+可匹配a、aa、aaa等; (ab)+可匹配ab、abab等 |
? |
匹配前面的字符或子表达式零次或一次 |
a?可匹配空字符串或a; (ab)?可匹配空字符串或ab |
[] |
字符集合,匹配其中任意一个字符 |
[abc] 可匹配a或b或c; [0 – 9]可匹配任意一位数字 |
[^] |
否定字符集合, 匹配不在其中的任意一个字符 |
[^abc]可匹配除a、b、c之外的任意字符 |
() |
分组,改变优先级或用于反向引用 |
(ab)+表示ab整体重复多次; (a(bc))中,\\1代表整个a(bc),\\2代表bc |
\\ |
转义字符,使特殊字符匹配自身 |
\\.匹配字符.; \\*匹配字符* |
\\d |
匹配任意一个数字,等价于[0 – 9] |
\\d可匹配0到9中的任意一个数字, 如a\\dc可匹配a1c、a2c等 |
\\D |
匹配任意一个非数字字符, 等价于[^0 – 9] |
\\D可匹配除数字外的字符,如\\Da可匹配a、b、@等与数字不同的字符 |
\\s |
匹配任意一个空白字符,包括空格、制表符、换行符等,等价于[ \\f\\n\\r\\t\\v] |
如a\\sb可匹配a b(中间有空格)、 a\\n b(a和b中间换行)等 |
\\S |
匹配任意一个非空白字符, 等价于[^ \\f\\n\\r\\t\\v] |
\\S可匹配除空白字符外的字符,如\\Sa可匹配1a、ba等,只要不是空白字符开头即可 |
\\w |
匹配任意一个单词字符,包括字母、数字和下划线,等价于[a-zA – Z0 – 9_] |
\\w可匹配a到z、A到Z、0到9以及_,如\\w+可匹配abc、123、abc123_等单词字符组合 |
\\W |
匹配任意一个非单词字符, 等价于[^a-zA – Z0 – 9_] |
\\W可匹配除单词字符外的字符, 如\\W+可匹配@、#、(空格)等 |
1.3 量词
(1)*(零次或多次):匹配前面的字符或子表达式零次或多次。例如a*,可以匹配空字符串,也可以匹配一个或多个连续的a,如a、aa、aaa等。
(2)+(一次或多次):匹配前面的字符或子表达式一次或多次。例如a+,至少匹配一个a,可以是a、aa、aaa等,但不能匹配空字符串。
(3)?(零次或一次):匹配前面的字符或子表达式零次或一次。例如a?,可以匹配空字符串,也可以匹配一个a。
(4){n}(恰好 n 次):匹配前面的字符或子表达式恰好 n 次。例如a{3},只能匹配连续的三个a,即aaa。
(5){n,}(至少 n 次):匹配前面的字符或子表达式至少 n 次。例如a{2,},可以匹配aa、aaa、aaaa等两个或更多个连续的a。
(6){n,m}(n 到 m 次):匹配前面的字符或子表达式 n 到 m 次(n 和 m 为非负整数,且 n <= m)。例如a{1,3},可以匹配a、aa、aaa。
1.4 字符集
(1)普通字符集[]:
在方括号内列出的字符,表示匹配其中任意一个字符。例如[abc],可以匹配a、b或c。。
(2)范围字符集[a – z]、[0 – 9]等等:
通过连字符表示字符范围。[a – z]匹配任意一个小写字母,[0 – 9]匹配任意一个数字。
(3)否定字符集[^]:
在方括号内开头使用^,表示匹配不在括号内的任意一个字符。例如[^abc],可以匹配除了a、b、c之外的任意字符。
二、正则表达式的匹配模式
2.1 贪婪匹配与非贪婪匹配
(1)贪婪匹配:
正则表达式默认采用贪婪匹配模式,即尽可能多地匹配字符。例如,对于字符串"aaaab",正则表达式a+会匹配"aaaa",因为它会一直匹配到不能再匹配为止。
(2)非贪婪匹配:
在量词后面加上?表示非贪婪匹配,即尽可能少地匹配字符。对于上述字符串"aaaab",正则表达式a+?会匹配"a",然后继续往后匹配下一个"a"。
例题:对于字符串"aabbb",分别用b+和b+?去匹配,b+会匹配"bbb";通过Matcher的find()方法循环查找时,b+?会依次匹配"b"、"b"、"b"。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Example {
public static void main(String[] args) {
String str = "aabbb";
// 贪婪匹配
Matcher greedyMatcher = Pattern.compile("b+").matcher(str);
if (greedyMatcher.find()) {
System.out.println("贪婪匹配结果:" + greedyMatcher.group()); // 输出 "bbb"
}
// 非贪婪匹配
Matcher lazyMatcher = Pattern.compile("b+?").matcher(str);
System.out.print("非贪婪匹配结果:");
while (lazyMatcher.find()) {
System.out.print(lazyMatcher.group() + " "); // 输出 "b b b "
}
}
}
2.2 全局匹配与局部匹配
(1)全局匹配:在 Java 中,通过Matcher的find()方法循环调用实现全局匹配,即遍历字符串中所有符合模式的部分。
(2)局部匹配:默认情况下,使用Matcher的find()方法首次调用仅返回第一个匹配的结果,即局部匹配。
例题:对于字符串"123abc456abc",分别实现局部匹配和全局匹配:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Example {
public static void main(String[] args) {
String str = "123abc456abc";
Pattern pattern = Pattern.compile("abc");
Matcher matcher = pattern.matcher(str);
// 局部匹配(获取第一个结果)
if (matcher.find()) {
System.out.println("局部匹配结果:" + matcher.group()); // 输出 "abc"
}
// 全局匹配(获取所有结果)
System.out.print("全局匹配结果:");
matcher.reset(); // 重置匹配器,重新开始匹配
while (matcher.find()) {
System.out.print(matcher.group() + " "); // 输出 "abc abc "
}
}
}
2.3 区分大小写与不区分大小写匹配
(1)区分大小写匹配:
默认情况下,正则表达式是区分大小写的。例如[A-Z]只能匹配大写字母,[a-z]只能匹配小写字母。对于字符串"Abc",正则表达式[a-z]{3}不能匹配,因为其中包含大写字母A。
(2)不区分大小写匹配:
在 Java 中,通过Pattern.CASE_INSENSITIVE标志实现不区分大小写匹配。
三、正则表达式的分组与捕获
3.1 分组的概念与用途
(1)分组定义:
使用圆括号()将多个字符或子表达式组合在一起,形成一个逻辑单元。例如(ab)+,表示ab这个组合可以重复一次或多次。
(2)用途:
改变优先级,使括号内的部分作为一个整体进行操作;还可用于后续的反向引用。例如(a(bc)),\\1代表整个a(bc),\\2代表bc。例如,对于字符串"abcabc",用(abc)+可以匹配整个字符串,因为abc这个组合重复了两次。
3.2 捕获组与反向引用
(1)捕获组:
每个分组就是一个捕获组,从左到右,从 1 开始为每个捕获组编号。例如在(a(bc))中,有两个捕获组,(a(bc))是第 1 个捕获组,(bc)是第 2 个捕获组。
(2)反向引用:
使用\\n(n 为捕获组编号)来引用之前捕获组匹配到的内容。例如,正则表达式(ab)\\1,可以匹配abab,因为\\1引用了第一个捕获组(ab)匹配到的内容。例题:判断字符串"xyxy"能否被(xy)\\1匹配,结果是可以匹配;判断"xyyx"能否被匹配,结果是不能匹配。
3.3 非捕获组
(1)非捕获组定义:
使用(?:)表示非捕获组,它只起到分组的作用,不会捕获匹配的内容,也不会分配组号。例如(?:ab)+,与(ab)+类似,但(?:ab)+不会产生捕获组。
(2)用途:
当只需要分组改变优先级,而不需要捕获内容时使用非捕获组,可以提高效率,减少资源占用。例如,对于正则表达式(?:a|b)c,它匹配ac或bc,但不会捕获a或b。例题:在字符串"ac"、"bc"、"abc"中,(?:a|b)c可以匹配"ac"和"bc",而(a|b)c虽然也能匹配"ac"和"bc",但会产生捕获组,在一些场景下若不需要捕获组,使用(?:a|b)c更合适。
四、正则表达式在Java中的应用
4.1主要类与方法:
(1)Pattern类:表示编译后的正则表达式模板,通过Pattern.compile(String regex)方法编译正则表达式,得到Pattern对象。
(2)Matcher类:用于执行匹配操作,通过Pattern.matcher(CharSequence input)方法创建Matcher对象,关联输入字符串。
4.2Matcher类的常用方法:
- boolean find():查找输入字符串中是否有匹配的子串,可多次调用,每次找下一个匹配。
- boolean matches():判断整个输入字符串是否完全匹配正则规则。
- String group():返回当前匹配到的整个子串。
- String group(int n):返回第 n 个捕获组匹配到的内容,n = 0 表示整个匹配。
- int start():返回当前匹配子串的起始下标。例如int startIndex = matcher.start();。
- int end():返回当前匹配子串的结束下标(exclusive,即实际结束位置 + 1)。
- String replaceAll(String replacement):将所有匹配的子串替换为指定字符串。
- String replaceFirst(String replacement):只替换第一个匹配的子串。
五、正则表达式习题(持续更新)
1.数学表达式解析器
题目:
给出如下表达式:x#y = 2*x+3*y+4 x$y = 3*x+y+2
公式中,$的优先级高于#,相同的运算符,按从左到右的顺序计算。给定表达式请计算出结果
正则表达式使用思路:
- 目的:解析数学表达式,先处理$运算,再处理#运算
- 核心思想:使用正则表达式匹配特定模式,提取数字进行计算,然后替换原字符串
// 导入正则表达式包
import java.util.regex.Matcher;
import java.util.regex.Pattern;// 编译正则表达式模式
Pattern pattern = Pattern.compile("(\\\\d+)\\\\$(\\\\d+)");// 创建匹配器并循环查找
while (true) {
Matcher matcher = pattern.matcher(str);
if (!matcher.find()) break;// 提取匹配的内容
String subStr = matcher.group(0); // 完整匹配:"4$5"
long x = Long.parseLong(matcher.group(1)); // 第一个数字:4
long y = Long.parseLong(matcher.group(2)); // 第二个数字:5// 替换计算结果
str = str.replaceFirst(subStr.replace("$", "\\\\$"), 3 * x + y + 2 + "");
}
2.字符类正则表达式的处理
题目:
给定一段“密文”字符串 s,其中字符都是经过“密码”加密的,现需要将“密文”解密并输出。
映射的规则('a' ~ 'i')分别用('1' ~ '9')表示;('j' ~ 'z')分别用("10*" ~ "26*")表示。
正则表达式使用思路:
- 目的:解密密码,按空格和星号分割输入字符串
- 核心思想:使用字符类正则表达式匹配多种分隔符
// 使用字符类正则表达式分割
String[] arrs = str.split("[ \\\\*]+");
3.提取字符串中的最长合法简单数学表达式
题目:
提取字符串中的长度最长合法简单数学表达式,并计算表达式的值。如果没有,则返回 0 。
简单数学表达式只能包含以下内容:
- 0-9数字,符号+-*
说明:
正则表达式使用思路:
- 目的: 从字符串中提取最长的有效数学表达式并计算结果
- 核心思想: 使用捕获组正则表达式匹配数学运算表达式
// 使用捕获组正则表达式匹配数学运算
String regex01 = "([0-9]+)\\\\+([0-9]+)"; // 加法表达式
String regex02 = "([0-9]+)-([0-9]+)"; // 减法表达式
String regex03 = "([0-9]+)\\\\*([0-9]+)"; // 乘法表达式
// 下面仅对加法表达式进行演示
Pattern pattern01 = Pattern.compile(regex01);
Matcher matcher01 = pattern01.matcher(text);
if (matcher01.find()) {
String fullMatch01 = matcher01.group(0); // 整个式子
int preExpression01 = Integer.parseInt(matcher01.group(1)); // 第一部分
int rearExpression01 = Integer.parseInt(matcher01.group(2)); // 第二部分
}
4.复杂的字符串组合的处理
题目:
对数字,字符,数字串,字符串,以及数字与字符串组合进行倒序排列。
符号的定义:
- “-”作为连接符使用时作为字符串的一部分,例如“11-22”作为一个整体字符串呈现;
- 连续出现 2 个 “-” 及以上时视为字符串间隔符,如“a-b”中的”–“视为间隔符,是 2 个独立整体字符串”a”和”b”;
- 除了 1,2 里面定义的字符以外其他的所有字符,全都作为间隔符处理,倒序后间隔符作为空格处理;
- 要求倒排后的单词间隔符以一个空格表示;如果有多个间隔符时,只允许出现一个空格
正则表达式使用思路:
- 目的: 识别合法单词和分隔符,倒序输出单词
- 核心思想: 使用分组正则表达式区分合法单词和分隔符
// 使用分组正则表达式区分单词和分隔符
// 1. 定义正则:匹配“合法单词”(含单个'-'的组合或者不含)和“分隔符”(连续–+或非法字符)
// 分组1: 合法单词(字母、数字、单个'-'组合)
// 分组2: 分隔符(连续–+或非法字符)
Pattern pattern = Pattern.compile("([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?)|([^a-zA-Z0-9-]|–+)");/*
* 对上述正则表达式的解释:
* 整体结构为 (分组1)|(分组2);为或结构
* (1)分组1:([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?)的说明:
* 非捕获分组(?: 表示不捕获该分组),整体是可选的(最多出现1次):
* -:匹配单个短横线;
* [a-zA-Z0-9]+:短横线后必须接至少1个字母/数字(避免-出现在结尾);
* ?:量词,表示整个分组“出现0次或1次”(即最多只能有一个-)。
* (2)([^a-zA-Z0-9-]|–+)
* [^…] 表示 “非字符集”,即匹配除了字母、数字、单个-之外的所有字符
* –+ 整体匹配 “连续2个及以上的短横线”(如 –、— 等)
*/Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
String word = matcher.group(1); // 合法单词
String separator = matcher.group(2); // 分隔符
}
评论前必须登录!
注册