目录
正则表达式
基本介绍
- 一个正则表达式,就是使用某种模式去匹配字符串的一个公式。很多人因为它们看上去比较古怪所以不敢使用,不过,经过练习后,就会觉得这些复杂的表达式写起来还是相当简单的,而且,一旦弄懂他们,你就能把数小时辛苦而且易错的文本处理工作缩短在几分钟(甚至几秒钟)内完成
- 请注意,正则表达式不是只有java才有,实际上很多编程语言都支持正则表达式进行字符串操作。
正则表达式底层实现
实例分析
package priv.user.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 2021/5/28 9:44
* 通过下面示例分析正则表达式底层实现
*/
public class RegTheory {
public static void main(String[] args) {
String content = "建安五年(公元200年),曹操与袁绍相持与官渡,曹军夜袭乌巢,袁绍军大败。" +
"建安十三年(公元208年),孙刘联军在长江火烧赤壁,大破曹军。" +
"章武元年(公元221年),陆逊火烧连营八百里,大破蜀军,刘备仅以身免。";
//目标匹配所有公元年份
String regex = "公元(\\d)(\\d\\d)年";
//1.创建正则表达式对象
Pattern pattern = Pattern.compile(regex);
//2.创建匹配器
Matcher matcher = pattern.matcher(content);
//3.循环遍历
while (matcher.find()) {
System.out.println("年份:" + matcher.group(0));
}
}
}
分组的概念:正则表达式中的一个"()",就表示一个分组。并且分组的编号跟小括号的位置有关,第一个"()“就表示第一个分组,第二个”()"就表示第二个分组。
原理剖析:
- 根据指定的规则,定位满足规则的子字符串,比如(2)(00)。
- 找到满足规则的子字符串后,将子字符串的开始索引记录到 matcher 对象的 int[] groups 属性中。
- groups[0] = 5,将该子字符串的结束索引加一记录到 groups[1] = 11
- 记录第一组"()"匹配到的字符串 groups[2] = 7, groups[3] = 8
- 记录第二组"()"匹配到的字符串 groups[4] = 8, groups[5] = 10
- 如果有更多分组…
- 同时记录 oldLast 的值为 子字符串的结束的索引加一即11,即下次执行 find 时,就从11开始匹配。
正则表达式语法
基本介绍
如果想要灵活的使用正则表达式,就必须了解其中各种元字符(Metacharacter)的功能,元字符从功能上大致分为以下几类:
- 限定符
- 选择匹配符
- 分组组合和反向引用符
- 特殊字符
- 字符匹配符
- 定位符
元字符功能详解
转义
字符 | 说明 |
---|---|
\\ | 将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,“n"匹配字符"n”。"\n"匹配换行符。序列"\\“匹配”\","\(“匹配”("。 |
字符匹配符
字符 | 说明 |
---|---|
[ ] | 可接收的字符列表。例如,[abcd]匹配a、b、c、d中任意一个字符 |
[^] | 不接收的字符列表。例如,[^abcd]匹配除a、b、c、d外的任意一个字符 |
- | 连字符。例如,A-Z,表示匹配A到Z的大写字母 |
. | 匹配除"\r\n"之外的任何单个字符。若要匹配包括"\r\n"在内的任意字符,请使用诸如"[\s\S]"之类的模式。 |
\\d | 数字字符匹配。等效于 [0-9]。 |
\\D | 非数字字符匹配。等效于 [^0-9]。 |
\\w | 匹配任何字类字符,包括下划线。与"[A-Za-z0-9_]"等效。 |
\\W | 与任何非单词字符匹配。与"[^A-Za-z0-9_]"等效。 |
\\s | 匹配任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r\t\v] 等效。 |
\\S | 匹配任何非空白字符。与 [^ \f\n\r\t\v] 等效。 |
选择匹配符
字符 | 说明 |
---|---|
| | 匹配"|"之前或之后的表达式。例如,ab|bc,匹配ab或bc |
限定符
字符 | 说明 |
---|---|
* | 零次或多次匹配前面的字符或子表达式。例如,zo* 匹配"z"和"zoo"。* 等效于 {0,}。 |
+ | 一次或多次匹配前面的字符或子表达式。例如,"zo+"与"zo"和"zoo"匹配,但与"z"不匹配。+ 等效于 {1,}。 |
? | 零次或一次匹配前面的字符或子表达式。例如,"do(es)?“匹配"do"或"does"中的"do”。? 等效于 {0,1}。 |
{n} | n 是非负整数。正好匹配 n 次。例如,"o{2}"与"Bob"中的"o"不匹配,但与"food"中的两个"o"匹配。 |
{n,} | n是非负整数。至少匹配 n次。例如,"o{2,}“不匹配"Bob"中的"o”,而匹配"foooood"中的所有 o。"o{1,}“等效于"o+”。"o{0,}“等效于"o*”。 |
{n,m} | m 和 n 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,"o{1,3}"匹配"fooooood"中的头三个 o。‘o{0,1}’ 等效于 ‘o?’。注意:您不能将空格插入逗号和数字之间。 |
定位符
字符 | 说明 |
---|---|
^ | 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与"\n"或"\r"之后的位置匹配。 |
$ | 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与"\n"或"\r"之前的位置匹配。 |
\\b | 匹配一个字边界,即字与空格间的位置。例如,“er\b"匹配"never"中的"er”,但不匹配"verb"中的"er"。 |
\\B | 非字边界匹配。“er\B"匹配"verb"中的"er”,但不匹配"never"中的"er"。 |
分组
字符 | 说明 |
---|---|
(?:pattern) | 匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符 (|)组合模式部件的情况很有用。例如,'industr(?:y|lies)是比 'industry|industries’更经济的表达式。 |
(?=pattern) | 执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,‘Windows (?=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”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。 |
正则表达式的三个常用类
Pattern类
package priv.user.regex;
import java.util.regex.Pattern;
/**
* 2021/5/28 12:53
*/
public class RegPattern {
public static void main(String[] args) {
//Pattern类没有公共构造方法,需要调用其静态方法compile创建Pattern对象
Pattern pattern = Pattern.compile("\\d\\d");
//若是只需要验证某段字符串是否符合正则表达式格式而不需要输出搜索结果,可以使用Pattern类中的match
//注意matches方法是进行全局匹配
boolean isMatch = Pattern.matches("\\d\\d", "23");
}
}
Matcher类
package priv.user.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 2021/5/28 12:53
*/
public class RegPattern {
public static void main(String[] args) {
//Pattern类没有公共构造方法,需要调用其静态方法compile创建Pattern对象
Pattern pattern = Pattern.compile("(\\d)(\\d)");
//若是只需要验证某段字符串是否符合正则表达式格式而不需要输出搜索结果,可以使用Pattern类中的match
boolean isMatch = Pattern.matches("\\d\\d", "23");
//Matcher类没有公共的构造方法,需要调用Pattern对象的matcher方法创建Matcher对象
Matcher matcher = pattern.matcher("1556");
//常用方法
//find方法
while (matcher.find()) {
//start和end属性
System.out.println("结果:" + "1556".substring(matcher.start(), matcher.end()));
}
//替换方法
String content = matcher.replaceAll("6");
System.out.println(content);
//整体匹配方法
//Pattern的静态方法matches底层就是调用的Matcher类的matches方法
System.out.println("整体匹配:" + matcher.matches());
}
}
PatternSyntaxException类
PatternSyntaxException类是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
分组、捕获、反向引用
需求:给你一段文本,找出所有四个数字连在一起的子串,并且这四个数字要满足第一位与第四位相等,第二位与第三位相等。
介绍
要解决前面的问题,我们需要了解正则表达式的几个概念。
- 分组:我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式或一个分组。
- 捕获:把正则表达式中子表达式或分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式。
- 反向引用:圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\分组号,外部反向引用$分组号
案例
package priv.user.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 2021/5/28 16:08
*/
public class RegExam {
public static void main(String[] args) {
String content = "hello11 world22";
//匹配两个连续相同的数字
String regex = "(\\d)\\1";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("find out:" + matcher.group(0));
}
}
}
package priv.user.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 2021/5/28 16:08
*/
public class RegExam {
public static void main(String[] args) {
String content = "hello11111world";
//匹配五个连续相同的数字
String regex = "(\\d)\\1{4}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("find out:" + matcher.group(0));
}
}
}
package priv.user.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 2021/5/28 16:08
*/
public class RegExam {
public static void main(String[] args) {
String content = "hello123321world";
//匹配一个6位数,其中第一位与最后一位相同,第二位与第五位相同,第三位与第四位相同
String regex = "(\\d)(\\d)(\\d)\\3\\2\\1";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("find out:" + matcher.group(0));
}
}
}
经典的结巴去重程序
需求:把"我…我正正…在在…学学学学…习习…正则表达式"这段文本通过正则表达式修改成为一段通顺的语句,即"我正在学习正则表达式"。
package priv.user.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 2021/5/28 16:21
*/
public class RegStuttering {
public static void main(String[] args) {
String word = "我....我正正....在在....学学学学....习习....正则表达式";
//1.首先将所有的"."去掉
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(word);
word = matcher.replaceAll("");
System.out.println(word);//测试是否去掉"."
//2.匹配相同的内容,并去重
//注意:这里使用到了反向引用 $1 来替换重复内容
pattern = Pattern.compile("(.)\\1+");
matcher = pattern.matcher(word);
word = matcher.replaceAll("$1");
System.out.println(word);//测试是否已经去重
}
}
String类中使用正则表达式
替换功能
public static void main(String[] args) {
String content = "python是流行的编程语言";
String newContent = content.replaceAll("\\w+", "java");
System.out.println(newContent);
}
判断功能
public static void main(String[] args) {
String content = "python是流行的编程语言";
//这里是全局匹配
boolean isMatch = content.matches("^\\w+[\\u4e00-\\u9f5a]*$");
System.out.println(isMatch);
}
分割功能
public static void main(String[] args) {
String content = "hello/world&apple$orange#happpy%banana";
String[] words = content.split("[/&%$#]");
for (String word : words) {
System.out.println(word);
}
}
巩固练习案例
匹配汉字
public static void main(String[] args) {
String content = "我爱中国";
String regex = "^[\\u4e00-\\u9f5a]+$";
boolean isMatch = Pattern.matches(regex, content);
if (isMatch) {
System.out.println("符合要求");
} else {
System.out.println("不符合要求");
}
}
匹配URL
public static void main(String[] args) {
String content = "https://www.bilibili.com/";
//注意:正则表达式中如果将"$","."等特殊字符写进"[]"里面,表示就是匹配这个字符本身,不需要使用转义
String regex = "^(http|https)://([\\w-]+\\.)+\\w+/[\\w#$%&-.?]*";
boolean isMatch = Pattern.matches(regex, content);
if (isMatch) {
System.out.println("符合要求");
} else {
System.out.println("不符合要求");
}
}
验证电子邮箱是否合法
要求:
- 只能有一个@
- @前面是用户名,可以是"a-z,A-Z,0-9,-_"字符
- @后面是域名,并且域名只能是英文字母,比如sohu.com或者qq.com
public static void main(String[] args) {
String content = "ilovechina@tsinghua.org.cn";
String regex = "^[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+$";
boolean isMatch = Pattern.matches(regex, content);
if (isMatch) {
System.out.println("符合要求");
} else {
System.out.println("不符合要求");
}
}
验证是不是整数或者小数
要求:考虑整数和负数
public static void main(String[] args) {
String content = "123456";
String regex = "^[+-]?([1-9]\\d*|0)(\\.\\d+)?$";
boolean isMatch = Pattern.matches(regex, content);
if (isMatch) {
System.out.println("符合要求");
} else {
System.out.println("不符合要求");
}
}
对一个URL进行解析
http://www.sohu.com:8080/abc/xyz/index.html
要求:
- 得到协议
- 得到域名
- 得到端口号
- 得到访问资源的文件名
public static void main(String[] args) {
String content = "http://www.sohu.com:8080/abc/xyz/index.html";
String regex = "(\\w+)://([\\w.]+):?(\\d*)?[\\w&*%$#@./]*/([\\w.]+)";
Matcher matcher = Pattern.compile(regex).matcher(content);
while (matcher.find()) {
System.out.println("协议头" + matcher.group(1));
System.out.println("域名" + matcher.group(2));
System.out.println("端口号" + matcher.group(3));
System.out.println("资源名" + matcher.group(4));
}
}