学习内容来自B站韩顺平老师的Java基础课
可以快速方便的匹配字符串的内容,匹配内容可以通过特有规则的 pattern 指定
比如从给定的文本中找出所有四个数字连在一起的子串,并且这四个数字中,第一个与第四个相同,第二个与第三个相同,比如 1221,3443
如果使用传统方法,只能通过遍历字符串,并且遍历的同时记录连续数字个数,然后再做相应判断,比较麻烦
而使用正则表达式,就可以通过指定 pattern 快速匹配出想要的内容
或者说验证邮箱、手机号的格式,也可以指定 pattern 快速进行判断
比如在一串文本中找到所有四个数字连在一起的子串:
public static void main(String[] args) { String content = "1998年12月8日,第二代Java平台的企业版J2EE发布。1999年6月,Sun公司发布" + "了第二代Java平台(简称为Java2)的3个版本:J2ME(Java2 Micro Edition,Java2平" + "台的微型版),应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition," + "Java 2平台的标准版),应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2" + "平台的企业版),应用于基于Java的应用服务器。Java 2平台的发布,是Java发展过程中最重要" + "的一个里程碑,标志着Java的应用开始普及。"; // 匹配所有四个数字 // 1. \\d 表示一个任意的数字 String regStr = "\\d\\d\\d\\d"; // 2. 创建 Pattern 对象 Pattern pattern = Pattern.compile(regStr); // 3. 创建匹配器 // 说明:创建匹配器 matcher,按照 regStr 指定的规则去匹配 content 字符串 Matcher matcher = pattern.matcher(content); // 4. 开始匹配 // 找到就返回 true,否则返回 false // 匹配到的内容放入 matcher.group(0) while (matcher.find()) { System.out.println("找到:" + matcher.group(0)); } }
其中matcher.find() 完成的任务有:
matcher.group(0) 分析:
源码:
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]) 之间的子串,类似 subString 方法
那么为什么是 group(0),这个 0 又代表什么意思?
对上述例子稍作修改,在 pattern 中加上两对小括号,如下:
这样做相当于对正则表达式进行分组,有多少对括号就分成多少组,那么现在使用 matcher.find() 方法完成的任务有:
即分组后,groups 中 0 和 1 索引记录的仍为匹配到的子串的首尾索引,往后的位置依次记录分组对应的索引
比如:
public static void main(String[] args) { String content = "1998年12月8日,第二代Java平台的企业版J2EE发布。1999年6月,Sun公司发布" + "了第二代Java平台(简称为Java2)的3个版本:J2ME(Java2 Micro Edition,Java2平" + "台的微型版),应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition," + "Java 2平台的标准版),应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2" + "平台的企业版),应用于基于Java的应用服务器。Java 2平台的发布,是Java发展过程中最重要" + "的一个里程碑,标志着Java的应用开始普及。"; // 匹配所有四个数字 // 1. \\d 表示一个任意的数字 String regStr = "(\\d\\d)(\\d\\d)"; // 2. 创建 Pattern 对象 Pattern pattern = Pattern.compile(regStr); // 3. 创建匹配器 // 说明:创建匹配器 matcher,按照 regStr 指定的规则去匹配 content 字符串 Matcher matcher = pattern.matcher(content); // 4. 开始匹配 // 找到就返回 true,否则返回 false // 匹配到的内容放入 matcher.group(0) /** * matcher.find() 完成的任务: * 1. 根据指定的规则,定位满足要求的字符串(比如 1998 ) * 2. 找到后,将子串索引记录到 matcher 对象的属性 int[] groups 中 * 开始索引记录到 groups[0],即 groups[0] = 0 * 结束索引 +1 后记录到 groups[1] 中,即 groups[1] = 4 * (考虑分组) 2.1 groups[0] = 0,groups[1] = 4 * (考虑分组) 2.2 对于子串 1998,记录第 1 组() 匹配的字符串 19 groups[2] = 0,groups[3] = 2 * (考虑分组) 2.3 对于子串 1998,记录第 2 组() 匹配的字符串 98 groups[4] = 2,groups[5] = 4 * (考虑分组) 如果有更多的分组,以此类推 * 3. 同时记录 oldLast 的值为 groups[1] 的值,用来作为下次执行 find() 方法的匹配开始位置 * * matcher.group(0) 分析: * * 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(); * } * * 1. 上述方法可以概括为返回 [groups[0], groups[1]) 之间的子串,类似 subString 方法 * */ while (matcher.find()) { System.out.println("找到:" + matcher.group(0)); System.out.println("第 1 组 () 匹配到的值:" + matcher.group(1)); System.out.println("第 2 组 () 匹配到的值:" + matcher.group(2)); } }
输出结果:
找到:1998 第 1 组 () 匹配到的值:19 第 2 组 () 匹配到的值:98 找到:1999 第 1 组 () 匹配到的值:19 第 2 组 () 匹配到的值:99
正则表达式由各种元字符组成,元字符从功能上大致分为:
首先需要知道转义符为 \\
就像使用 ( 去匹配 abc($abc(123( 会报错
在 ( 前加上 \ 即可
注意:在 Java 的正则表达式中,两个 \ 表示其它语言中的一个 \
需要用到转义符的字符有:
. * + ( ) $ / \ ? [ ] ^ { }
其中
除此之外,还有:
\\s
匹配任意空白字符(空格、制表符)\\S
匹配任意非空白字符,与上一个相反[abcd]
表示匹配 abcd 中的任意一个字符[^abcd]
表示匹配不是 abcd 的任意一个字符Java 的正则表达式默认区分大小写,如何实现不区分大小写?
(?i)abc
表示 abc 都不区分大小写a(?i)bc
表示 bc 不区分大小写a((?i)b)c
表示只有 b 不区分大小写Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSIVE);
在匹配某个字符时可以是选择性的,既可以匹配这个,又可以匹配那个。通俗理解就是逻辑表达式中的或操作,符号也和或一样,为 |
用于指定前面的字符和组合项连续出现多少次,比如前面 \\d{3} 等价于 \\d\\d\\d
注意:
a{3 ,4}
后,待匹配文本为 aaaa456,那么会匹配到 4 个 a,即 aaaa,而不是 3 个 aa{3 ,4}
后,待匹配文本为 aaaaaaa789,那么会先匹配到 4 个 a,即 aaaa,再匹配到 3 个 a,即 aaa;而如果待匹配文本为 aaaaaa678,那么只会匹配到 4 个 a,即 aaaa+
表示匹配出现大于等于 1 次的,如果指定为 1+
,待匹配文本为 1111456,那么会直接匹配到 1111*
和?
同理
例子:
public static void main(String[] args) { String content = "jieruigou NN GGG1237gou 9987gou"; 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("第 1 个分组内容:" + matcher.group(1)); System.out.println("第 1 个分组内容(通过组名):" + matcher.group("g1")); System.out.println("第 2 个分组内容:" + matcher.group(2)); System.out.println("第 2 个分组内容(通过组名):" + matcher.group("g2")); } }
输出结果:
找到:1237 第 1 个分组内容:12 第 1 个分组内容(通过组名):12 第 2 个分组内容:37 第 2 个分组内容(通过组名):37 找到:9987 第 1 个分组内容:99 第 1 个分组内容(通过组名):99 第 2 个分组内容:87 第 2 个分组内容(通过组名):87
例子 1:
(?:pattern)
的使用
public static void main(String[] args) { String content = "hi杰瑞狗 jerry杰瑞dog 杰瑞队长hello"; // 以下两种写法等价 // String regStr = "杰瑞狗|杰瑞dog|杰瑞队长"; String regStr = "杰瑞(?:狗|dog|队长)"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println("找到:" + matcher.group(0)); } }
输出结果
找到:杰瑞狗 找到:杰瑞dog 找到:杰瑞队长
例子 2:
(?=pattern)
的使用
public static void main(String[] args) { String content = "hi杰瑞狗 jerry杰瑞dog 杰瑞队长hello"; // 以下两种写法等价 // String regStr = "杰瑞狗|杰瑞dog|杰瑞队长"; // String regStr = "杰瑞(?:狗|dog|队长)"; String regStr = "杰瑞(?=dog|队长)"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println("找到:" + matcher.group(0)); } }
输出结果:
找到:杰瑞 找到:杰瑞
(?!pattern)
就相当于(?=pattern)
反过来的效果,(?=pattern)
匹配到的(?!pattern)
都匹配不到
注意:
之前提过 Java 默认的是贪婪匹配,非贪婪匹配可以通过加 ?
来实现
public void isCharacter() { String content = "杰瑞狗"; // 汉字的编码范围 String regStr = "^[\u0391-\uffe5]+$"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); if (matcher.find()) { System.out.println("满足格式"); } else { System.out.println("不满足格式"); } }
// 要求:六位数字 public void isMailCode() { String content = "411320"; // 汉字的编码范围 String regStr = "^[1-9]\\d{5}$"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); if (matcher.find()) { System.out.println("满足格式"); } else { System.out.println("不满足格式"); } }
// 要求:1 - 9 开头的数(5位~10位) public void isQQId() { String content = "411320"; // 汉字的编码范围 String regStr = "^[1-9]\\d{4,9}$"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); if (matcher.find()) { System.out.println("满足格式"); } else { System.out.println("不满足格式"); } }
// 要求:1开头,第二位是 3 4 5 8 其中一个,共 11 位 public void isPhoneNumber() { String content = "15966667777"; // 汉字的编码范围 String regStr = "^1[3|4|5|8]\\d{9}$"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); if (matcher.find()) { System.out.println("满足格式"); } else { System.out.println("不满足格式"); } }
public void isURL() { String content = "https://www.bilibili.com/video/BV1fh411y7R8?p=894&spm_id_from=pageDriver"; /** * 分析思路 * 1. 开头可能有 https:// 或者 http:// * 2. 域名由 数字、字母、下划线、- 组成 * 3. 之后的路径由 \ 开头,然后跟上字母、数字以及一些字符组成 */ String regStr = "^((http|https)://)?([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&/.%#]*)?$"; // [.?*] 中括号中的字符表示匹配字符本身 Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); if (matcher.find()) { System.out.println("满足格式"); } else { System.out.println("不满足格式"); } }
pattern 对象是一个正则表达式对象,没有公共构造器
若想创建 Pattern 对象,就需要调用它的静态方法 compile() 返回 Pattern 对象
该方法需要接受一个正则表达式作为它的第一个参数,如:
Pattern pattern = Pattern.compile("^1[3|4|5|8]\\d{9}$");
Pattern 类还有其他方法,如
public void testMatches() { String content = "hello jerry hello, gougougou"; String regStr = "hello"; // 若正则表达式能整体匹配给定文本,返回 true,否则返回 false boolean matches = Pattern.matches(regStr, content); System.out.println(matches ? "整体匹配成功" : "整体匹配失败"); }
就像上面应用实例的各种验证,其实可以使用 matches 方法
事实上,该方法底层仍然是调用 Matcher 类的 matches 方法
Matcher 对象是对输入字符串进行解释和匹配的引擎,与 Pattern 类一样,Matcher 也没有公共构造方法,需要调用 Pattern 对象的 matcher 方法获得 Matcher 对象
Matcher matcher = pattern.matcher(content);
Matcher 类的常用方法有:
例子 1:
public void testMethod() { String content = "hello jerry dog jack jessi hello jim hello"; String regStr = "hello"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println("========="); // 当前匹配到的子串的开始索引,相当于 groups[0] System.out.println(matcher.start()); // 当前匹配到的子串的结束索引 + 1,相当于 groups[1] System.out.println(matcher.end()); System.out.println("找到:" + content.substring(matcher.start(), matcher.end())); } // 整体匹配方法,校验某个字符串是否满足某个规则 System.out.println("整体匹配=" + matcher.matches()); }
输出结果:
========= 0 5 找到:hello ========= 27 32 找到:hello ========= 37 42 找到:hello 整体匹配=false
例子 2:
请将字符串 “hello jerry dog jack jessi hello jim hello” 中的 jerry 换成 杰瑞狗
public void testExchange() { String content = "hello jerry dog jack jessi hello jerry jim hello"; String regStr = "jerry"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); // replaceAll 方法会返回替换后的新字符串,并不修改原来的字符串 String newContent = matcher.replaceAll("杰瑞狗"); System.out.println("newContent = " + newContent); }
输出结果:
newContent = hello 杰瑞狗 dog jack jessi hello 杰瑞狗 jim hello
PatternSyntaxException 是一个非强制异常类,用来表示正则表达式中的语法错误
重新回到一开始提到的问题:
可以发现之前介绍的内容不能完成这个功能,所以需要介绍新方法–反向引用
首先需要明确三个概念
那么现在可以通过反向引用上面提出的问题:
public static void main(String[] args) { String content = "jerry dog3443 dog1234 jerry 1221 hello"; String regStr = "(\\d)(\\d)\\2\\1"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println("找到=" + matcher.group(0)); } }
再来一个例子:
在字符串中检索编号,形式为:12321-333444111,即以五位数开头,然后接一个 -
,之后接一个九位数,要求每三位要相同
public void testNum() { String content = "jerry12321-444555999 dog3443 dog1234 jerry 1221 hello"; String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println("找到=" + matcher.group(0)); } }
把类似:”我…我要…学学学学…java编程!“
通过正则表达式修改成”我要学java编程!“
public static void main(String[] args) { String content = "我...我要...学学学学...java编程!"; // 去掉所有的 . Pattern pattern = Pattern.compile("\\."); Matcher matcher = pattern.matcher(content); content = matcher.replaceAll(""); System.out.println(content); // 去掉重复的字,方法一 // 先用 (.)\\1+ 匹配连续相同的字 pattern = Pattern.compile("(.)\\1+"); matcher = pattern.matcher(content); while (matcher.find()) { System.out.println("找到=" + matcher.group(0)); } // 再用反向引用 $1 替换匹配到的内容 String newContent = matcher.replaceAll("$1"); System.out.println("newContent = " + newContent); // 去掉重复的字,方法二 content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1"); System.out.println("content = " + content); }
输出结果:
我我要学学学学java编程! 找到=我我 找到=学学学学 newContent = 我要学java编程! content = 我要学java编程!
String 类的 replaceAll(String regex, String replacement) 方法,可以直接使用正则表达式进行替换
例子:
public static void main(String[] args) { // 将下面文本中的 JDK1.3、JDK1.4 替换成 JDK String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其获得了" + "Apple公司Mac OS X的工业标准的支持。2001年9月24日,J2EE1.3发布。2002" + "年2月26日,J2SE1.4发布。自此Java的计算能力有了大幅提升,与J2SE1.3相比," + "其多了近62%的类和接口。在这些新特性当中,还提供了广泛的XML支持、安全套接字" + "(Socket)支持(通过SSL与TLS协议)、全新的I/OAPI、正则表达式、日志与断言" + "。2004年9月30日,J2SE1.5发布,成为Java语言发展史上的又一里程碑。为了表示" + "该版本的重要性,J2SE 1.5更名为Java SE 5.0(内部版本号1.5.0),代号为“Ti" + "ger”,Tiger包含了从1996年发布1.0版本以来的最重大的更新,其中包括泛型支持、" + "基本类型的自动装箱、改进的循环、枚举类型、格式化I/O及可变参数。"; // 使用 String 类的方法 content = content.replaceAll("JDK1\\.3|JDK1\\.4", "JDK"); System.out.println(content); }
String 类的 matches(String regex) 方法
例子:
判断给定的手机号是否是 138 / 139 开头
public void testMatches() { String content = "13866666666"; if (content.matches("13(8|9)\\d{8}")) { System.out.println("符合要求!"); } else { System.out.println("不符合要求!"); } }
String 类的 split(String regex) 方法
例子:
按照 - + # ~ 分割字符串
public void testSplit() { String content = "我+是-一~个#字-符+串"; String[] split = content.split("~|\\+|-|#"); for (String s : split) { System.out.println("s = " + s); } }