正则表达式
- 正则表达式
-
- 1.字符类
- [2. 逻辑运算符](#2. 逻辑运算符)
- [3. 预定义字符](#3. 预定义字符)
- [4. 数量词](#4. 数量词)
- [5. 数据爬取](#5. 数据爬取)
- [6. 按要求爬取](#6. 按要求爬取)
-
- [6.1 贪婪爬取](#6.1 贪婪爬取)
- [6.2 非贪婪爬取](#6.2 非贪婪爬取)
- [7. 字符串中使用正则表达式的方法](#7. 字符串中使用正则表达式的方法)
- [8. 分组括号](#8. 分组括号)
-
- [8.1 匹配和捕获分组](#8.1 匹配和捕获分组)
- [8.2 引用和后向引用](#8.2 引用和后向引用)
- [8.3 零宽断言](#8.3 零宽断言)
- [8.4 分组嵌套与递归](#8.4 分组嵌套与递归)
- [8.5 忽略大小写](#8.5 忽略大小写)
- [8.6 代码示例](#8.6 代码示例)
正则表达式
正则表达式是一种用来匹配和操作字符串的强大工具。它是一种描述字符模式的方式,能够帮助你在文本中查找、替换和提取特定的字符序列。
常用的正则表达式元字符和操作符:
-
字符串匹配:
-
普通字符:字符直接匹配,例如
abc
匹配字符串中的 "abc"。 -
点号:
.
匹配除换行符以外的任意单个字符。 -
转义字符:使用反斜杠
\
来转义特殊字符,例如\.
匹配句号。
-
-
字符类:
-
方括号:
[]
用来匹配方括号内的任意一个字符,例如[aeiou]
匹配任意一个元音字母。 -
脱字符:
^
在方括号内使用,用来否定字符类,例如[^0-9]
匹配非数字字符。
-
-
重复匹配:
-
星号:
*
匹配前面的字符零次或多次,例如a*b
匹配 "b"、"ab"、"aab" 等。 -
加号:
+
匹配前面的字符一次或多次,例如a+b
匹配 "ab"、"aab" 等。 -
问号:
?
匹配前面的字符零次或一次,例如colou?r
匹配 "color" 和 "colour"。 -
大括号:
{n}
匹配前面的字符恰好 n 次,例如a{3}
匹配 "aaa"。 -
连字符:
{m,n}
匹配前面的字符至少 m 次、最多 n 次,例如a{2,4}
匹配 "aa"、"aaa" 和 "aaaa"。
-
-
边界匹配:
-
插入符号:
^
用于匹配字符串的开头,例如^hello
匹配以 "hello" 开头的字符串。 -
美元符号:
$
用于匹配字符串的结尾,例如world$
匹配以 "world" 结尾的字符串。
-
-
特殊字符:
-
反斜杠:
\
用于转义特殊字符。 -
竖线:
|
用于分隔多个模式,例如foo|bar
匹配 "foo" 或 "bar"。 -
圆括号:
()
用于分组字符,例如(abc)+
匹配 "abc"、"abcabc" 等。
-
1.字符类
- 语法示例:
-
[abc]:代表a或者b,或者c字符中的一个。
-
[^abc]:代表除a,b,c以外的任何字符。
-
[a-z]:代表a-z的所有小写字符中的一个。
-
[A-Z]:代表A-Z的所有大写字符中的一个。
-
[0-9]:代表0-9之间的某一个数字字符。
-
[a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
-
[a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。
- 代码示例:
java
package com.itheima.a08regexdemo;
public class RegexDemo2 {
public static void main(String[] args) {
//public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true
// 只能是a b c
System.out.println("-----------1-------------");
System.out.println("a".matches("[abc]")); // true
System.out.println("z".matches("[abc]")); // false
// 不能出现a b c
System.out.println("-----------2-------------");
System.out.println("a".matches("[^abc]")); // false
System.out.println("z".matches("[^abc]")); // true
System.out.println("zz".matches("[^abc]")); //false
System.out.println("zz".matches("[^abc][^abc]")); //true
// a到zA到Z(包括头尾的范围)
System.out.println("-----------3-------------");
System.out.println("a".matches("[a-zA-z]")); // true
System.out.println("z".matches("[a-zA-z]")); // true
System.out.println("aa".matches("[a-zA-z]"));//false
System.out.println("zz".matches("[a-zA-Z]")); //false
System.out.println("zz".matches("[a-zA-Z][a-zA-Z]")); //true
System.out.println("0".matches("[a-zA-Z]"));//false
System.out.println("0".matches("[a-zA-Z0-9]"));//true
// [a-d[m-p]] a到d,或m到p
System.out.println("-----------4-------------");
System.out.println("a".matches("[a-d[m-p]]"));//true
System.out.println("d".matches("[a-d[m-p]]")); //true
System.out.println("m".matches("[a-d[m-p]]")); //true
System.out.println("p".matches("[a-d[m-p]]")); //true
System.out.println("e".matches("[a-d[m-p]]")); //false
System.out.println("0".matches("[a-d[m-p]]")); //false
// [a-z&&[def]] a-z和def的交集。为:d,e,f
System.out.println("----------5------------");
System.out.println("a".matches("[a-z&[def]]")); //false
System.out.println("d".matches("[a-z&&[def]]")); //true
System.out.println("0".matches("[a-z&&[def]]")); //false
// [a-z&&[^bc]] a-z和非bc的交集。(等同于[ad-z])
System.out.println("-----------6------------_");
System.out.println("a".matches("[a-z&&[^bc]]"));//true
System.out.println("b".matches("[a-z&&[^bc]]")); //false
System.out.println("0".matches("[a-z&&[^bc]]")); //false
// [a-z&&[^m-p]] a到z和除了m到p的交集。(等同于[a-1q-z])
System.out.println("-----------7-------------");
System.out.println("a".matches("[a-z&&[^m-p]]")); //true
System.out.println("m".matches("[a-z&&[^m-p]]")); //false
System.out.println("0".matches("[a-z&&[^m-p]]")); //false
}
}
2. 逻辑运算符
-
语法示例:
-
&&:并且
-
| :或者
-
\ :转义字符
-
-
代码示例:
java
public class Demo {
public static void main(String[] args) {
String str = "had";
//1.要求字符串是小写辅音字符开头,后跟ad
String regex = "[a-z&&[^aeiou]]ad";
System.out.println("1." + str.matches(regex));
//2.要求字符串是aeiou中的某个字符开头,后跟ad
regex = "[a|e|i|o|u]ad";//这种写法相当于:regex = "[aeiou]ad";
System.out.println("2." + str.matches(regex));
}
}
java
package com.itheima.a08regexdemo;
public class RegexDemo3 {
public static void main(String[] args) {
// \ 转义字符 改变后面那个字符原本的含义
//练习:以字符串的形式打印一个双引号
//"在Java中表示字符串的开头或者结尾
//此时\表示转义字符,改变了后面那个双引号原本的含义
//把他变成了一个普普通通的双引号而已。
System.out.println("\"");
// \表示转义字符
//两个\的理解方式:前面的\是一个转义字符,改变了后面\原本的含义,把他变成一个普普通通的\而已。
System.out.println("c:Users\\moon\\IdeaProjects\\basic-code\\myapi\\src\\com\\itheima\\a08regexdemo\\RegexDemo1.java");
}
}
3. 预定义字符
-
语法示例:
-
"." : 匹配任何字符。
-
"\d":任何数字[0-9]的简写;
-
"\D":任何非数字[^0-9]的简写;
-
"\s": 空白字符:[ \t\n\x0B\f\r] 的简写
-
"\S": 非空白字符:[^\s] 的简写
-
"\w":单词字符:[a-zA-Z_0-9]的简写
-
"\W":非单词字符:[^\w]
-
-
代码示例:
java
public class Demo {
public static void main(String[] args) {
//.表示任意一个字符
System.out.println("你".matches("..")); //false
System.out.println("你".matches(".")); //true
System.out.println("你a".matches(".."));//true
// \\d 表示任意的一个数字
// \\d只能是任意的一位数字
// 简单来记:两个\表示一个\
System.out.println("a".matches("\\d")); // false
System.out.println("3".matches("\\d")); // true
System.out.println("333".matches("\\d")); // false
//\\w只能是一位单词字符[a-zA-Z_0-9]
System.out.println("z".matches("\\w")); // true
System.out.println("2".matches("\\w")); // true
System.out.println("21".matches("\\w")); // false
System.out.println("你".matches("\\w"));//false
// 非单词字符
System.out.println("你".matches("\\W")); // true
System.out.println("---------------------------------------------");
// 以上正则匹配只能校验单个字符。
// 必须是数字 字母 下划线 至少 6位
System.out.println("2442fsfsf".matches("\\w{6,}"));//true
System.out.println("244f".matches("\\w{6,}"));//false
// 必须是数字和字符 必须是4位
System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
System.out.println("23dF".matches("[\\w&&[^_]]{4}"));//true
System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false
}
}
4. 数量词
-
语法示例:
-
X? : 0次或1次
-
X* : 0次到多次
-
X+ : 1次或多次
-
X{n} : 恰好n次
-
X{n,} : 至少n次
-
X{n,m}: n到m次(n和m都是包含的)
-
-
代码示例:
java
public class Demo {
public static void main(String[] args) {
// 必须是数字 字母 下划线 至少 6位
System.out.println("2442fsfsf".matches("\\w{6,}"));//true
System.out.println("244f".matches("\\w{6,}"));//false
// 必须是数字和字符 必须是4位
System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
System.out.println("23dF".matches("[\\w&&[^_]]{4}"));//true
System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false
}
}
5. 数据爬取
请注意,爬取网站的数据时需要谨慎并遵守相关法律法规和网站的规定。在进行爬取操作前,请确保获得了合法的授权,并遵守使用条款和隐私政策。
在Java中,可以使用内置的 java.util.regex
包中的类来处理正则表达式和数据爬取。以下是使用Java正则表达式进行数据爬取的步骤及其讲解:
-
导入相关类和包:
在Java文件的顶部,导入
java.util.regex
包。 -
获取文本数据:
从目标文本源(可能是网页、文档或其他数据源)中获取要进行数据爬取的文本数据。
-
构建正则表达式模式:
基于你想要提取的数据模式,构建相应的正则表达式模式。例如,如果你想要提取文本中的所有URL链接,可以使用
String pattern = "((http|https)://[\\w\\d\\-.]+(\\.[\\w\\d\\-.]+)+(:\\d+)?(/\\S*)?)"
。 -
创建正则表达式对象:
使用
Pattern
类的compile()
方法来创建正则表达式对象。例如:Pattern regexPattern = Pattern.compile(pattern);
。 -
获取匹配结果:
使用正则表达式对象
regexPattern
的matcher()
方法创建匹配器对象,并将目标文本作为参数传递。然后,使用匹配器对象的方法(如find()
、group()
等)来获取匹配结果。 -
处理提取的数据:
根据需要,你可以对提取的数据进行进一步的处理,如整理、清洗、存储等。
下面是一个具体的示例代码,用于从文本中提取URL链接:
java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo {
public static void main(String[] args) {
String text = "这是一个示例文本,其中包含一些URL链接:http://www.example.com 和 https://www.example.org";
String pattern = "((http|https)://[\\w\\d\\-.]+(\\.[\\w\\d\\-.]+)+(:\\d+)?(/\\S*)?)";
Pattern regexPattern = Pattern.compile(pattern);
Matcher matcher = regexPattern.matcher(text);
while (matcher.find()) {
String url = matcher.group();
System.out.println(url);
}
}
}
以上示例将输出两个匹配到的URL链接:http://www.example.com
和 https://www.example.org
。
需要注意的是,在Java中,正则表达式中的反斜杠
\
通常需要进行转义,因此需要使用\\
表示一个单独的反斜杠。
6. 按要求爬取
-
只写+和*表示贪婪匹配,
-
如果在+和后面加问号表示非贪婪爬取
-
+? 非贪婪匹配
-
*? 非贪婪匹配
-
-
贪婪爬取:在爬取数据的时候尽可能的多获取数据
-
非贪婪爬取:在爬取数据的时候尽可能的少获取数据
举例:
java
如果获取数据:ab`+
贪婪爬取获取结果:abbbbbbbbbbbb
非贪婪爬取获取结果:ab
6.1 贪婪爬取
在Java中,贪婪爬取(Greedy Crawling)是指使用爬虫程序从互联网上持续不断地抓取尽可能多的网页数据。贪婪爬取的目标是尽可能地收集更多的数据,以便进行进一步的分析和处理。
贪婪爬取主要涉及以下几个步骤:
-
确定起始点:选择一个或多个起始URL作为初始爬取点,可以是单个网页或一个网站的主页。
-
下载网页:使用Java中提供的网络库(如HttpURLConnection或HttpClient)进行网页的下载,并将下载的网页保存为HTML文档或文本,以供后续处理使用。
-
解析网页:使用HTML解析库(例如Jsoup)解析下载的网页,提取出所需的信息。可以使用选择器来选择特定HTML元素(如链接、文本、图像等),并获取它们的属性或内容。
-
提取链接:从解析后的网页中提取所有的链接,以便进一步扩展爬取的范围。可以使用正则表达式、字符串处理或HTML解析库来提取链接。
-
遍历链接:根据需求,可以使用广度优先搜索(BFS)或深度优先搜索(DFS)等遍历算法,在已有的链接中选择下一个要爬取的页面。对于每个链接,重复步骤2-4,下载、解析并提取链接。
-
重复操作:不断迭代地重复步骤2-5,直到达到预定的爬取深度、爬取时间或其他终止条件。在每个迭代中,将爬取得到的数据保存到文件或数据库中,供后续使用。
需要注意以下几个方面:
-
爬虫的行为需要遵守网站的爬取规则,避免对服务器造成过多的压力,以及遵守相关法律法规。
-
在爬取数据之前,要对目标网站的robots.txt文件进行检查,了解网站对爬虫的访问限制,确保按照网站的要求进行爬取。
-
贪婪爬取可能会导致大量的网络请求,因此需要进行适度的请求速率控制,以免对网络带宽和服务器资源造成过大负载。
6.2 非贪婪爬取
在Java中,非贪婪爬取(Non-Greedy Crawling)是指使用爬虫程序从互联网上抓取特定的网页数据,而不是尽可能多的数据。非贪婪爬取的目标是有目的地选择特定的网页,并只收集所需的数据,以便进行进一步的处理和分析。
非贪婪爬取通常涉及以下几个步骤:
-
确定起始点:选择一个或多个起始URL作为初始爬取点,这些URL可以是单个网页或特定网站的页面。
-
下载网页:使用Java中提供的网络库(如HttpURLConnection或HttpClient)进行网页的下载,并保存为HTML文档或文本文件,以供后续处理使用。
-
解析网页:使用HTML解析库(例如Jsoup)解析下载的网页,提取出所需的信息。可以使用选择器来选择特定的HTML元素(如链接、文本、图像等),并获取它们的属性或内容。
-
提取链接:从解析后的网页中提取特定的链接,以便进一步选择需要爬取的页面。可以使用正则表达式、字符串处理或HTML解析库来提取链接。
-
判断条件:根据需求设定特定的条件,例如只爬取特定类型的网页(如新闻、论坛帖子等),或根据关键词来筛选。
-
过滤数据:根据条件进行数据的筛选和过滤,只保留所需的数据,其他数据可以忽略。
-
重复操作:迭代地重复步骤2-6,根据设定的条件和需求,选择特定的网页进行爬取和数据提取。
需要注意以下几个方面:
-
爬取行为需要遵守网站的爬取规则,避免对服务器造成不必要的负荷,以及遵守相关法律法规。
-
在爬取数据之前,要遵守网站的robots.txt文件和使用条款,确保按照网站的要求进行爬取。
-
非贪婪爬取的目的是有目的地选择特定的网页和数据,因此需要根据需求设定特定条件和过滤规则。
7. 字符串中使用正则表达式的方法
在Java中,可以使用正则表达式来匹配和操作字符串。
方法 | 说明 |
---|---|
matches(String regex) |
判断字符串是否与正则表达式匹配,返回布尔值。 |
replaceAll(String regex, String replacement) |
将符合正则表达式的字符串替换为指定的字符串。 |
replaceFirst(String regex, String replacement) |
将第一个符合正则表达式的子字符串替换为指定的字符串。 |
split(String regex) |
根据正则表达式拆分字符串,并返回分隔后的字符串数组。 |
find() |
在字符串中查找下一个与正则表达式匹配的字串,返回布尔值。 |
group() |
返回上一次匹配操作的匹配结果。若结合find() 方法使用,可以获取所有匹配的字串。 |
replaceAll(String regex, Function<MatchResult, String> replacer) |
使用自定义逻辑替换符合正则表达式的字符串。 |
-
split()
方法细节:-
方法在底层跟之前一样也会创建文本解析器的对象
-
然后从头开始去读取字符串中的内容,只要有满足的,那么就切割。
-
-
replaceAll
方法细节:-
方法在底层跟之前一样也会创建文本解析器的对象
-
然后从头开始去读取字符串中的内容,只要有满足的,那么就用第一个参数去替换。
-
下面是使用正则表达式方法的示例:
java
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
public static void main(String[] args) {
String text = "Hello, my email address is example@email.com";
// 使用matches()方法判断字符串是否匹配正则表达式
boolean isMatch = text.matches(".*@.*\\.com");
System.out.println("Matches: " + isMatch);
// 使用replaceAll()方法替换符合正则表达式的字符串
String replacedText = text.replaceAll("email", "mailbox");
System.out.println("Replaced text: " + replacedText);
// 使用split()方法根据正则表达式拆分字符串
String[] splitText = text.split("\\s+|\\p{Punct}");
System.out.println("Split text: " + Arrays.toString(splitText));
// 使用find()方法查找字符串中匹配正则表达式的子串
Pattern pattern = Pattern.compile("\\w+@\\w+\\.\\w+");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String match = matcher.group();
System.out.println("Match: " + match);
}
// 使用自定义逻辑替换符合正则表达式的字符串
String customizedReplace = matcher.replaceAll((MatchResult result) -> "REPLACED");
System.out.println("Customized replace: " + customizedReplace);
}
}
8. 分组括号
在Java的正则表达式中,分组括号 ()
用于将一部分正则表达式作为一个整体进行匹配、捕获或分组操作。以下是关于分组括号的详细说明:
8.1 匹配和捕获分组
-
(pattern)
:将pattern
组合成一个子表达式,并将其作为一个整体进行匹配。同时,被括号包围的部分也会被捕获到一个组内,供后续处理使用。捕获的组从左到右以1
开始编号。 -
(?:pattern)
:类似于普通的分组,但不会捕获匹配的文本。适用于只需要进行分组但不需要对结果进行处理的情况。
在正则表达式中,匹配和捕获分组是常用的功能:
-
匹配分组:
- 使用圆括号
( )
将模式组合在一起形成一个分组。这样,整个分组将被视为一个整体,与输入文本进行匹配。 - 匹配分组允许对模式的一部分进行重复操作。例如,正则表达式
(abc)+
可以匹配一个或多个连续的 "abc"。
示例:
- 正则表达式
(\d{3})-\d{3}-\d{4}
,用于匹配电话号码的格式为 "XXX-XXX-XXXX",其中(\d{3})
是一个匹配分组,用于匹配三个连续的数字。 - 输入文本 "123-456-7890" 将匹配这个正则表达式,并且匹配分组
(\d{3})
将捕获 "123"。
- 使用圆括号
-
捕获分组:
- 除了用于匹配,分组还可以用于捕获匹配的内容。当匹配成功时,捕获分组中的内容将被捕获并作为结果的一部分返回。
- 使用圆括号
( )
将要捕获的内容包裹起来即可创建一个捕获分组。
示例:
- 正则表达式
(Hello), (world)!
,用于匹配 "Hello, world!" 并将 "Hello" 和 "world" 作为两个捕获分组。 - 输入文本 "Hello, world!" 将匹配这个正则表达式,并且第一个捕获分组
(Hello)
将捕获 "Hello",第二个捕获分组(world)
将捕获 "world"。
-
访问捕获分组:
- 可以使用索引或名称来访问捕获分组中的内容。
- 在大多数正则表达式引擎中,索引从1开始,依次递增。可以通过
group(index)
获取相应索引的捕获分组内容。 - 正则表达式引擎还支持为分组指定名称,使其更容易理解和访问。可以通过
group("name")
或group(name)
来获取指定名称的捕获分组内容。
示例:
- 正则表达式
(\d{2})-(\d{2})-(\d{4})
,用于匹配日期格式为 "MM-DD-YYYY" 的字符串,并将月、日和年分别作为捕获分组。 - 输入文本 "01-19-2024" 将匹配这个正则表达式。可以使用
matcher.group(1)
获取月份 "01",使用matcher.group(2)
获取日期 "19",使用matcher.group(3)
获取年份 "2024"。
8.2 引用和后向引用
\n
:引用第n
个捕获组的内容,其中n
为组的编号。这可用于在正则表达式中引用之前已经匹配的内容。\k<name>
:引用命名捕获组<name>
的内容。
在正则表达式中,引用和后向引用是用于引用和重用已捕获分组的功能。
-
引用:
- 引用允许在正则表达式中引用前面的捕获分组。
- 引用使用反斜线
\
加上分组的索引或名称来表示。 - 引用将使得模式要求匹配与引用的分组完全相同的内容。
示例:
- 正则表达式
(\w+)@\1.com
,用于匹配重复的电子邮件地址。\1
是对第一个捕获分组的引用。 - 输入文本 "abc@abc.com" 将匹配这个正则表达式,因为
\1
引用了第一个捕获分组的内容 "abc"。
-
后向引用:
- 后向引用是一种引用前面捕获分组的内容的方法。
- 后向引用通过在正则表达式中使用
\
加上分组的索引或名称来表示,类似于引用。 - 后向引用将使得模式要求与引用的分组中的内容相同的内容。
示例:
-
正则表达式
<([a-zA-Z]+)>.*<\/\1>
,用于匹配简单的HTML标签,并确保起始标签和结束标签的名称相同。<\/\1>
是对第一个捕获分组的后向引用。 -
输入文本 "This is a paragraph.
" 将匹配这个正则表达式,因为后向引用
<\/\1>
要求结束标签与起始标签的名称相同。
8.3 零宽断言
(?=pattern)
:正向预查。在匹配pattern
之前检查字符串中是否有匹配项,但不消费输入字符串。(?!pattern)
:负向预查。在匹配pattern
之前检查字符串中是否没有匹配项,但不消费输入字符串。(?<=pattern)
:正向后查。在匹配pattern
之后检查字符串中是否有匹配项,但不消费输入字符串。(?<!pattern)
:负向后查。在匹配pattern
之后检查字符串中是否没有匹配项,但不消费输入字符串。
零宽断言(Zero-width assertions)是正则表达式中的一种特殊语法,用于在匹配模式中指定一个位置但不消耗字符。它们提供了一种条件匹配的方式,但不会使匹配结果包括在最终的匹配文本中。下面详解零宽断言的概念并给出示例:
零宽断言由以下几种类型:
-
正向肯定先行断言(Positive lookahead):
(?=pattern)
:匹配一个位置,其后紧跟的内容满足 pattern 才返回匹配结果。- 也称为零宽正预测先行断言。
示例:
- 正则表达式
\w+(?=\.)
,用于匹配一个单词后紧跟着一个句点的情况。(?=\.)
是一个正向肯定先行断言。 - 输入文本 "apple." 中的 "apple" 将匹配这个正则表达式,因为
(?=\.)
要求 "apple" 后面必须是一个句点。
-
正向否定先行断言(Negative lookahead):
(?!pattern)
:匹配一个位置,其后紧跟的内容不满足 pattern 才返回匹配结果。- 也称为零宽负预测先行断言。
示例:
- 正则表达式
\d+(?!\.)
,用于匹配一个数字,但不能后跟一个句点的情况。(?!\.))
是一个正向否定先行断言。 - 输入文本 "123" 将匹配这个正则表达式,因为它不后跟一个句点,而输入文本 "123." 不会匹配。
-
反向肯定后发断言(Positive lookbehind):
(?<=pattern)
:匹配一个位置,其前面紧邻的内容满足 pattern 才返回匹配结果。- 也称为零宽正回顾后发断言。
示例:
- 正则表达式
(?<=https://)\w+
,用于匹配以 "https://" 开头的域名。(?<=https://)
是一个反向肯定后发断言。 - 输入文本 "https://example.com" 中的 "example" 将匹配这个正则表达式,因为
(?<=https://)
要求前面是 "https://"。
-
反向否定后发断言(Negative lookbehind):
(?<!pattern)
:匹配一个位置,其前面紧邻的内容不满足 pattern 才返回匹配结果。- 也称为零宽负回顾后发断言。
示例:
- 正则表达式
(?<!\d)\.\d+
,用于匹配一个小数点前面不是数字的情况。(?<!\d)
是一个反向否定后发断言。 - 输入文本 ".456" 将匹配这个正则表达式,因为它前面不是数字,而输入文本 "1.456" 不会匹配。
8.4 分组嵌套与递归
-
分组可以嵌套使用,以构建复杂的匹配模式。
-
通过使用递归匹配模式,可以让正则表达式重复自身。
分组嵌套和递归是正则表达式中一种高级的功能,用于处理复杂的模式匹配情况。
-
分组嵌套:
-
分组嵌套允许将一个捕获分组放在另一个捕获分组中。
-
分组嵌套使用小括号
( )
来表示,可以在括号内嵌套其他分组。 -
分组嵌套可以用于更复杂的模式匹配并在捕获结果中获取多层的分组内容。
示例:
- 正则表达式
((\w+)\s?)+
,用于匹配多个由单词和可选空格组成的字符串。在这个表达式中,外层分组((\w+)\s?)
嵌套了内层分组(\w+)
。 - 输入文本 "Hello World" 将匹配这个正则表达式,并且可以通过捕获分组来获取 "Hello" 和 "World" 两个单词。
-
-
递归:
-
递归正则表达式允许一个模式在自身中重复出现。
-
递归表达式使用条件语句
(?(condition)yes-pattern|no-pattern)
来表示,其中condition
是一个子模式,以判断是否执行yes-pattern
或no-pattern
。 -
递归模式通过在
yes-pattern
中引用整个正则表达式本身实现。
示例:
- 正则表达式
(\d+(?R)?)+
,用于匹配多个连续的数字。其中(?R)
是对整个正则表达式的递归引用。 - 输入文本 "123456789" 将匹配这个正则表达式,因为它匹配了连续的数字。通过递归引用,这个表达式可以匹配含有多个连续数字的情况,例如 "123456789123"。
-
使用分组嵌套和递归,可以更灵活地构建复杂的模式匹配逻辑。它们为正则表达式提供了更强大的能力,用于解决一些需要多层嵌套或重复出现的匹配问题。然而,需要注意的是,复杂的递归表达式可能会导致性能问题,因此在使用时需要谨慎。
8.5 忽略大小写
在Java正则表达式中,可以通过在正则表达式的开头添加(?i)
标记来实现忽略大小写匹配。这个标记使得整个正则表达式都不区分大小写。
下面是示例代码:
java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IgnoreCaseExample {
public static void main(String[] args) {
String text = "Hello, World!";
Pattern pattern = Pattern.compile("(?i)hello");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
System.out.println("Match found");
} else {
System.out.println("Match not found");
}
}
}
在上面的例子中,
(?i)
标记使得正则表达式中的 "hello" 不区分大小写,所以它可以匹配 "Hello"。输出将会是 "Match found"。
8.6 代码示例
示例1:
java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GroupingExample {
public static void main(String[] args) {
String text = "Hello, my email address is example@email.com";
// 使用捕获分组
Pattern pattern1 = Pattern.compile("(\\w+)@(\\w+\\.\\w+)");
Matcher matcher1 = pattern1.matcher(text);
while (matcher1.find()) {
String fullMatch = matcher1.group(0); // 整个匹配的文本
String username = matcher1.group(1); // 第1个捕获组,匹配邮箱前面的内容
String domain = matcher1.group(2); // 第2个捕获组,匹配邮箱后面的域名部分
System.out.println("Full match: " + fullMatch);
System.out.println("Username: " + username);
System.out.println("Domain: " + domain);
}
// 使用零宽断言
Pattern pattern2 = Pattern.compile("(?=\\bhello\\b)hello, (world)");
Matcher matcher2 = pattern2.matcher("hello, world");
while (matcher2.find()) {
String hello = matcher2.group(0); // 整个匹配的文本
String world = matcher2.group(1); // 第1个捕获组,匹配world
System.out.println("Hello: " + hello);
System.out.println("World: " + world);
}
// 使用递归匹配模式
String nestedText = "{{{{{{text}}}}}}";
Pattern pattern3 = Pattern.compile("(\\{\\g<1>*\\})text");
Matcher matcher3 = pattern3.matcher(nestedText);
while (matcher3.find()) {
String match = matcher3.group(0); // 整个匹配的文本
System.out.println("Match: " + match);
}
}
}
示例2:如何识别组号?
只看左括号,不看有括号,按照左括号的顺序,从左往右,依次为第一组,第二组,第三组等等
java
//需求1:判断一个字符串的开始字符和结束字符是否一致?只考虑一个字符
//举例: a123a b456b 17891 &abc& a123b(false)
// \\组号:表示把第X组的内容再出来用一次
String regex1 = "(.).+\\1";
System.out.println("a123a".matches(regex1));
System.out.println("b456b".matches(regex1));
System.out.println("17891".matches(regex1));
System.out.println("&abc&".matches(regex1));
System.out.println("a123b".matches(regex1));
System.out.println("--------------------------");
//需求2:判断一个字符串的开始部分和结束部分是否一致?可以有多个字符
//举例: abc123abc b456b 123789123 &!@abc&!@ abc123abd(false)
String regex2 = "(.+).+\\1";
System.out.println("abc123abc".matches(regex2));
System.out.println("b456b".matches(regex2));
System.out.println("123789123".matches(regex2));
System.out.println("&!@abc&!@".matches(regex2));
System.out.println("abc123abd".matches(regex2));
System.out.println("---------------------");
//需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致
//举例: aaa123aaa bbb456bbb 111789111 &&abc&&
//(.):把首字母看做一组
// \\2:把首字母拿出来再次使用
// *:作用于\\2,表示后面重复的内容出现日次或多次
String regex3 = "((.)\\2*).+\\1";
System.out.println("aaa123aaa".matches(regex3));
System.out.println("bbb456bbb".matches(regex3));
System.out.println("111789111".matches(regex3));
System.out.println("&&abc&&".matches(regex3));
System.out.println("aaa123aab".matches(regex3));
示例3:
需求:
将字符串:我要学学编编编编程程程程程程。
替换为:我要学编程
java
String str = "我要学学编编编编程程程程程程";
//需求:把重复的内容 替换为 单个的
//学学 学
//编编编编 编
//程程程程程程 程
// (.)表示把重复内容的第一个字符看做一组
// \\1表示第一字符再次出现
// + 至少一次
// $1 表示把正则表达式中第一组的内容,再拿出来用
String result = str.replaceAll("(.)\\1+", "$1");
System.out.println(result);
示例4:
java
//(?i) :表示忽略后面数据的大小写
//忽略abc的大小写
String regex = "(?i)abc";
//a需要一模一样,忽略bc的大小写
String regex = "a(?i)bc";
//ac需要一模一样,忽略b的大小写
String regex = "a((?i)b)c";
示例5 :
非捕获分组:分组之后不需要再用本组数据,仅仅是把数据括起来。
java
//身份证号码的简易正则表达式
//非捕获分组:仅仅是把数据括起来
//特点:不占用组号
//这里\\1报错原因:(?:)就是非捕获分组,此时是不占用组号的。
//(?:) (?=) (?!)都是非捕获分组//更多的使用第一个
//String regex1 ="[1-9]\\d{16}(?:\\d|x|x)\\1";
String regex2 ="[1-9]\\d{16}(\\d Xx)\\1";
//^([01]\d|2[0-3]):[0-5]\d:[@-5]\d$
System.out.println("41080119930228457x".matches(regex2));