Java学习笔记@正则表达式
一、正则表达式入门体验
1.1 实现提取文本中的英文单词
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "";
// 创建Pattrn 对象,即正则表达式对象
Pattern pattern = Pattern.compile("[a-zA-Z]+");
// 创建匹配器对象, 使得 mather匹配器按照 pattern模式 到content文本中匹配
Matcher matcher = pattern.matcher(content);
// 循环匹配
while(matcher.find()){
// 匹配内容,文本放到m.group(0)
System.out.println("找到: " + matcher.group(0));
}
}
}
1.2 提取数字
Pattern pattern = Pattern.compile("[0-9]+");
1.3 提取数字和英文字母
Pattern pattern = Pattern.compile("([0-9]+)|(a-zA-Z+)");
1.4 提取百度热搜标题
context是在百度随便一个页面的源代码里复制的,仅仅是为了测试正则表达式的强大功能。
Pattern pattern = Pattern.compile("<a target=\"_blank\" title=\"(\\S*)\"");
1.5 取 IP 地址
Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.+\\d+");
二、Matcher.find()
-
根据指定的规则定义满足规则的 子字符串,如2021
-
找到后,将子字符串开始的索引记录到matcher对象的属性
int [] groups;把该子字符串的结束索引 + 1 的值记录到groups[1] -
同时记录
oldlast的值为 子字符串结束的索引 + 1,即下次执行find的开始位置 -
group方法源码:
public String group(int 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]记录的位置,从 content开始截取子字符串返回
- 若再次执行
find方法,仍按上述分析执行
三、正则表达式的分组
分组是什么? 如 (\d\d)(\d\d) ,正则表达式中有 () 则表示分组 ,第1个 () 表示 第 1 组, 第 2 个 () 表示 第 2组
- 根据指定规则,定位满足规则的子字符串,比如: (20) (21)
- 找到后,将子字符串开始索引记录到
mathcer对象的int [] groupsgroups[0] = 0, 把该子字符串结束的索引+1的值记录到groups[1]- 记录第1组
()匹配到的字符串groups[2] = 0groups[3] = 2 - 记录第2组
()匹配到的字符串groups[4] = 2groups[5] = 4 - 如果有更多的分组,以此类推...
四、正则表达式语法
4.1 六大元字符
限定符、选择匹配符、分组组合和反向引用符、特殊字符、字符匹配符、定位符
4.2 特殊字符
什么时候会用到转义号 \\ ?
当我们使用正则表达式去检索某些特殊字符的时候,不用转义号的话会检索不到结果,甚至会报错。
其他语言一般是用单个斜杠。
**特殊字符 **有 : . * + ( ) $ / \ ? [ ] ^ { }
4.3 字符匹配符
| 符号 | 含义 | 示例 | 解释 |
|---|---|---|---|
[ ] |
可接受的字符列表 | [efgh] |
e、f、g、h中的任意1个字符 |
[ ^ ] |
不接受的字符列表 | [^abc] |
除 a、b、c之外的任意1个字符,包括数字和特殊符号 |
- |
连字符 | A-Z |
任意单个大写字母 |
| 符号 | 含义 | 示例 | 说明 | 匹配输入 |
|---|---|---|---|---|
. |
匹配除n以外的任何字符 |
a..b |
以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 | aaab、aefb、a35b、a#*b |
\\d |
匹配单个数字字符,相当于[0-9] |
\\d{3}(\\d)? |
包含3个或4个数字的字符串 | 123 、9876 |
\\D |
匹配单个非数字字符,相当于[^0-9] |
\\D(\\d)* |
以单个非数字字符开头,后接任意个数字字符串 | a、A342 |
\\w |
匹配单个数字、大小写字母字符和下划线,相当于[0-9a-zA-Z] |
\\d{3}\\\w{4} |
以3个数字字符开头的长度为7的数字字母字符串 | 234abcd 、 12345Pe |
\\W |
匹配单个非数字、大小写字母字符,相当于[^0-9a-zA-Z] |
\\W+\\d{2} |
以至少1个非数字字母字符开头,2个数字字符结尾的字符串 | #29 、#?@10 |
\\s |
匹配任何空白字符(空格,制表符TAB等) | |||
\\S |
匹配任何非空白字符,和\s 相反 |
4.3.1 应用案例
案例1 代码测试[a-z] 的正则匹配
[a-z] 说明[a-z] 表示可匹配a-z 中任意一个字符,比如 [a-z]、 [A-Z] 去匹配 a11c8 会得到什么结果?
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "a11c8";
String regStr1 ="[a-z]";
String regStr2 = "[A-Z]";
Pattern pattern = Pattern.compile(regStr1);
System.out.println(regStr1 + " 的结果: ");
print(regStr1, content);
System.out.println(regStr2 + " 的结果: ");
print(regStr2, content);
}
public static void print(String reg, String c){
Pattern p= Pattern.compile(reg);
Matcher matcher = p.matcher(c);
while(matcher.find()){
System.out.println("找到: " + matcher.group(0));
}
}
}
运行结果:
[a-z] 的结果:
找到: a
找到: c
[A-Z] 的结果:
案例2 字母大小写问题
java正则表达式 默认是区分字母大小写 的,如何实现不区分大小写?
(?i) abc 表示 abc 都不区分大小写
a(?i) bc 表示 bc 不区分大小写
a((?i)b)c 表示 只有b 不区分大小写
另外一种方法 , 在创建pattern 对象时多加一个参数
Patten pat = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
此时指定匹配时都不区分字母大小写
案例3 ^ 符号的运用
[^ a-z] 表示可以匹配不是a-z中的任意一个字符,比如用它去匹配a11c8 看看结果?用[^a-z]{2} 呢?
由于和案例2类似就不敲代码了,需要注意的是后面的{2} 是表示需要 **连续两个 **同时满足的条件
4.4 选择匹配符
在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以匹配那个,这是需要用到 选择匹配符号|
| 符号 | 符号 | 示例 | 解释 |
|---|---|---|---|
| ` | ` | 匹配` | ` 之前之后的表达式 |
4.5 限定符
| 符号 | 含义 | 示例 | 说明 | 匹配输入 |
|---|---|---|---|---|
* |
指定字符重复0次或 n 次(无要求) | (abc)* |
仅包含任意个abc的字符串,等效于\w* |
abc、abcabcabc |
+ |
指定字符重复1次或n次(至少1次) | m+(abc)* |
以至少1个m开头,后接任意个abc的字符串 | m、mabc、mabcabc |
? |
指定字符重复0次或1次(最多1次) | m+abc? |
以至少1个m开头,后接ab或abc的字符串 | mab、mabc、mmmab、mmabc |
{n} |
只能输入n个字符 | [abcd]{3} |
由abcd中字母组成的任意长度为3的字符串 | abc、dbc、adc |
{n, } |
指定至少 n 个匹配 | [abcd]{3, } |
由 abcd中字母组成的任意长度不小于3的字符串 | aab、dbc、aaabdc |
{n, m} |
必定至少n个但不多于m个匹配 | [abcd]{3,5} |
由abcd中字母组成的任意长度不小于3且不大于5的字符串 | abc、abcd、aaaaa、bcdab |
4.6 贪婪匹配
Java匹配默认是贪婪匹配,即:尽可能匹配多的元素
非贪婪匹配符号?
当字符? 紧随任何其他限定符(*、+、?、{n}、{n, }、{n,m}) 之后时,匹配模式是“非贪心的"。
即匹配尽可能短的字符串 ,例如:
在字符串oooo 中 ,o+? 只匹配单个o ,而o+ 匹配所有o
4.7 定位符
| 符号 | 含义 | 示例 | 说明 | 匹配输入 |
|---|---|---|---|---|
^ |
指定起始字符 | ^[0-9]+[a-z]* |
以至少1个数字开头,后接任意个小写字母的字符串 | 123、6aa、555edf |
| ` | 符号 | 含义 | 示例 | 说明 |
| ----- | ---------------------- | ------------------ | ------------------------------------------------------------ | ---------------------- |
^ |
指定起始字符 | ^[0-9]+[a-z]* |
以至少1个数字开头,后接任意个小写字母的字符串 | 123、6aa、555edf |
| 指定结束字符 | ^[0-9]\\-[a-z]+$ |
以1个数字开头后接连字符- ,并以至少1个小写字母结束的字符串 |
1-a |
||
\\b |
匹配目标字符串的边界 | cc\\b |
这里说的字符串的边界指的是字串间有空格,或者是目标字符串的结束位置 | hhcc、 ppcc |
\\B |
匹配目标字符串的非边界 | cc\B |
和\b的含义刚刚相反 |
ccy |
4.8 分组
| 常用分组构造形式 | 说明 |
|---|---|
(pattern) |
非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其他捕获结果则根据左括号的顺序从开始自动编号。 |
(? < name> pattern) |
命名捕获,将匹配的子字符串捕获到一个组名称或编号名称中。用于name 的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号。例如 ?('name') |
4.8.1 非命名捕获
import sun.security.rsa.RSAUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "test2021pkpk1314";
/* 下面为非命名分组
说明
1. matcher.group(0) 得到匹配得到的字符串
2. matcher.group(1) 得到匹配的字符串的第1个分组内容
... 以此类推
*/
String regStr = "(\\d\\d)(\\d\\d)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("找到 = " + matcher.group(0));
System.out.println("第一组内容: " + matcher.group(1));
System.out.println("第二组内容: " + matcher.group(2));
}
}
}
输出结果:
找到 = 2021
第一组内容: 20
第二组内容: 21
找到 = 1314
第一组内容: 13
第二组内容: 14
4.8.2 命名分组
import sun.security.rsa.RSAUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "test2021pkpk1314";
/* 下面为非命名分组
说明
1. matcher.group(0) 得到匹配得到的字符串
2. matcher.group(1) 得到匹配的字符串的第1个分组内容
... 以此类推
*/
String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("找到 = " + matcher.group(0));
System.out.println("第一组内容: " + matcher.group("g1"));
System.out.println("第二组内容: " + matcher.group("g2"));
}
}
}
输出结果和上述相同
4.8.3. 特别的非捕获分组
| 常用分组构造形式 | 说明 |
|---|---|
(?:pattern) |
匹配pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用or字符(I) 组合模式不见的情况很有用,例如 `industr(?:y |
(?=pattern) |
非捕获匹配。例如`Windows(?=95 |
(?!pattern) |
该表达式匹配不处于匹配pattern 的字符串的起始点的搜索字符串也是一个非捕获匹配。例如`Windows(?!95 |
import sun.security.rsa.RSAUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "qwtoi测试1qwti测试2哦哦测试3qwtp";
String regStr = "测试(?:1|2|3)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find())
System.out.println("找到 = " + matcher.group(0));
}
}
输出结果:
找到 = 测试1
找到 = 测试2
找到 = 测试3
五、正则表达式应用示例
5.1 检查是否全为汉字
正则表达式:^[\u0391-\uffe5]+$
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
print("测试一下");
print("测试1下");
print("ok");
}
public static void print(String content){
String regStr = "^[\u0391-\uffe5]+$";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
System.out.print(content +" ->检查结果: ");
System.out.println(matcher.find() ? "YES": "NO");
}
}
输出结果:
测试一下 ->检查结果: YES
测试1下 ->检查结果: NO
ok ->检查结果: NO
5.2 检查是否为5-10位数字
思路:限制开头是1-9的数字,之后用{} 限制个数和结尾
正则表达式:^[1-9]\\d{4, 10}$
5.3 检查手机号码
要求:必须以13,14,15,18开头的11位,比如13366667777
正则表达式:^1[3|4|5|8]\\d{9}$
5.4 分析B站视频网站的URL
测试文本
提取头 和 BV号
正则表达式:^(?<g1>(https|http)://)([\\w.]+\\/)+(?<BV>BV+[\\w]+)+
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "https://www.bilibili.com/video/BV1ov4115757";
String regStr = "^(?<g1>(https|http)://)([\\w.]+\\/)+(?<BV>BV+[\\w]+)+";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()) {
System.out.println(matcher.group("g1"));
System.out.println(matcher.group("BV"));
}
}
}
六、正则表达式三大常用类
6.1. Pattern类
pattern对象是一个正则表达式对象。Pattern类没有公共构造方法。要创建一个Pattern对象,调用其公用静态方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。即:Pattern pt = Pattern.compile(regStr);
matches 方法
返回值类型:boolean
作用:返回一个字符串的整体是否与正则表达式匹配,不用规定开头,默认从0开始匹配。
例如:
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "hello qwt 额";
String regStr = ".*qw.*";
System.out.println("整体匹配结果: " + Pattern.matches(regStr, content));
}
}
输出结果:
整体匹配结果:true
matches方法 源码:
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
public boolean matches() {
return match(from, ENDANCHOR);
}
6.2 Matcher类
Matcher 对象是对输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher也没有公共构造方法。需要调 matcher 方法来获得一个Matcher 对象,即:Matcher matcher = pattern.matcher(content);
常见方法
方法一览 其中 public省略
| 方法名 | 说明 |
|---|---|
int start() |
返回以前匹配的初始索引 |
int start(int group) |
返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引 |
int end() |
返回最后匹配字符之后的偏移量 |
end(int group) |
返回在以前的匹配操作期间,由给定组所捕获的子序列的最后字符偏移量+1 |
boolean lookingAt() |
尝试将从区域开头开始的输入序列与该模式匹配 |
find() |
尝试查找与该模式匹配的输入序列的下一个子序列 |
find(int start) |
重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列 |
boolean mathes() |
尝试将整个区域与模式匹配 |
Matcher appendReplacement(StringBuffer x, String replacement) |
实现非终端添加和替换步骤 |
StringBuffer appendTail(StringBuffer x) |
实现终端添加和替换步骤 |
String replaceAll(String replacement) |
替换模式与给定替换字符串相匹配的输入序列的每个子序列,不改变原字符串 |
String replaceFirst(String replacement) |
替换模式与给定替换字符串匹配的输入序列的第一个子序列 |
static String quoteReplacement(String s) |
返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher 类的appendReplacement 方法一个字面字符串一样工作。 |
6.3 PatternSyntaxException类
一个 非强制异常 类 ,它表示一个正则表达式模式中的语法错误。
七. 分组、捕获、反向引用三者的关系
7. 1 概念
分组
可用 圆括号 ()组成一个比较复杂的匹配模式,那么一个圆括号()的部分就可看做是一个 子表达式 / 分组
捕获
把正则表达式中 子表达式 / 分组匹配的内容,保存到内存中以数字编号或显示命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第1个出现的分组组号为1,第2个为2,以此类推,组0代表的则是整个正则表达式
反向引用
圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较使用的匹配模式,这个操作称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式 外部,内部反向引用 \\分组号,外部反向引用 $ 分组号
7.2 反向引用案例
内部案例
-
匹配两个连续的相同数字 :
(\\d)\\1 -
匹配五个连续的相同数字:
(\\d)\\1{4} -
匹配个位与千位相同,十位与百位相同的数:(5225,1551)
(\\d)(\\d)\\2\\1 -
检索一个商品编号,形式如:12321-333999111这样的号码,即:
要求满足前面为五位数、后面为九位数、中间用 - 号连接,并且九位数为AAABBBCCC的形式
(\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2})
外部引用去重
经典结巴程序
把 类似: 我...我要....学学学学....编程java!
通过正则表达式 改成我要学编程java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "我...我要....学学学学....编程java";
String regStr = "\\.";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
// 1. 去掉所有的点
content = matcher.replaceAll("");
// 2. 去掉重复的字
/*
思路: (1) 使用(.)\\1+ 查找2个及以上连续且相同的字符
(2) 使用反向引用 $1 来替换匹配到的内容
*/
pattern = Pattern.compile("(.)\\1+"); // 分组的捕获内容记录到 $1
matcher = pattern.matcher(content);
// 反向引用 替换
content = matcher.replaceAll("$1");
System.out.println(content);
}
}
输出结果:
我要学习编程java
简化版本
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
String content = "我...我要....学学学学....编程java";
String regStr = "\\.";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
System.out.println(
Pattern.compile("(.)\\1+").
matcher(matcher.replaceAll("")).
replaceAll("$1"));
}
}
八、梦幻联动 | String类中使用正则表达式
8.1 replaceAll () 方法
将文本
2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布," +
"几周后其获得了Apple公司Mac OS X的工业标准的支持。
中的JDK1.3 和 JDK1.4 替换成 JDK
public class Main{
public static void main(String[] args) {
String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布," +
"几周后其获得了Apple公司Mac OS X的工业标准的支持。";
System.out.println(content.replaceAll("JDK1\\.3|JDK1\\.4", "JDK"));
}
}
8.2 matches () 方法
验证一个手机号,要求必须以133、132开头
public class Main{
public static void main(String[] args) {
String[] p = {"13212341234", "133", "1331224123", "132123412345","0"};
for(String s : p)
System.out.println(
s.matches("1(33|32)\\d{8}") ?
s + "->True":
s + "->False");
}
}
输出结果:
13212341234->True
133->False
1331224123->False
132123412345->False
0->False
8.3 split()方法
按照 # 或者 - 或者 ~ 或者 数字分割字符串
public class Main{
public static void main(String[] args) {
String content = "hello#abc-java8test~加油";
for(String x : content.split("#|-|~|\\d+"))
System.out.println(x);
}
}
输出结果:
hello
abc
java
test
加油
九、练习巩固 | 又觉得自己行了吗?先来试试这些案例
9.1 验证电子邮件
规定电子邮件的规则为:
- 只能有一个@
- @前面为用户名, 可以是a-z 、 A-Z、0-9、-_字符
- @后面为域名,并且域名只能是英文字母,如:sohu.com
- 写出对应的正则表达式,验证输入的字符串是否为满足规则
正则表达式:[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
print();
String x = in.next();
while(!"q".equals(x)){
System.out.println(isOK(x) ? "YES" : "NO");
print();
x = in.next();
}
}
public static void print(){
System.out.println("电子邮件检验系统 -> 输入邮箱直接检验,输入q退出系统");
}
public static boolean isOK(String content){
String regStr = "[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+";
return content.matches(regStr); // 整体匹配
}
}
9.2 验证整数、小数、整数负数
RT,特殊情况, +- 可表示数字的符号,数字不能由多余的前导0,如01.1 非法
正则表达式:^[-+]?([1-9]\\d*|0)(\\.\\d+)?$
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
print();
String x = in.next();
while(!"q".equals(x)){
System.out.println(isOK(x) ? "YES" : "NO");
print();
x = in.next();
}
}
public static void print(){
System.out.println("数字检验系统 -> 输入数字直接检验,输入q退出系统");
}
public static boolean isOK(String content){
String regStr = "^[-+]?([1-9]\\d*|0)(\\.\\d+)?$";
return content.matches(regStr); // 整体匹配
}
}
9.3 全面解析URL
协议、域名、端口、文件名
前提:网址无特殊符号
输入样例1
输出样例1
整体匹配= http://www.baidu.com:8080/abc/test/index.html
协议: http
域名: www.baidu.com
端口: 8080
文件: index.html
输入样例2
输出样例2
整体匹配= http://www.baidu.com:8080/abc/test///index.html
协议: http
域名: www.baidu.com
端口: 8080
文件: index.html
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
print();
String x = in.next();
while(!"q".equals(x)){
run(x);
print();
x = in.next();
}
}
public static void print(){
System.out.println("URL分析系统 -> 输入网址直接分析,输入q退出系统");
}
public static void run(String content){
String regStr = "^([a-zA-Z]+)://([a-zA-Z.]+):(\\d+)[\\w-/]*/([\\w.]+)$";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if(matcher.matches()){ // 整体匹配
System.out.println("整体匹配= " + matcher.group(0));
System.out.println("协议: " + matcher.group(1));
System.out.println("域名: " + matcher.group(2));
System.out.println("端口: " + matcher.group(3));
System.out.println("文件: " + matcher.group(4));
} else
System.out.println("分析结果为空.");
}
}

浙公网安备 33010602011771号