目录
一、步骤
二、底层源码
三、正则表达式语法
四、正则表达式的三个常用类
五、String中使用正则表达式演示
六、总结
一、步骤
📌代码演示
public class RegTheory {
public static void main(String[] args) {
String regStr = "(\\d\\d)(\\d\\d)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher("1999sAsdadasd123adasc1326asdA");
while (matcher.find()){
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
System.out.println(matcher.group(2));
}
}
}
二、底层源码
关于matcher.groups()
中的参数为0,1…的讨论❓
// search(nextSearchIndex)
boolean search(int from) {
this.hitEnd = false;
this.requireEnd = false;
from = from < 0 ? 0 : from;
this.first = from;
this.oldLast = oldLast < 0 ? from : oldLast;
for (int i = 0; i < groups.length; i++)
groups[i] = -1;
acceptMode = NOANCHOR;
boolean result = parentPattern.root.match(this, from, text);
if (!result)
this.first = -1;
this.oldLast = this.last;
return result;
}
1. 无分组
matcher.find()
→search(nextSearchIndex)
。真正找到索引的是search()
。- 根据指定的规则,定位满足规则的子字符串(比如1998)。
- 找到后,将子字符串的开始的索引记录到 matcher对象的属性
int[] groups
;groups[0] = 0
,把该子字符串的结束的索引+1
的值记录到 groups[1] =4。 - 同时记录
oldLast
的值为子字符串的结束的索引+1
的值即4,即下次执行find
时,就从4开始匹配。
2. 有分组
分组
的概念:一个括号就是一个分组。groups数组
是存储匹配到结果的地方。- 和无分组一样,
groups[0
]和groups[1]
位置存储的是匹配到的开始的索引和结束索引+1;接下来groups[2]
和groups[3]
存储的是匹配到的第一组的开始的索引和结束索引+1;groups[4]
和groups[5]
存储的是匹配到的第二组的开始的索引和结束索引+1;如果有更多分组则以此类推。
// match.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();
}
- 我们在取出匹配到的数据的时候matcher.group()中的参数是0的时候,我们可以根据
getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
看出他调用的是getSubSequence(groups[0],groups[1]),而groups[0]和groups[1]位置存储的是匹配到的开始的索引和结束索引+1,所以他刚好能返回匹配到的结果。 - 如果参数是1,则是getSubSequence(groups[2],groups[3]),也会取到第一组匹配到的数据。
- 他是group * 2的原因在于它是需要两个位置来记录开始索引和结束索引+1这两个索引。
💡注意:
需要区分groups
和group
:groups
是matcher的一个属性,它是一个数组用来存储索引;而group
是group方法的参数。
三、正则表达式语法
1. 元字符
1.1 转义符
java的正则表达式中,两个
\\
代表其他语法中的一个\
。当我们在使用正则表达式去检索某些特殊字符的时候,需要用到转移符号,否则检索不到结果,甚至会报错。
需要用到转移符号的字符有以下:. * + ( ) $ / \ ? [ ] ^ { }
1.2 字符匹配符
符号 | 符号 | 示例 | 解释 |
---|---|---|---|
[] | 可接收的字符列表 | [efgh] | e、f、g、h中的任意1个字符 |
[^] | 不接收的字符列表 | [^abc] | 除a、b、c之外的任意1个字符, 包括数字和特殊符号 |
- | 连字符 | A-Z | 任意单个大写字母 |
. | 匹配除\n以外的任何字节 | a…b | 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 |
\\d | 匹配单个数字字符,相当于[0-9] | \\d{3}(\\d)? | 包含3个或4个数字的字符串 |
\\D | 匹配单个非数字字符,相当于[^0-9] | \\D(\\d)* | 以单个非数字字符的开头,后接任意个数字字符串 |
\\w | 匹配单个数字、大小写字母字符,相当于[0-9a-zA-Z] | \\d{3}\\w{4} | 以3个数字字符开头的长度为7的数字字母字符串 |
\\W | 匹配单个非数字,大小写字母字符,相当于[^0-9a-zA-Z] | \\W+\\d{2} | 以至少1个非数字字母字符开头,2个数字字符结尾的字符串 |
\\s | 匹配任何空白字符(空格,制表符等) | ||
\\S | 匹配任何非空白字符,和\\s刚好相反 |
💡说明:
java正则表达式默认
是区分字母的大小写。
如何实现不区分大小写的方式❓
- 方式一:在需要不区分大小写的
表达式
前面写上(?i)
。但需要注意的是,如果是表达式中间的某个字符不区分大小写的时候需要将它们括起来,例如:a((?i)b)c
。- 方式二:
Pattern pat = Pattern.compile(regEx,Pattern.CASE_INSENSITIVE);
如果在中括号里面写上这些符号,则代表这些符号本身。
2. 限定符
符号 | 含义 | 示例 | 说明 | 匹配输入 |
---|---|---|---|---|
* | 指定字符重复0次或n次(无要求)零到多 | (abc)* | 仅包含任意个abc的字符串,等效于\w* | abc、 abcabcabc |
+ | 指定字符重复1次或n次(至少 一次)1到多 | m+(abc)* | 以至少1个m开头,后接任意个abc的字 符串 | m、mabc、 mabcabc |
? | 指定字符重复0次或1次(最多 一次)0到1 | m+abc? | 以至少1个m开头,后接ab或abc的字符 串 | mab、mabc、mmmab. mmabc |
{} | 只能输入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 |
💡说明:
- java默认为
贪婪匹配
。即:在限定出现次数里,优先匹配出现次数最多
的一组。例如:用a{3,5}来匹配aaaaaacx,返回的式aaaaa。?
组合的时候,它只作用于离他最近的那一个字符。例如:m+abc?
中,?
只作用于字符c。
如果想要使用非贪婪匹配,则在限定符后面加上?
即可。
2.1 选择匹配符
符号 | 符号 | 示例 | 解释 |
---|---|---|---|
| | 匹配“|”之前或之后的表达式 | ab|cd | ab或者cd |
2.2 分组组合和反向引用符
📌相关概念了解
- 分组
我们可以用圆括号
组成一个比较复杂的四配模式,那么一个圆括号的部分我们可以看作是一个子表达式
或一个分组
。
- 捕获分组
把正则表达式中子表达式
或分组匹配
的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志
,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式
。
常用分组构造形式 | 说明 |
---|---|
(pattern) | 非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号。(在分析底层源码的时候说过) |
(?pattern) | 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包括任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如(?`name`) |
📌演示代码:
public class RegTheory {
public static void main(String[] args) {
String content = "123-abc";
String regStr = "(?<i1>\\d)(?<i2>\\d)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
System.out.println(matcher.group("i1"));
System.out.println(matcher.group("i2"));
}
}
}
// 结果是1,2
- 非捕获分组
常用分组构造形式 | 说明 |
---|---|
(?:pattern) | 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储以后使用的匹配。这对于用“or”字符(|)组合模式部件的情况很有用。例如:`industr(?:y |
(?=pattern) | 它是一个非捕获匹配。例如:`windows(?=95 |
(?!pattern) | 该表达式匹配不处于匹配pattern的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如:`windows(?!95 |
这些正则表达式虽然都带了括号,但他们并不能使用
matcher.group()
来获取。
- 反向引用
圆括号的内容被捕获
后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用
,这种引用既可以是在正则表达式内部
,也可以是在正则表达式外部
,内部反向引用\\
分组号,外部反向引用$
分组号。
- 去重案例
String content = "hello helloasdas";
String regStr = "(\\w)\\1+";
Pattern compile = Pattern.compile(regStr);
Matcher matcher = compile.matcher(content);
String str = matcher.replaceAll("$1");
System.out.println(str);
2.3 定位符
符号 | 含义 | 示例 | 说明 | 匹配输入 |
---|---|---|---|---|
^ | 指定起始字符 | ^[0-9]+[a-z]* | 以至少1个数字开头,后接任意个小写字 母的字符串 | 123、6aa、555edf |
$ | 指定结束字符 | ^[0-9]\\-[a-z]+$ | 以1个数字开头后接连字符“-”,并以至少1个小写字母结尾的字符串 | 1-a |
\\b | 匹配目标字符串的边界 | han\\b | 这里说的字符串的边界指的是子串间有空格,或者是目标字符串的结束位置 | hanshunping sphan nnhan |
\\B | 匹配目标字符串的非边界 | han\\B | 和b的含义刚刚相反 | hanshunping sphan nr |
四、正则表达式的三个常用类
1. Pattern
pattern对象
是一个正则表达式对象。Pattern类
没有公共构造方法。要创建一个 Pattern对象,调用其公共静态方法,它返回一个 Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如:Pattern r = Pattern.compile(pattem)
📌示例代码:
String content = "18345678901";
String regStr = "(1(?:3|4|5|8))\\d{9}$";
boolean matches = Pattern.matches(regStr, content);
System.out.println(matches);
这里的是整体匹配,即它是从头开始匹配的。但使用
matcher.find()
是部分匹配。例如:👇
String content = "hello helloasdas";
String regStr = "hello";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()){
System.out.println(matcher.group(0));
} // 这里会匹配到两个hello
boolean matches = Pattern.matches(regStr, content);
System.out.println(matches); // 这是false,因为它是整体匹配,即正则表达式去匹配整个字符串。
matches()
的底层也只是封装了find()
方法。
2. Matcher
Matcher 对象
是对输入字符串进行解释和匹配的引擎。与Pattern类一样, Matcher也没有公共构造方法。你需要调用 Pattern对象的matcher()
来获得一个 Matcher对象
。
方法 | 说明 |
---|---|
public int start() | 返回以前匹配的初始索引 |
public int start(int group) | 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引public int end()返回最后匹配字符之后的偏移量 |
public int end(int group) | 返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。(返回的是最后的索引+1) |
public boolean lookingAt() | 尝试将从区域开头开始的输入序列与该模式匹配。 |
public boolean find() | 尝试查找与该模式匹配的输入序列的下一个子序列。 |
public boolean find(int start) | 重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。 |
public boolean matches() | 尝试将整个区域与模式匹配。 |
public Matcher appendReplacement(StringBuffer sb, String replacement) | 实现非终端添加和替换步骤。 |
public StringBuffer appendTail(StringBuffer sb) | 实现终端添加和替换步骤 |
public String replaceAll(String replacement) | 替换模式与给定替换字符串相匹配的输入序列的每个子序列。( 不会修改原内容,会返回一个修改后的字符串) |
public String replaceFirst(String replacement) | 替换模式与给定替换字符串匹配的输入序列的第一个子序列。 |
public static String quoteReplacement(String s) | 返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher类的appendReplacement方法一个字面字符串一样工作。 |
public Matcher appendReplacement(StringBuffer sb, String replacement) | 实现非终端添加和替换步骤。 |
public StringBuffer appendTail(StringBuffer sb) | 实现终端添加和替换步骤。 |
3. PatternSyntaxException
PatternSyntaxException
是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
五、String中使用正则表达式演示
方法 | 说明 |
---|---|
public String replacrAll(String regex,String replacement) | 使用replacement替换字符串中和regex匹配的字符 |
public boolean matches(String regex) | 匹配和regex相符的字符串 |
public String[] split(String regex) | 分割字符串。 |
📌演示:
String content = "hello helloasdas";
String str = content.replaceAll("(\\w)\\1+", "$1");
System.out.println(str); // 结果helo heloasdas
String content = "hehllos";
String regex = "^(h)\\w*s$";
boolean matches = content.matches("^(h)\\w*s$");
System.out.println(matches); //结果 true
String content = "hehllos";
String[] hs = content.split("h");
for (String h : hs) {
System.out.println(h);
} // 结果: e llos
六、总结
以上就是
正则表达式
的所有内容。主要介绍了正则表达式的语法
、使用
还有一些底层原理。如果文章中有错误还请留言📫或者私信我。🙏
最后希望大家多多 关注+点赞+收藏^_^,你们的鼓励是我不断前进的动力!!!
感谢感谢~~~🙏🙏🙏