Java正则表达式

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java正则表达式相关的知识,希望对你有一定的参考价值。

Java正则表达式

课程链接:https://www.bilibili.com/video/BV1Eq4y1E79W?p=1

在刷题过程中,也经常见正则表达式,感觉非常方便。今天偶然看到就来学一下!

简介

用于处理类似的文本问题,就是用某种模式去匹配字符串的一个公式
正则表达式是对字符串执行模式匹配的技术
正则表达式:regular expression => RegExp

底层实现(很重要)

package study01;


import java.util.regex.Matcher;
import java.util.regex.Pattern;

//分析java正则表达式的底层实现
public class RegTheory {
    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平台的企业版),应" +
                "用3443于基于Java的应用服务器。oDva 2平台的发布,是Java发 展过程中最重要的一个" +
                "里程碑,标志着Java的应用开始普及9889";
        //目标:匹配所有四个数字的子串
        //说明
        //1.\\\\d 表示一个任意的数字
        String regStr = "(\\\\d\\\\d)(\\\\d\\\\d)";
        //2.创建模式对象[即正则表达式对象]
        Pattern pattern = Pattern.compile(regStr);
        //3.创建一个匹配器
        //说明:创建一个匹配器matcher,按照正则表达式的规则匹配字符串content
        Matcher matcher = pattern.matcher(content);

        //4.开始匹配
        /**
         * matcher.find()完成的任务
         *
         * 1.根据指定的规则,定位满足条件的子字符串(比如1998)
         * 2.找到后,将子字符串开的的索引记录到 matcher对象的属性 int[] groups;
         *   group[0] = 0, 把该字符串的结束的索引+1的值记录到 group[1] = 4
         * 3. 同时记录oldlast 的值为 字符串的结束的索引+1,即4,即下次执行find时,就从4这个位置开始匹配
         *
         * 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();
         *     }
         *  可以看出,根据 group[0] = 0 和 group[1] = 4 记录的位置,从content开始截取子字符串返回
         *  就是[0,4) 包含0 但是不包含索引为 4 的位置
         *
         *  如果再次执行find方法,仍然按照上面的分析来执行
         *  即group[0] = 31, group[1] = 35
         */

        /**
         * matcher.find()完成的任务(考虑分组)
         * 什么是分组,比如:(\\d\\d)(\\d\\d),正则表达式中有()表示分组,第一个()表示第一组,第二个()表示第二组...
         * 1.根据指定的规则,定位满足条件的子字符串(比如1998)
         * 2.找到后,将子字符串开的的索引记录到 matcher对象的属性 int[] groups;
         *   2.1 group[0] = 0, 把该字符串的结束的索引+1的值记录到 group[1] = 4
         *   2.2 记录1组()匹配的字符串groups[2] = 0 groups[3] = 2
         *   2.3 记录2组()匹配的字符串groups[4] = 2 groups[5] = 4
         *   2.4 如果有更多的分组,以此类推
         * 3. 同时记录 oldlast 的值为 字符串的结束的索引+1,即4,即下次执行find时,就从4这个位置开始匹配
         */
        while(matcher.find()){
            //小结
            //1.如果正则表达式有()即分组
            //取出匹配到的字符串规则如下:
            //group(0)表示匹配到的整体子字符串
            //group(1)表示匹配到的第一组子字符串
            //group(2)表示匹配到的第二组子字符串
            //但是不能越界
            System.out.println("找到:" + matcher.group(0));
            System.out.println("第一组匹配到的值" + matcher.group(1));
            System.out.println("第二组匹配到的值" + matcher.group(2));
            //System.out.println("第二组匹配到的值" + matcher.group(3)); //越界
        }
    }
}
输出:
找到:1998
第一组匹配到的值19
第二组匹配到的值98
找到:1999
第一组匹配到的值19
第二组匹配到的值99
找到:3443
第一组匹配到的值34
第二组匹配到的值43
找到:9889
第一组匹配到的值98
第二组匹配到的值89

