最近工作中遇到了文本处理的工作,我们知道正则表达式是文本处理的利器,之前在学习正则表达式的时候,快速过了一遍,没实际上手过,真正用到还真有点手足无措,我将会分为两篇文章去记录自己复习过程的笔记还有踩的坑,视频学习推荐韩顺平老师录制的课程。
快速入门
/**
* 快速入门:
* 提取文本中的所有英语单词
*/
public class QuickStartRegex {
public static void main(String[] args) {
String text = "Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念," +
"因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程";
// 1.创建一个 Pattern 对象,相当于一个正则表达式对象
Pattern pattern = Pattern.compile("[a-zA-z]+");
// 2.创建一个匹配器对象
Matcher matcher = pattern.matcher(text);
// 3.开始循环匹配,这个匹配器对象按照 pattern 到 text 去匹配
while (matcher.find()) {
System.out.println(matcher.group(0));
}
}
}
从这个 QuickStart 我们可以看到正则表达式对于文本的处理是非常方便的,如果用传统的方法去找文中英语单词,需要一个一个去遍历识别,代码量大。
常见表达式语法
限定符
用于指定其前面的字符和组合项连续出现多少次
| 符号 | 描述 | 示例 | 解释 | 匹配的输入 |
|---|---|---|---|---|
| * | 指定字符重复0次或n次 | (abc)* | 仅包含任意多个 abc 的字符串,等效于\w* | abc、abcdef |
| + | 指定字符重复1次或n次 | m+(abc)* | 以至少一个 m 开头,后接任意个 abc 的字符串 | m、mabc、mabcabc |
| ? | 指定字符重复 0 次或 1 次 | m+abc? | 以至少一个 m 开头,后接 ab 或abc 的字符串 | mab、mabc、mmmmab、mmabc |
| {n} | 只能输入 n 个字符 | [abcd]{3} | 由 abcd 中字母组成的任意长度为 3 的字符串 | abc、dbc、adc |
选择匹配符
| 符号 | 描述 | 示例 | 解释 | 匹配的输入 | |||
|---|---|---|---|---|---|---|---|
| 匹配" | "之前或之后的表达式 | ab | cd | ab或者cd |
分组组合
捕获分组
| 常用分组构造形式 | 说明 |
|---|---|
| (pattern) | 非命名捕获。捕获匹配的字符串。编号为 0 的第一个捕获是由整个正则表达式模式匹配的文本,其他捕获结果则根据左括号的顺序从 1 开始自动编号。 |
| (?pattern) | 命名捕获,将匹配的子字符串捕获到一个组名称或编号名称中。用于 name 的字符串不能包括任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如(?'name') |
示例:
/**
* 非命名分组
*/
public class TestRegxGroup {
public static void main(String[] args) {
String str = "fanzibang 1234 fanfan5678zizibangbang";
String regex = "(\d\d)(\d\d)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
while(matcher.find()) {
System.out.println("匹配到字符串:" + matcher.group(0));
System.out.println("第一个分组的内容:" + matcher.group(1));
System.out.println("第二个分组的内容:" + matcher.group(2));
}
}
}
/**
* 命名分组
*/
public class TestRegexNameGroup {
public static void main(String[] args) {
String str = "fanzibang 1234 fanfan5678zizibangbang";
String regex = "(?<g1>\d\d)(?<g2>\d\d)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
while(matcher.find()) {
System.out.println("匹配到字符串:" + matcher.group(0));
System.out.println("第一个分组的内容:" + matcher.group(1));
System.out.println("g1分组的内容:" + matcher.group("g1"));
System.out.println("第二个分组的内容:" + matcher.group(2));
System.out.println("g2分组的内容:" + matcher.group("g2"));
}
}
}
非捕获分组
| 常用分组构造形式 | 说明 | |||
|---|---|---|---|---|
| (?:pattern) | 匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用 "or" 字符 ( | ) 组合模式部件的情况很有用。例如, "industr(?:y | ies)" 是比 "industry | industries" 更经济的表达式。 |
| (?=pattern) | 它是一个非捕获匹配。例如,"Windows s(?=95 | 98 | NT | 2000)" 匹配 "Windows 2000" 中的 "Windows",但不匹配 "Windows 3.1" 中的 "Windows"。 |
| (?!pattern) | 该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配。例如,"Windows(?!95 | 98 | NT | 2000)" 匹配 "Windows 3.1" 中的 "Windows",但不匹配 "Windows 2000" 中的 "Windows"。 |
反向引用符
圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个被称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\分组号,外部反向引用 $分组号。
示例:
- 要匹配两个连续的相同数字:(\d)\1
- 要匹配五个连续的相同数字:(\d)\1{4}
- 要匹配个位与千位相同,十位与百位相同的数字 5225、1551:(\d)(\d)\2\1
字符匹配符
| 符号 | 描述 | 示例 | 解释 | 匹配的输入 |
|---|---|---|---|---|
| [ ] | 可接收的字符列表 | [abcd] | a、b、c、d中的任意1个字符 | |
| [^] | 不接收的字符列表 | [^abc] | 除a、b、c、d之外的任意1个字符,包括数字和符号 | |
| - | 连字符 | A-Z | 任意1个大写字母 | |
| . | 匹配除了\n以外的任何字符 | a..b | 以a开头,b结尾,中间包括两个任意字符 | aabb、ajhb、a*#b |
| \d | 匹配单个数字字符,相当于[0-9] | \d{3}(\d)? | 包含3个或4个数字的字符串 | |
| \D | 匹配单个非数字字符,相当于[^0-9] | \D(\d)* | 以单个非数字字符后接任意个数字字符 | a123、B9866 |
| \w | 匹配单个数字、大小写字母字符,相当于[0-9a-zA-Z] | \d{3}\w{4} | 以3个数字字符开头后接4个数字字母字符串 | 111a3b6、123456a |
| \W | 匹配单个非数字、大小写字母字符,相当于[^0-9a-zA-Z] | \W+\d{2} | 至少1个非数字字母字符开头,后接2个数字字符结尾 | #78、!99 |
定位符
规定匹配的字符串出现的位置
| 符号 | 描述 | 示例 | 解释 | 匹配的输入 |
|---|---|---|---|---|
| ^ | 指定起始字符 | ^[0-9]+[a-z]* | 以至少 1 个数字开头,后接任意个小写字母的字符串 | 123、1、68asd |
| $ | 指定结束字符 | ^[0-9]\-[a-z]+$ | 以 1 个数字开头后接连字符"-",并以至少 1 个小写字母结尾的字符串 | 2-a、123-abc |
| \b | 匹配目标字符串的边界 | abc\b | 字符串的边界指的是是子串间有空格,或者是目标字符串的结束位置 | abcdfsadasdabc fghabc |
| \B | 匹配目标字符串的非边界 | abc\B | 与\b的含义相反 | abcdfsadasdabc fghabc |
更详细的语法规则,我们可以去菜鸟教程查看。
三个常用的类
接下来我们来认识 Java 正则表达式中三个常用的类:Pattern 类、Matcher 类和 PatternSyntaxException,通过上一篇文章的一些例子,我们知道,Pattern 类 和 Matcher 类,Pattern 对象相当于一个正则表达式对象,负责规定匹配规则,Matcher 相当于一个匹配器对象,负责匹配字符串。
Pattern
Pattern 对象是一个正则表达式对象,Pattern 类没有公共构造方法,通过调用其静态方法创建一个 Pattern 对象,该方法接收一个正则表达式作为它的参数,比如:
Pattern pattern = Pattern.compile("[a-zA-z]+");
Matcher
Matcher 类是对输入的字符串进行匹配的引擎,与 Pattern 类一样,Matcher 类也没有公共构造方法,通过调用 Pattern 对象的 matcher 方法获得一个 Matcher 对象。比如:
Matcher matcher = pattern.matcher("112233");
PatternSyntaxException
PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
常用方法
Pattern
| 方法 | 说明 |
|---|---|
| public static boolean matches(String regex, CharSequence input) | 判断是否匹配成功。 |
示例:
/**
* Pattern.matches 演示
*/
public class TestPatternMatchersMethod {
public static void main(String[] args) {
String regex = "\d\d";
String text = "abc";
boolean matches = Pattern.matches(regex, text);
System.out.println("是否匹配成功:" + matches);
}
}
==========结果=============
是否匹配成功:false
Matcher
| 方法 | 说明 |
|---|---|
| public int start() | 返回上一个匹配项的起始索引。 |
| public int start(int group) | 返回以前匹配操作期间,由给定组所捕获的子序列的初始索引。 |
| public int end() | 返回最后匹配字符之后的偏移量。 |
| public int end(int group) | 返回以前匹配操作期间,由给定组所捕获的子序列的最后字符之后的偏移量。 |
| 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) | 实现终端追加和替换步骤。该方法从输入序列中读取字符,从追加位置开始,并将其追加到给定的字符串缓冲区。该方法用于在一次或多次调用 appendReplacement 方法后调用,以复制输入序列的剩余部分。 |
| public String replaceAll(String replacement) | 用给定的替换字符串替换输入序列中与模式匹配的每个子序列。 |
| public String replaceFirst(String replacement) | 用给定的替换字符串替换输入序列中与模式匹配的第一个子序列。 |
| public static String quoteReplacement(String s) | 返回指定字符串的字面替换字符串。此方法生成的字符串将在匹配器类的 appendReplacement 方法中用作字面替换 s。生成的字符串将与作为字面序列的 s 中的字符序列相匹配。 |
示例:
/**
* 演示 Matcher 类的一些常用方法
*/
public class TestMatcherMethods {
public static void main(String[] args) {
String text = "hello world!";
String regex = "([a-z]+)\1";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("=============");
System.out.println(matcher.start());
System.out.println(matcher.end());
System.out.println("substring: " + text.substring(matcher.start(), matcher.end()));
System.out.println("匹配的字符串:" + matcher.group());
}
// 整体匹配方法,校验某个字符串是否满足规则
System.out.println("整体匹配:" + matcher.matches());
}
}
========结果==========
2
4
substring: ll
匹配的字符串:ll
整体匹配:false
/**
* 演示 Matcher 类的一些常用方法
*/
public class TestMatcherMethods02 {
public static void main(String[] args) {
String text = "hello word";
String regex = "word";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
System.out.println("被替换的字符串:" + Matcher.quoteReplacement(text));
System.out.println("appendTail: " + matcher.appendTail(new StringBuffer("你好!")));
System.out.println("replaceFirst: " + matcher.replaceFirst("fanzibang"));
}
}
========结果==========
被替换的字符串:hello word
appendTail: 你好!hello word
replaceFirst: hello fanzibang