什么是正则表达式?
正则表达式是一种用于匹配和操作文本的强大工具。它由一系列字符和特殊字符组成的模式,用于描述要匹配的文本模式。正则表达式可以在文本中查找、替换、提取和验证特定的模式。在处理大量文本数据时,正则表达式提供了极大的便利。
例如,我们要将下面这一段文字中《》中的文字全部提取出来。
周星驰(Stephen Chow),1962年6月22日出生于中国香港,祖籍浙江宁波,华语影视男演员、导演、编剧、监制、制片人、出品人、主持人、国家一级演员、西南民族大学客座教授、中国人民大学教授;1980年成为丽的电视台特约演员,从而进入演艺圈,1981年出演荧幕处女作《IQ成熟时》,1988年将演艺事业重心转向大银幕,在电影《霹雳先锋》中首次担任男主角,1990年凭借喜剧片《一本漫画闯天涯》确立其无厘头表演风格,之后又凭借喜剧动作片《赌圣》、喜剧片《逃学威龙》两度打破香港电影票房纪录,1993年上映的古装喜剧片《唐伯虎点秋香》使得周星驰第四次拿到香港电影年度票房冠军,1994年开始转型,首度出任导演的电影作品是《国产凌凌漆》,1995年主演的喜剧爱情片《大话西游》成为其后现代电影代表作,2001年自导自演的喜剧片《少林足球》打破香港电影票房纪录,2003年成为美国《时代周刊》封面人物,2013年执导古装电影《西游·降魔篇》,该片以2.18亿美元票房成绩打破华语电影全球票房纪录,2016年担任科幻喜剧片《美人鱼》的导演、编剧、制片人,该片创下中国内地影史单片票房纪录;作为演员,他获得过第21届香港电影金像奖最佳男主角奖、亚太电影节最佳男主角奖等奖项,入选“中国电影百年百位优秀演员”以及“中国电影百年名人堂”;作为导演,他先后获得第21届香港电影金像奖最佳导演奖、第42届台湾电影金马奖最佳导演等奖项。
如果使用传统的遍历方式,代码量大,效率不高。
正则表达式:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Regexp_1 {
public static void main(String[] args) {
String text = "周星驰(Stephen Chow),1962年6月22日出生于中国香港,祖籍浙江宁波 [178]," +
"华语影视男演员、导演、编剧、监制、制片人、出品人、主持人、国家一级演员、西南民族大学客座教授 [82]、中国人民大学教授 [82]。\n" +
"1980年,成为丽的电视台特约演员,从而进入演艺圈 [32]。1981年,出演荧幕处女作《IQ成熟时》 " +
"[161]。1988年,将演艺事业的重心转向大银幕,在电影《霹雳先锋》中首次担任男主角 " +
"[91]。1990年,凭借喜剧片《一本漫画闯天涯》确立其无厘头的表演风格 [137]," +
"之后又凭借喜剧动作片《赌圣》、喜剧片《逃学威龙》两度打破香港电影票房纪录 [1] [142]。1" +
"993年上映的古装喜剧片《唐伯虎点秋香》使得周星驰第四次拿到香港电影年度票房冠军 [136]。1994年,周星驰开始转型," +
"他首度出任导演的电影作品是《国产凌凌漆》 [94]。1995年,主演的喜剧爱情片《大话西游》成为周星驰后现代电影的代表作 [92]。" +
"2001年,自导自演的喜剧片《少林足球》打破香港电影票房纪录 [24]。2003年,成为美国《时代周刊》封面人物 [4]。" +
"2013年,执导古装电影《西游·降魔篇》,该片以2.18亿美元的票房成绩打破华语电影在全球的票房纪录 [5]。" +
"2016年,担任科幻喜剧片《美人鱼》的导演、编剧、制片人 [6-7],该片创下中国内地影史单片票房纪录 [81] [84]。\n" +
"作为演员,他获得过第21届香港电影金像奖最佳男主角奖,亚太电影节最佳男主角奖等奖项 [3] [139]," +
"入选"中国电影百年百位优秀演员"以及"中国电影百年名人堂" [85] [138]。作为导演,他先后获得第21届香港电影金像奖最佳导演奖、" +
"第42届台湾电影金马奖最佳导演等奖项 [3] [80]。";
String content = "127.0.0.1, 192.168.31.5, 128.165.22.43";
//1. 先创建一个pattern对象,模式对象,可以理解成一个正则表达式对象
Pattern pattern = Pattern.compile("《(.*?)》");
//2. 创建一个匹配器对象
//理解: 就是match匹配器按照pattern模式,到content文本去匹配
//找到就返回true,否则就返回flase
Matcher matcher = pattern.matcher(text);
//3. 开始循环匹配
while (matcher.find()) {
//匹配内容,文本,放到mgroup(0)中
System.out.println("找到 "+matcher.group(0));
}
}
}
运行结果:
基本语法
转义字符
-
\.
- 匹配字面意义上的句点(.),而不是"任意单个字符"的通配符。 -
\\
- 匹配反斜杠(\)本身。 -
\*
- 匹配星号(*),而不是"零次或多次"的重复符号。 -
\+
- 匹配加号(+),而不是"一次或多次"的重复符号。 -
\?
- 匹配问号(?),而不是"零次或一次"的限定符。 -
\|
- 匹配竖线(|),而不是"或"操作符。 -
\(
和\)
- 匹配圆括号(()),而不是用于分组的符号。 -
\{
和\}
- 匹配花括号({}),而不是用于指定重复次数的符号。 -
\[
和\]
- 匹配方括号([]),而不是用于定义字符集的符号。 -
\^
- 匹配脱字符(^),而不是"行的开始"的定位符。 -
\$
- 匹配美元符号($),而不是"行的结束"的定位符。 -
\-
- 在字符集中匹配连字符(-),而不是用于指定范围的符号。
在Java字符串中,反斜杠(\
)是一个转义字符,用于引入特殊字符序列。例如,\n
表示新行,\t
表示制表符等。因此,如果你想在字符串中表示一个实际的反斜杠字符,你需要使用双反斜杠(\\
)来转义它。
示例:
java
public static void main(String[] args) {
String content = "asd(qeq(adada((";
String RegStr = "\\(";
Pattern pattern = Pattern.compile(RegStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 "+matcher.group());
}
}
字符匹配符
java
public static void main(String[] args) {
String content = "ada13A a@";
// String regStr = "[a-z]";//匹配a-z之间任意一个字符
// String regStr = "[A-Z]";//匹配A-Z之间任意一个字符
// String regStr = "[0-9]";//匹配0-9之间任意一个字符
// String regStr = "abc";//匹配abc字符,字符串默认区分大小写
// String regStr = "(?i)abc";//匹配abc字符,不区分大小写
// String regStr = "a(?i)bc";//匹配abc字符,bc不区分大小写
// String regStr = "a((?i)b)c";//匹配abc字符,b不区分大小写
// String regStr = "[^0-9]";//匹配任何非数字字符。
// String regStr = "//D";//匹配非数字
// String regStr = "//d";//匹配数字
// String regStr = "//w";//匹配字母,数字,下划线
// String regStr = "//W";//匹配非w
// String regStr = "//s";//匹配任何空白字符(小写s)
// String regStr = "//S";//匹配任何非空白字符(大写S)
// String regStr = ".";//匹配除了\n之外所有字符 要是想用 . 只能转义 \\.
// String regStr = "ad{1}";//匹配ad
//说明
//1. 当出现Pattern.CASE_INSENSITIVE不区分字母大小写
Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
修饰符
修饰符 | 含义 | 描述 |
---|---|---|
i | ignore - 不区分大小写 | 将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。 |
g | global - 全局匹配 | 查找所有的匹配项。 |
m | multi line - 多行匹配 | 使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。 |
s | 特殊字符圆点 . 中包含换行符 \n | 默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。 |
选择匹配符
选择匹配符 是正则表达式中的一个元字符,用于在多个模式之间进行"或"匹配。它的符号是 |
,表示"匹配左边或右边的表达式"。
java
public static void main(String[] args) {
String content = "韩 han 汗流浃背";
String regStr = "han|韩|汗";
Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
限定符
限定符用于指定字符或分组的重复次数。
常见得分限定符
常见限定符 | 描述 |
---|---|
* |
匹配前面的元素零次或多次。 |
+ |
匹配前面的元素一次或多次。 |
? |
匹配前面的元素零次或一次。 |
{n} |
匹配前面的元素恰好 n 次。 |
{n,} |
匹配前面的元素至少 n 次。 |
{n,m} |
匹配前面的元素至少 n 次,至多 m 次。 |
java
String content = "31131aaad13";
// String regStr = "a{3}"; aaa
// String regStr = "1{2}"; 11
// String regStr = "\\d{2}"; 匹配任意两位数字
/**
* {n,m}
* 细节: java匹配默认是贪婪匹配,尽可能匹配多的
* \\d{2,4}
* 找到 3113
* 找到 13
*/
// String regStr = "\\d{2,4}";
//+
// String regStr = "1+";//匹配一个或者多个1
//*
// String regStr = "1+";//匹配0个1或者多个1
//?
String regStr = "d1?";//匹配d或者d1
Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
贪婪匹配和非贪婪匹配
什么是贪婪匹配:默认情况下,量词(如 *
、+
、?
、{}
)是贪婪匹配 的,即它们会尽可能多地匹配字符。如果你想取消贪婪匹配,改为非贪婪匹配 (也称为懒惰匹配 ),可以在量词后面加上 ?
。
贪婪匹配
java
String text = "aabab";
String regex = "a.*b"; // 贪婪匹配
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("找到匹配: " + matcher.group());
}
输出
找到匹配: aabab
非贪婪匹配
java
String text = "aabab";
String regex = "a*?b"; // 非贪婪匹配
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("找到匹配: " + matcher.group());
}
该案例目的是匹配a{}b
输出:
找到匹配: aab
找到匹配: ab
定位符
java
public static void main(String[] args) {
String content = "31131Saad";
String content = "lllzkdjkkwlgs kkwl yxgs";
//^ 指定起始字符
//至少以一个数字开头,后接任意字母
// String regStr = "^[0-9]+[a-z]*"; 输出 31131Saad
//$ 指定结束字符
//以任意数字开始,以任意字母结束
// String regStr = "^[0-9]+[a-z]*$"; 输出31131Saad
// \\b匹配目标字符串的边界 所谓的边界是指 字符串的 最后 ,或者被空格隔开的 子字符串的后面
// String regStr = "kk\\b"; 输出为空
// \B匹配目标字符串的非边界
String regStr = "kk\\B"; //输出 找到 kk 找到 kk
Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
分组
在正则表达式中,捕获分组 是一种将匹配的子字符串捕获并存储起来的机制。通过使用圆括号 ()
,可以将正则表达式的一部分标记为一个分组,匹配的内容会被捕获并可以在后续操作中使用。
捕获分组的作用
-
提取匹配的子字符串:可以从匹配的文本中提取特定的部分。
-
引用分组:可以在正则表达式中或替换操作中引用捕获的分组。
-
分组操作 :可以对分组进行量词操作(如
*
、+
、{}
)。
捕获分组的语法
非分组捕获
如果不需要捕获分组,可以使用非捕获分组语法 (?:...)
。非捕获分组不会存储匹配的内容,也不会分配分组编号。
java
public static void main(String[] args) {
String content = "lll你好 lllHello lll同学";
//找到你好lll Hellolll lll同学 的字符串
//上面的写法可以等价非捕获分组
String regStr = "lll(?:你好|Hello|同学)";
输出: 找到 lll你好
找到 lllHello
找到 lll同学
在这个例子中"(?:你好|Hello|同学)"匹配到了(你好|Hello|同学)但是并没有捕获,因此matcher.group(0) 返回整个匹配的字符串。
String text = "2023-10-05";
String regex = "(\\d{4})-(?:\\d{2})-(\\d{2})"; // 非捕获月份
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
System.out.println("年: " + matcher.group(1)); // 第1个分组
System.out.println("日: " + matcher.group(2)); // 第2个分组
}
在这个例子中,(?:\\d{2}) 匹配了月份,但没有捕获它,因此 matcher.group(2) 直接对应日期的分组。
//找到 lll 这个关键字,但是要求只是查找你好和Hello中的lll
// String regStr = "lll(?=你好|Hello)";
输出:找到 lll
找到 lll
(?=你好|Hello):这是一个正向零宽断言,表示 lll 后面必须紧跟 你好 或 Hello,但 你好 或 Hello 不会被包含在匹配结果中。
//找到 lll 这个关键字,但是要求只是查找不是 lll你好 lllHello 中的lll
// String regStr = "lll(?!你好|Hello)";
输出:找到 lll
(?!你好|Hello):这是一个负向零宽断言,表示 lll 后面不能是 你好 或 Hello。
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
匹配URL
java
public static void main(String[] args) {
String s = "https://www.bilibili.com/video/BV1Eq4y1E79W?spm_id_from=333.788.player.switch&vd_source=ab5366d1a2923f7defae5751c26f3023&p=18";
// String s = "https://www.bilibili.com";
Pattern pattern = Pattern.compile("^((https|http)://)?([\\w-]+\\.)+([\\w-])+(/[\\w-?~_+&=!^$.#]*)*$");
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
System.out.println("满足格式");
} else {
System.out.println("不满足格式");
}
}
分析
^((https|http)://)?
:可选的http://
或https://
协议部分。?
是一个量词 ,表示前面的元素可以出现 0 次或 1 次。
([\\w-]+\\.)+
:匹配域名的子域名部分 www.bilibili. 因为有限定符+ 所以可以有多个 xxxx.
([\\w-])+
:匹配顶级域名 com
(/[\\w-?~_+&=!^$.#]*)*
:匹配路径及其查询参数部分,路径是可选的
matches
特性 | matches (String 类) |
Matcher 类 |
---|---|---|
功能 | 检查整个字符串是否完全匹配正则表达式 | 对字符串进行复杂的正则表达式匹配操作 |
返回值 | boolean |
无固定返回值,支持多种操作(如 find 、group 、replaceAll 等) |
匹配范围 | 必须从字符串的开头匹配到结尾 | 可以查找字符串中任意部分匹配的子字符串 |
使用场景 | 验证字符串是否符合特定格式 | 提取、替换、查找字符串中的特定模式 |
mathes: 整体匹配, 只返回tue或false ,当没有^时, matche 没有约束,就会错误 但是matches不会
java
String text = "abc123";
String regex = "abc\\d+";
boolean result = text.matches(regex);
System.out.println(result); // 输出: true
java
String text = "abc123 def456";
String regex = "\\d+";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("找到匹配: " + matcher.group());
}
输出
找到匹配: 123
找到匹配: 456
Matcher 类常用方法
start & end
作用:
-
start()
:返回当前匹配的子字符串的起始索引。 -
end()
:返回当前匹配的子字符串的结束索引。
java
public static void main(String[] args) {
String s = "hello 123 hello lll sb";
// String regStr = "hello";
String regStr = "lll.*";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(s);
while (matcher.find()) {
System.out.println("=================");
System.out.println(matcher.start());
System.out.println(matcher.end());
System.out.println("找到: " + s.substring(matcher.start(), matcher.end()));
}
//整体匹配方法,常用于,去校验某个字符串是否满足某个规则
System.out.println("整体匹配 = " + matcher.matches());
}
输出
start: 16
end: 19
replaceAll & replaceFirst
-
作用:
-
replaceAll(String replacement)
:替换所有匹配的子字符串。 -
replaceFirst(String replacement)
:替换第一个匹配的子字符串。
-
-
返回值 :
String
,替换后的字符串。
javascript
String text = "abc123 def456";
String regex = "\\d+"; // 匹配一个或多个数字
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
String result1 = matcher.replaceAll("***");
String result2 = matcher.replaceFirst("***");
System.out.println("replaceAll: " + result1); // 输出: abc*** def***
System.out.println("replaceFirst: " + result2); // 输出: abc*** def456
find
-
作用:在输入字符串中查找下一个匹配的子字符串。
-
返回值 :
boolean
,如果找到匹配的子字符串,则返回true
,否则返回false
。 -
特点:可以多次调用,每次查找下一个匹配的子字符串。
javaString text = "abc123 def456"; String regex = "\\d+"; // 匹配一个或多个数字 Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(text); while (matcher.find()) { System.out.println("找到匹配: " + matcher.group()); } 输出 找到匹配: 123 找到匹配: 456
group
-
作用:返回当前匹配的子字符串。
-
重载方法:
-
group()
:返回整个匹配的子字符串。 -
group(int group)
:返回指定分组的子字符串。 -
group(String name)
:返回命名分组的子字符串(需使用命名分组语法(?<name>...)
)。
-
-
返回值 :
String
,匹配的子字符串。
反向引用
反向引用是正则表达式中的一种功能,用于在同一个正则表达式中引用前面已经捕获的分组内容。通过反向引用,可以匹配与前面分组相同的内容,从而实现更复杂的模式匹配。
反向引用的语法
-
在正则表达式中,使用
\n
(n
是分组的编号)来引用第n
个捕获分组的内容。 -
分组编号从 1 开始,依次递增。
(\w+) \1
-
(\w+)
:捕获一个或多个字母、数字或下划线,作为第 1 个分组。 -
\1
:引用第 1 个分组的内容,要求匹配与第 1 个分组相同的字符串。
反向引用的作用
-
匹配重复内容:例如,匹配重复的单词、标签等。
-
验证对称结构:例如,匹配成对的括号、引号等。
-
简化复杂模式:通过引用分组内容,避免重复编写相同的模式。
java
public static void main(String[] args) {
// String s = "tom5225 jack12 tom11111 mack22 luck33";
String s = "12321-333999111";
//匹配两个连续相同的数
// String regex = "(\\d)\\1";
//(\\d)该分组用于匹配数字 \\1
//匹配五个连续相同的数
// String regex = "(\\d)\\1{4}";
//匹配个位与千位相同,十位与百位相同的数
// String regex = "(\\d)(\\d)\\2\\1";
/**
* 检索商品编号,要求前面是一个五位数然后-一个九位数,连续的每三位相同
*/
String regex = "\\d{5}-(\\d)\\1{2}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(s);
while (matcher.find()) {
System.out.println("找到" + matcher.group(0));
}
}
解释一下 (\d)(\d)\2\1
1. 正则表达式结构
-
(\\d)
:捕获一个数字(0-9),作为第 1 个分组。 -
(\\d)
:捕获另一个数字,作为第 2 个分组。 -
\\2
:反向引用第 2 个分组的内容,要求匹配与第 2 个分组相同的数字。 -
\\1
:反向引用第 1 个分组的内容,要求匹配与第 1 个分组相同的数字。
2. 匹配规则
这个正则表达式匹配的四位数字需要满足以下条件:
-
第 1 位是任意数字(
\\d
)。 -
第 2 位是任意数字(
\\d
)。 -
第 3 位必须与第 2 位相同(
\\2
)。 -
第 4 位必须与第 1 位相同(
\\1
)。
反向引用的注意事项
-
反向引用只能引用前面已经定义的分组。
-
如果引用的分组不存在,正则表达式会抛出异常或匹配失败。
-
反向引用的内容必须与分组的内容完全一致。
反向引用学会以后就可以结合replaceAll等方法对字符串进行各种操作
比如
将 我....我要....学学学学....编程java! 字符串恢复成正常人说话
java
public static void main(String[] args) {
String s = "我....我要....学学学学....编程java! ";
s = s.replaceAll("\\.+", "");
s = s.replaceAll("(.)\\1+", "$1");
System.out.println(s);
}
spilt
spilt(String regex) 可以传入一个匹配规则,通过这个规则进行分组
java
public static void main(String[] args) {
String s = "hello~lll#qqq";
String[] split = s.split("~|#|\\d+");
for (String string : split) {
System.out.println(string);// hello lll qqq
}
}
底层实现
java
package com.hyx.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author kkwlgs
* date 2024/10/15 19:39
* 功能描述:分析正则表达式的底层实现
*/
public class RegTheory {
public static void main(String[] args) {
String content = "1234和获取文件的和监控维护的艰苦环境i哦亲黑龙去维护起来换热器然后琼海日哦6546在哦ihi Ohio和hi哦好" +
"是,二姐琼恩和.u二u琼文i偶你还高1122,饿哦急哦急哦1234.SFS手机hi后11322.";
//目标: 匹配所有含有四个数字的字符串
//1. \\d表示任意一个数字
String regStr = "(\\d\\d)(\\d\\d)";
//2. 创建模式对象
Pattern pattern = Pattern.compile(regStr);
//3. 创建匹配器
//说明:创建匹配器matcher按照正则表达式规则去匹配
Matcher matcher = pattern.matcher(content);
/**
* match.find() 完成的任务
* 什么是分组,比如(\d\d)(\d\d),正则表达式中有(),表示分组,第一个()表示第一组,第二个表示第二组
* 1. 根据指定的规则,定位满足规则得分子字符串
* 2. 找到后,将子字符串的位置的索引记录到 int[] groups
* 2.1 groups[0] = 0,把子字符串的结束位置的索引的+1的值记录到groups[1] = 5
* 2.2 记录1组()匹配到的字符串groups[2] = 0,groups[3] = 2
* 2.3 记录2组()匹配到的字符串groups[4] = 2,groups[5] = 4
* 2.4 更多分组.....
* 3. 同时记录oldLast的值为子字符串的结束的索引+1的值即4,下次执行find时,就从4开始
*
* matcher.group
* if (first < 0)
* throw new IllegalStateException("No match found");
* if (group < 0 || group > groupCount())
* throw new IndexOutOfBoundsException("No group " + group);
* if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
* return null;
* return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
* 截取groups[0]到groups[1]的值就是从0到4 [0,4)
* 继续执行match.find()
*/
//4. 开始匹配
while (matcher.find()) {
//小结
//1. 如果正则表达式有()即分组
//2. 取出匹配的字符串规则如下
//3. group(0) 表示匹配到的子字符串
//4. group(1) 表示匹配到的子字符串的第一组字符串
//5. group(2) 表示匹配到的子字符串的第二组字符串
//6. .....但是分组不能超过group(0)的组数
System.out.println("找到 " + matcher.group(0));
System.out.println("第一组() " + matcher.group(1));
System.out.println("第二组() " + matcher.group(2));
}
}
}