正则表达式语法

●基本介绍
如果要想灵活的运用正则表达式,必须了解其中各种元字符的功能,元字符从功能上大致分为:
1.限定符
2.选择匹配符
3.分组组合和反向引用符
4.特殊字符
5.字符匹配符
6.定位符

元字符(Metacharacter) - 转移号\\\\

\\\\符号说明:在我们使用正则表达式去检索某些特殊字符的时候,需要用到转义符号,否则检索不到结果,甚至会报错的。案例:用 $ 去匹配"abc$(" 会怎样? 用(去匹配"abc$(" 会怎样?

再次提示:
在Java的正则表达式中,两个 \\\\ 代表其他语言中的一个 \\

. 的含义是匹配所有字符,如果要匹配 . ,需要进行转移
需要用到转移符号的字符有如下: . * + ( ) $ / \\ ? [ ] ^ { }

元字符-字符匹配符

在这里插入图片描述
在这里插入图片描述补充:

  1. \\\\w也可以找到下划线,\\\\W相反
  2. \\\\s四配任何空白字符(空格制表符等)
  3. \\\\S匹配任何非空白字符,和\\\\s刚好相反
  4. "."匹配除\\r\\n之外的所有字符,如果要匹配 “.” 本身则需要使用转义字符 \\\\
  5. 若要匹配包括"\\r\\n"在内的任意字符,请使用诸如"[s\\S]"之类的模式(不太懂)

补充:

\\r与\\n合起来就是回车换行的意思,回车是将光标移到当前行的行首;
换行是将光标移到当前行的下一行,但还是同一列,不会回到行首。
它们合起来可以将光标移到下一行的行首,也就是回车并换行。
但在不同的系统中它们的功能也不太相同。比如在windows里,\\r\\n表示回车换行;但在linux中\\n就代表回车换行。
这也是为什么在linux下用vim打开windows编辑的文件会发现在每一行尾都有个^M字符的原因。

解释:第二行(\\\\d)?表示有0个或者1个数字;+代表1到多个

Java正则表达式默认区分大小写,如何实现不区分大小写

(?i)abc表示abc都不区分大小写
a(?i)bc表示bc不区分大小写
a((?i)b)c表示只有b不区分大小写
Pattern pattern = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);

//不区分大小写
Pattern pattern = Pattern.compile(regStr, Pattern.CASE_INSENSITIVE);
[^a-z] 说明:

[^a-z] 表示可以匹配不是a-z中的任意一个字符 ,比如
[^a-z] 去匹配a11c8会得到什么结果? 得到118
[^a-z] {2}又会得到什么结果呢? 表示匹配不是a-z并且连续两个,结果是11

选择匹配符

在匹配某个字符串的时候是选择性的,即:既可以匹配这个,又可以匹配那个,这时你需要用到选择匹配符号 |
在这里插入图片描述

元字符-限定符

用于指定其前面的字符和组合项连续出现多少次
在这里插入图片描述
注意:第三行?只作用在c字符上,如果希望同时生效要括起来
在这里插入图片描述

String content = "111111";
String regStr = "1{4}";
这个匹配结果只有一个1111
String regStr = "1{4, 5}";
这个输出结果是11111,java匹配时默认是贪婪匹配,即尽可能匹配多的

String regStr = "a1?";	//表示匹配a或者a1

元字符-定位符

定位符,规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置,这个也是相当有用的
在这里插入图片描述

String content = "123abc";
String regStr = "^[0-9]+[a-z]*";
这个匹配结果是123abc
如果String content = "a123abc";
因为开头不是数字,匹配不到
如果String content = "123abc232";
这个还是匹配到123abc

String content = "123ab-12abc";
String regStr = "^[0-9]+\\\\-[a-z]+$";	//匹配不到
String regStr = "^[0-9]+.+\\\\-.+[a-z]+$";	//匹配到全部

