引用
Java正则表达式介绍
正则表达式(Regular Expression)—— 是一种对字符串操作的逻辑公式,用事先定义好的特殊字符和特殊字符组合来组成一个“规则字符串”。再利用这个“规则字符串”来表达对字符串的过滤。
正则表达式不是只java才有,很多语言都支持正则表达式进行字符串操作。
Java.until.regex包主要包括如下三个类
- Pattern类
pattern对象是一个正则表达式的编译表示。Pattern类没有公共构造方法(意思是不能new Pattern
来构造对象)。要创建一个Pattern对象,必须首先调用其公共静态编译方法Pattern.complie(regex)
,返回一个Pattern对象。该方法接收一个正则表达式作为它的第一参数
//可以直接调用Pattern类的matchas()方法,进行全匹配!(指整个字符串都满足正则表达式)
String content = "123";
String regStr = "//d+"
System.out.println(Pattern.matchas(regStr,content))
//该方法会返回一个Boolean值,这里会打印出true。它不会返回匹配到的字符串
- Matcher类
Matcher对象是对输入字符串进行解释和匹配操作的引擎。与Pattern类一样,Matcher也没有公共构造方法。需要调用Pattern对象的matcher方法来获得一个Matcher对象,pattern.matchar(content)
实例
public static void main(String[] args) {
String content = "mikasa biubu hellow word mikasagaf";
String regStr = "mikasa";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("======");
System.out.println(matcher.start());
System.out.println(matcher.end());
System.out.println("字符:"+content.substring(matcher.start(), matcher.end()));
}
//返回的结果为
//======
//0
//6
//字符:mikasa
//======
//25
//31
//字符:mikasa
//整体匹配,常用于验证某个字符串是否满足某个规则
System.out.println("整体匹配:"+matcher.matches());//这里返回false
//将匹配到的mikasa替换为eren
String newContent = matcher.replaceAll("eren");
System.out.println(newContent);
//打印内容:eren biubu hellow word erengaf
}
- PatternSyntaxException是一个非强制异常类,他表示一个正则表达式模式中的语法错误
以下实例使用正则表达式查找文本字符串中所有连续的4个数字
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Demo01 {
public static void main(String[] args) {
String content = "出生于医学世家;1958年8月,在第一届全运会的比赛测验中,以54秒2的成绩,打破了当时54秒6的400米栏全国纪录。" +
"1960年毕业于医学院(;2007年获英国荣誉博士;2007年10月重点实验室主任;" +
"2014年获香港荣誉理学博士2019年被聘为中国医学科学院学部委员2020年8月11" +
"予2020年度科学与技术成就奖”。";
//目标:匹配所有的四个数字(年份)
//1、编写正则
String regStr = "\\d\\d\\d\\d";//说明:\\d表示一个任意的数字
//2、创建模式对象[正则表达式对象]
Pattern pattern = Pattern.compile(regStr);
//3.创建一个匹配器
//说明:创建匹配器matcher,按照 正则表达式规则 去匹配 content字符串
Matcher matcher = pattern.matcher(content);
//4、开始匹配
while(matcher.find()){
System.out.println("找到:"+ matcher.group(0));
}
}
}
- 这里详细说明一下
matcher.find()
完成的任务:
-
根据指定的规则,定位满足规则的子字符串(如第一个匹配的就是1958)
-
找到后,将子字符串开始的索引记录到matcher对象的属性
int[] groups
,groups[0]=11把该子字符串结束的索引+1的值记录到groups[1]=15 -
同时记录
oldLast
的值,即子字符串结束的索引+1的值。下次执行find方法时即从oldLast开始匹配
-
对
matcher.group(0)
分析- 根据groups[0]=11和group[1]=15记录的位置,从content开始截取子字符串的范围就是[11,15),包含11但是不包含15
//matcher对象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();
}
java正则表达式中的分组
java中也叫做捕获组,是指把多个字符当作一个单独单元处理的方法,它通过对括号内的字符分组来创建。例如,正则表达式(mikasa)
就创建了单一分组
捕获组是通过从左自右计算其括号开始编号的,例如在((A)(B(C)))
,有四个这样的组:
-
group(1)为 ((A)(B©))
-
group(2)为 (A)
-
group(3)为 (B©)
-
group(4)为 ©
可以通过调用matcher对象的groupCount方法来查看表达式中有多少个分组,groupCount 方法返回一个 int 值,表示matcher对象当前有多个捕获组。还有一个特殊的分组也就是
matcher.group(0)
,他总是代表整个表达式。该组不包括在groupCount的返回值中
实例代码(将上面的代码中的正则表达式变为(\\d\\d)(\\d\\d)
)
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Demo01 {
public static void main(String[] args) {
String content = "出生于医学世家;1958年8月,在测验中,以54秒2的成绩,打破了当时54秒6的400米栏全国纪录。" +
"1960年毕业于医学院);2007年获英国荣誉博士;2007年10月实验室主任;" +
"2014年获香港中文大学荣誉理学博士2019年被聘为委员2020年8月11日" +
"选2020年“全国教书育人楷模”名单。"+
"予2020年12月15日,获颁香港理工大学荣誉博士学位;12月19日,获授澳门特区政府颁发的大莲花荣誉勋章。";
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("组1:"+ matcher.group(1));
System.out.println("组2:"+ matcher.group(2)
}
}
}
第一次find()
Java正则表达式语法
如果想要灵活的运用正则表达式,必须了解其中各种元字符的功能,元字符从功能上大致分为
-
限定符
-
选择匹配符
-
分组、捕获、反向引用
-
特殊字符
-
字符匹配符
-
定位符
java中的转义符是与其他语言有区别的!
在其他语言中,\\
表示:在正则表达式中插入一个普通的(字面上的)反斜杠,无任何特殊意义)
在java中,\\
表示:在正则表达式中插入一个反斜杠,所以其后的字符具有特殊意义
限定符
用于指定其前面的字符或组合项连续出现了多少次
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
* | 零次或多次匹配前面的字符或子表达式。等效于{0,} | zo* | zo*匹配z和zo、zoo、zooo…。 |
+ | 一次或多次匹配前面的字符或子表达式。等效于{1,} | m+(abc)+ | 以至少一个m开头,后接任意个abc的字符串 |
? | 零次或一次匹配前面的字符表达式。 | m+abc? | 以至少一个m开头 ,后接ab或abc的字符串 |
{n} | n是非负整数。正好匹配n次。 | [abcd]{3} | 由abcd中字母组成的任意长度为3的字符串 |
{n,} | n是非负整数。至少匹配n次。 | [abcd]{3,} | 由abcd中字母组成的任意长度不小于3的字符串 |
{n,m} | n和m是非负整数,其中n<=m。至少匹配n次,至多匹配m次。 | [abcd]{3,5} | 由abcd中字母组成的任意长度不小于3不大于5的字符串 |
选择匹配符
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
| | 匹配 “|” 之前或之后的表达式 | ab|cd | ab或者cd |
分组组合和反向引用符
组就是把多个字符当作单一的单元。分组是通过在()
小括号内放置正则表达式来创建的,每对小括号对应一个组。
捕获是把正则表达式中分组匹配(或者说是子表达式)中的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用
- 常用分组
常用分组构造形式 | 说明 |
---|---|
(pattern) | 非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号 |
(?pattern) | 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头。可使用单引号代替尖括号,例如(?‘name’ |
非命名捕获分组在上面讲述中已经提及过。下面通过一个具体实例讲述命名分组
public class Demo02 {
public static void main(String[] args) {
String content = "1960年毕业于北京医学院(今北京大学医学部);2007年获英国爱丁堡大学荣誉博士";
String regex = "(?<group1>\\d\\d)(?<group2>\\d\\d)";
//注:组的命名不能使用中文!
Pattern pattern = Pattern.compile(regex);
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("group1"));
System.out.println("第而个分组的内容"+matcher.group(2));
System.out.println("第而个分组的内容(通过组名的方式获取)"+matcher.group("group2"));
System.out.println();
}
}
}
运行结果为:
- 特别分组(非捕获分组,不能使用matcher.group(1))
特别分组构造形式 | 说明 |
---|---|
(?:pattern) | 匹配pattern,但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用“or”字符(|)组合模式部件的情况很有用。例如,'indust(?:y|ies) 是比industy|industries更优的表达式 |
(?=pattern) | 也是一个**非捕获匹配。**例如,“windows(?=95|98|NT|2000)” 能匹配"windows2000" "windows95"中的windows,但不能匹配"windows2001"中的windows |
(?!pattern) | 该表达式匹配不处于匹配pattern字符串的起始点的搜索字符。它是一个 非捕获匹配( “!” 可以理解为取反)。例如windows(?=95|98|NT|2000)" 能匹配"windows2001" 中的windows,但不能匹配"windows2000"中的windows |
实例
String content = "mikasa13biubiubiu";
String regex = "mikasa(?:13|14)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("匹配到了"+matcher.group(0));
//System.out.println("匹配到了"+matcher.group(1));
}
//打印出的内容为:mikasa13
这里需要说明一点,正则表达式mikasa(?:13|14)
与mikasa(13|14)
是会打印相同的结果,但区别在哪呢?前者是一个非捕获匹配,特殊分组,不捕获该匹配的子表达式,也就是说打印group(1)
会出现报错。但后者是非命名捕获匹配,常用分组,打印group(1)
的结果是13。
String content = "mikasa13biubmikasa15iubiu";
String regex = "mikasa(?=13|14)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("匹配到了"+matcher.group(0));
}
//打印出的内容只有一个:mikasa
//实际上匹配到的是mikasa13中的mikasa
String content = "mikasa13biubmikasa15iubiu";
String regex = "mikasa(?=13|14)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("匹配到了"+matcher.group(0));
}
//打印出的内容只有一个:mikasa
//实际上匹配到的是mikasa15中的mikasa
- 反向引用
反向引用是基于分组的,它允许重复正则而不需要再写一次。可以通过\\#
来引用前面定义的组,# 是组的序号,从1开始。正则引擎在处理匹配时,要求反向引用的组匹配的内容必须是一样的:即 (\\d\\d\\d)\\1
会匹配到123123,而不会匹配123456
实例
匹配两个连续相同的数字: (\\d)\\1
匹配五个连续相同的数字:(\\d)\\1{4}
匹配一个四位数,要求个位和千位相同,十位和百位相同:(\\d)(\\d)\\2\\1
匹配类似12321-333999111的商品编码。要求满足前面是一个五位数,然后是一个-,再是一个九位数要求连续三位要相同:\\d{5}-(\\d)\\1{1}(\\d)\\2{2}(\\d)\\3{2}
字符匹配符
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
[ ] | 指可接受的字符集 | [efgh] | 表示e、f、g、h中的任意一个字符 |
[^] | 不接收的字符集 | [^abc] | 除a、b、c之外的任意一个字符,包括数字和特殊符号 |
- | 连字符 | A-Z | 任意单个大写字符 |
. | 通配符,匹配除\n以外的任何字符 | a…b | 以a开头,b结尾,中间包括2个任意字符且长度为4的字符串 |
\d | 匹配单个数字字符,相当于[0-9] | \d{3}(\d)? | 包含三个或四个数字的字符串 |
\D | 匹配单个非数字字符,相当于[^0-9] | \D(\d)* | 以单个非数字字符开头,后接任意个数字的字符串 |
\w | 匹配单个数字、大小写字母字符,相当于[0-9a-zA-Z] | \d{3}\w{4} | 以三个数字字符开头长度为7的数字字母字符 |
\W | 匹配单个非数字、大小写字母字符,相当于[^0-9a-zA-Z] | \W+\d{2} | 以至少1一个非数字、字母开头,2个数字字符结尾的字符串。如#29、#¥%10 |
定位符
规定要匹配的字符串出现的位置,比如在字符串的开始还是结束的位置
符号 | 含义 | 示例 | 解释 |
---|---|---|---|
^ | 指定起始符 | ^\d+[a-z]* | 以至少一个数字开头,后面接任意个小写字母的字符串 |
$ | 指定结束符 | ^\d\-[a-z]+$ | 以一个数字开头,后接连接字符“-”,并以至少一个小写字母结尾的字符串 |
\b | 匹配目标字符串的边界 | han\b | 这里说的字符串边界指的是子串间有空格,或者是目标字符串结束的位置 |
\B | 匹配目标字符串的非边界 | han\B | 和\b的含义相反 |
一些实例
-
匹配字符串是否全是汉字
^[\u0391-\uffe5]$
-
邮政编码(1-9开头的6位数)
^[1-9]\\d{5}$
-
手机号(必须13,14,15,18开头)
^1[3458]\\d{9}$
-
url地址
^http[s]?://([\\w]+\\.)+[\\w]+(/[\\w-_=?#&.\*%]\*)\*$
-
ip地址(ipv4共四段,每段用
.
分隔,是0-255间的十进制数)- 将0-255用正则表达式表示,需要分段考虑
取值区间 | 正则写法 |
---|---|
250-255 | 25[0-5] |
200-249 | 2[0-4]\d |
100-199 | 1\d{2} |
0-99 | [1-9]?\d |
-
- 合并后的表达式为
((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)
- 实际上在这基础上是还能简化的,虽然ip地址中每个数字都不能大于255,但像
01.02.03.04
,这种前面带有0的数字也是ip地址。ip地址里的数字可以包含有前导0。所以可以将0-199归为一整段为([01]?\\d?\\d)
。简化后的表达式为((25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d?\\d)
- 合并后的表达式为
-
结巴去重
- 这里运用到了反向引用的知识点,内部的反向引用用
//1
,外部的用$1
- 把类似:“我…我要…学学学学…Java“,通过正则表达式修改为“我要学Java”
- 这里运用到了反向引用的知识点,内部的反向引用用
public static void main(String[] args) {
String content = "我...我要...学学学学....Java";
//1.去掉所有的.
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(content);
String content1 = matcher.replaceAll("");
//2.去除重复的汉字
//用"(.)\\1+"匹配重复的数字
//用 外部的反向引用$1来替换匹配到的内容
pattern = Pattern.compile("(.)\\1+");
matcher = pattern.matcher(content1);
while(matcher.find()){
System.out.println("匹配到的内容是:"+matcher.group(0));
}
//打印内容是
// 匹配到的内容是:我我
// 匹配到的内容是:学学学学
//替换
String content2 = matcher.replaceAll("$1");
System.out.println(content2);
//我要学Java
}
String类中的正则表达式
- 替换功能
public String **replaceAll**(String regex,String replacement)
//将Java7和Java8用Java代替
String content = "2011年,甲骨文公司举行了全球性的活动,以庆祝Java7的推出,随后Java7正式发布。2014年,甲骨文公司发布了Java8正式版";
String content1 = content.replaceAll("Java[78]","Java");
System.out.println(content1);
- 判断功能(进行整体匹配)
public boolean matches(String regex)
//要求验证一个手机号,要求必须是以138 139开头的
String content2 = "13821231341";
if(content2.matches("13[89]\\d{8}")){
System.out.println("是一个手机号");
}
- 分割功能
public String[] split(String regex)
String content3 = "hello#bob-I2am&lihua";
String[] s = content3.split("#|-|\\d|&");
for (String ss : s) {
System.out.println(ss);
}
//打印出的结果为:
//hello
//bob
//I
//am
//lihua