String content = "123ab 12ab";
String regStr = "ab\\\\b";
这个匹配到的是两个ab,空格也算分界,大写的B匹配前面的

分组

捕获分组

在这里插入图片描述

非捕获分组

在这里插入图片描述

//给分组命名,然后后面取出的时候除了用编号,还可以用组名
String regStr = "(?<g1>\\\\d\\\\d)(?<g2>\\\\d\\\\d)";

String regStr = "abcde|abcef|abctx";
//也可写成
String regStr = "abc(?:de|ef|tx)";	//这个不会捕获,所以不能取出group(1)

非贪婪匹配


当此字符紧随任何其他限定符(*、 +、?、{}、{n} {n,m}) 之后时,匹配模式是"非贪心的"。
“非贪心的"模式匹配搜索到的、尽可能短的字符串,而默认的"贪心的"模式匹配搜索到的、尽可能长的字符串。
例如,在字符串"oooo"中,"o+?“只匹配单个"o”,而"o+“匹配所有"o”。

正则表达式的应用实例

  1. 验证字符是否都是汉字(定位符加汉字范围)
String regStr = "^[\\u0391-\\uffe5]+$"; 
  1. 邮政编码,要求是以1-9开头 的一个六位数,例如123890
String regStr = "^[1-9]\\\\d{5}$";
String regStr = "^[0-9]{6}$" ;	//可行,当然不一定以1开头了
String regStr = "^[0-9]{6}" ;	//可行,同上
String regStr = "^[0-9]+$";		//可行,同上
  1. QQ号码,1-9开头的一个5位数到10位数
String regStr = "^[1-9]\\\\d{4,9}$";
  1. 手机号码,以13,14,15,18开头的11位数
String regStr = "^1[3|4|5|8]\\\\d{9}$";
  1. 验证URL,如: htps://www.bilbili.com/video/BV1fh411y7R8?from=searchseid=1831060912083761326
    注意:这里?. * 等符号写在中括号里面,其含义就是一个?. ,而不是限定符
//先确定开始部分 https:// 或者 http://
Stirng regStr = "^((http|https)://)?"
//第二步:匹配域名www.bilbili.com
Stirng regStr = "^((http|https)://)?([\\\\w-]+\\\\.)+[\\\\w-]+$"
//后面部分的匹配/video/BV1fh411y7R8?from=searchseid=1831060912083761326(可能有也可能没有)
Stirng regStr = "^((http|https)://)?([\\\\w-]+\\\\.)+[\\\\w-]+(\\\\/[\\\\w-?=&%\\.#]*)?$"

三个常用的类

java.util.regex包主要包括以下三个类Pattern类、Matcher类和PatternSyntaxException

Pattern类

pattern对象是一个正则表达式对象。 Pattern 类没有公共构造方法。要创建一个Pattern对象,其实是调用其公共静态方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如: Pattern r= Pattern.compile(pattern);

Matcher类

Matcher对象是对输入字符串进行解释和匹配的引擎。与Pattern 类一样,Matcher也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个Matcher对象

PatternSyntaxException

PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

Pattern类的方法matches

用于整体匹配,在验证输入的字符串是否满足条件时使用
整体匹配可以去掉定位符

import java.util.regex.Pattern;
public class Hello {
	public static void main(String[] args) {
		String content = "I am hsp from hspedu.com.";
		String pattern = ".*hspedu.*";
		boolean isMatch = Pattern.matches(pattern, content);
		System.out.println("是否整体匹配成功”+ isMatch);	//返回true
	}
}//简单追下源码PatternMethod.java(还是创建了比较器对象,调用了比较器对象的matches方法)

Matcher类的相关方法

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
这里原字符串不会被替换,如果想要被替换,直接用原字符串接收(下面会被替换)
在这里插入图片描述

分组、捕获、反向引用

例如需求:
给你一段文本,请你找出所有四个数字连在一 起的子串,并且这四个数字要满足①第1位与第4位相同②第2位与第3位相同,比如1221,5775

介绍

要解决前面的问题,我们需要了解正则表达式的几个概念:

  1. 分组
    我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。
  2. 捕获
    把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式
  3. 反向引用
    圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用 \\\\分组号,外部反向引用 $分组号

案例:
1.要匹配两个连续的相同数字: (\\\\d)\\\\1
2.要匹配五个连续的相同数字: (\\\\d)\\\\1{4}
3.要匹配个位与干位相同,十位与百位相同的数5225 , 1551 (\\\\d)(\\\\d)\\\\2\\\\1
思考题:
请在字符串中检索商品编号,形式如:12321-333999111这样的号码,要求满足前面是一个五位数,然后一个 - 号,然后是一个九位数,连续的每3位要相同
\\\\d{5}-(\\\\d)\\\\1{2}(\\\\d)\\\\2{2}(\\\\d)\\\\3{2}


经典的结巴程序:
把类似: "...我...学学学..编程java!";通过正则表达式修改成"我要学编程java"
1.去掉所有的点
String content = "...我...学学学..编程java!";通过正则表达式修改成"我要学编程java";
Pattern pattern = Pattern.compile("\\\\.");
Matcher matcher = pattern.matcher(content);
content = matcher.replaceAll("");
2.去掉重复的字
思路:使用(.)\\\\1+,使用反向引用S1替换匹配到的字符(外部)
//因为正则表达式变化了,所以需要重置一下matcher
pattern = Pattern.compile("(.)\\\\1+");
matcher = pattern.matcher(content);
content = matcher.replaceAll("$1");
3.使用一条语句
content = Pattern.compile("(.)\\\\1+").matcher(content).replaceAll("$1");

在String中使用正则表达式(常用)

替换功能

String类public String replaceAll(String regex, String replacement);

//将JKD1.3,JDK1.4替换成JDK
content = content.replaceAll("JDK1\\\\.3|JDK1\\\\.4", "JDK");
判断功能

//这个方法是整体匹配
String 类 public boolean matches(String regex){}
要求验证一个手机号,必须是以138 139开头的

content.matches("13(8|9)\\\\d{8}");
分割功能

String 类 public String[] split(Stirng regex)

String content = “hello#abc-jack12smith~北京”
要求按照 # 或者 - 或者 ~ 或者数字来分割

String[] ss = content.split("#|-|~|\\\\d+");
案例

1.验证电子邮件格式是否合法Homework01java
规定电子邮件规则为:

  1. 只能有一个@
  2. @前面是用户名,可以是a-z A-Z 0-9 -字符
  3. @后面是域名,并且域名只能是英文字母,比如sohu.com或者tsinghua.org.cn
  4. 写出对应的正则表达式,验证输入的字符串是否为满足规则
String regStr = "^[\\\\w-]+@([a-zA-z]+\\\\.)+[a-zA-z]+$"
//也可以不加定位符,因为是整体匹配,写上定位符更严谨

if(content.matches(regStr))
	输出“匹配成功”

2.要求验证是不是整数或者小数
提示:这个题要考虑正数还是负数
比如:123 -345 34.89 -87.9 -0.01 0.45 等

先写出简单的正则表达式,然后逐步完善
String regStr = "^[-+]?([1-9]\\\\d*|0)(\\\\.\\\\d+)?$";

3.对一个url进行解析

http://www.sohu.com:8080/abc/index.htm
a)要求得到协议是什么? http
b)域名是什么? www. sohu.com
c)端口是什么? 8080
d)文件名是什么? index.htm

思路:就是分组分别获取

//如果有别的需求,可以改进
String regStr = "^([a-zA-Z]+)://([a-zA-Z.]+):(\\\\d+)[\\\\w-/]*/([\\\\w.]+)$";

以上是关于Java正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

java 字符串替换

text 正则表达式片段

markdown 正则表达式模式片段

正则表达式匹配特定的 URL 片段而不是所有其他 URL 可能性

java正则表达式去除html标签

循环通过 python 正则表达式匹配