正则表达式

1、基本介绍

  • 一个正则表达式(regular expression),就是用某种模式去匹配字符串的一个公式。其描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

  • 典型的搜索和替换操作要求您提供与预期的搜索结果匹配的确切文本。虽然这种技术对于对静态文本执行简单搜索和替换任务可能已经足够了,但它缺乏灵活性,若采用这种方法搜索动态文本,即使不是不可能,至少也会变得很困难。而通过使用正则表达式,可以做到:

    • 测试字符串内的模式。
    • 替换文本。
    • 基于模式匹配从字符串中提取子字符串。
  • Java正则表达式主要包含java.util.regex包下的三个类:

    • Pattern类:是一个正则表达式的编译表示。Pattern类没有公共构造方法。要创建一个Pattern对象,必须首先调用其公共静态编译方法,它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。

    • Matcher类:是对输入字符串进行解释和匹配操作的引擎。与Pattern类一样,Matcher也没有公共构造方法。需要调用Pattern 对象的matcher方法来获得一个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 String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列
    • PatternSyntaxException:是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

  • 举例使用正则表达式匹配一段字符串中的所有四个数字连在一起的子串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class RegExpTheory {
    /**
    * 使用正则表达式匹配一段字符串中的所有四个数字连在一起的子串
    *
    * @param args
    */
    public static void main(String[] args) {
    String content = "1998年12月8日,第二代Java平台的企业版J2EE发布。1999年6月,Sun公司发布了第二代Java平台" +
    "(简称为Java2)的3个版本:J2ME(Java2 Micro Edition,Java2平台的微型版),应用于移动、无线1966" +
    "及有限资源的环境;J2SE(Java 2 Standard Edition,Java2平台的标准版),应用于桌面环境;J2EE(Java " +
    "2Enterprise Edition,Java2平台的企业版),应用于基于Java的应用服务器。Java2平台的发布,是Java" +
    "发展过程中最重2538要的一个里程碑,标志着Java的应用开始普及9889";
    // 1.编写正则表达式,\\d表示任意一个数字
    String regStr = "\\d\\d\\d\\d";
    // 2.创建模式对象
    Pattern pattern = Pattern.compile(regStr);
    // 3.创建匹配器,即按照正则表达式的规则去匹配目标字符串
    Matcher matcher = pattern.matcher(content);
    /**
    * 4.进行匹配操作
    * matcher.find():
    * 1.根据指定的规则定位符合条件的字符串
    * 2.找到后将此字符串的(开始索引)和(结束索引+1)记录在matcher对象的属性int[] groups数组中,如此例子中第一个匹配到1988,那么group[0]=0,group[1]=4
    * 3.同时记录matcher对象中oldLast属性的值为(结束索引+1),即下次执行matcher.find()时从此索引开始找
    */
    while (matcher.find()) {
    // matcher.group(0):根据group[0]=0和group[1]=4的初始和结束索引位置从目标字符串中截取子串返回,范围为[0,4)
    System.out.println("找到:" + matcher.group(0));
    }
    }
    }
    • debug第一次循环如下:

      image-20210523133638659
    • 最终运行结果:

  • 还是上面的例子,但是使用()对正则表达式进行分组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class RegExpTheory {
    /**
    * 使用正则表达式匹配一段字符串中的所有四个数字连在一起的子串
    *
    * @param args
    */
    public static void main(String[] args) {
    String content = "1998年12月8日,第二代Java平台的企业版J2EE发布。1999年6月,Sun公司发布了第二代Java平台" +
    "(简称为Java2)的3个版本:J2ME(Java2 Micro Edition,Java2平台的微型版),应用于移动、无线1966" +
    "及有限资源的环境;J2SE(Java 2 Standard Edition,Java2平台的标准版),应用于桌面环境;J2EE(Java " +
    "2Enterprise Edition,Java2平台的企业版),应用于基于Java的应用服务器。Java2平台的发布,是Java" +
    "发展过程中最重2538要的一个里程碑,标志着Java的应用开始普及9889";
    // 1.编写正则表达式,\\d表示任意一个数字
    String regStr = "(\\d\\d)(\\d\\d)";
    // 2.创建模式对象
    Pattern pattern = Pattern.compile(regStr);
    // 3.创建匹配器,即按照正则表达式的规则去匹配目标字符串
    Matcher matcher = pattern.matcher(content);
    /**
    * 4.进行匹配操作
    * matcher.find(),此时考虑分组,即正则表达式中出现(),此例子中有两对(),即分成两组:
    * 1.根据指定的规则定位符合条件的字符串
    * 2.找到后将此字符串的(开始索引)和(结束索引+1)记录在matcher对象的属性int[] groups数组中,如此例子中第一个匹配到1988,那么group[0]=0,group[1]=4;
    * 接着记录第1组()匹配到的字符串(19)的(开始索引)到group[2]=0,(结束索引+1)到group[3]=2;
    * 最后记录第2组()匹配到的字符串(88)的(开始索引)到group[4]=2,(结束索引+1)到group[5]=4
    * 3.同时记录matcher对象中oldLast属性的值为(结束索引+1),即下次执行matcher.find()时从此索引开始找
    */
    while (matcher.find()) {
    // matcher.group(0):根据group[0]=0和group[1]=4的初始和结束索引位置从目标字符串中截取子串返回,范围为[0,4)
    System.out.println("整体找到:" + matcher.group(0));
    // matcher.group(1):根据group[2]=0和group[3]=2的初始和结束索引位置从目标字符串中截取子串返回,范围为[0,2)
    System.out.println("第一组()找到:" + matcher.group(1));
    // matcher.group(2):根据group[4]=2和group[5]=4的初始和结束索引位置从目标字符串中截取子串返回,范围为[2,4)
    System.out.println("第二组()找到:" + matcher.group(2));
    }
    }
    }
    • debug第一次循环如下:

    • 最终运行结果:

      image-20210523134113492

2、语法

2.1 转义符

  • 在Java 中,\\表示我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义,而在其它语言中却表示正则表达式中插入一个普通的(字面上的)反斜杠,即Java中的\\相当于其它语言的\

  • 需要用到\\有:*+()$/\?[]^{}.

2.2 字符匹配符

符号 含义 示例 解释
[] 可接收的字符列表 [abc] 匹配a、b、c中的任意一个字符
[^] 不可接收的字符列表 [^abc] 除a、b、c之外的任意一个字符,包括数字和特殊符号
- 连字符 A-Z 任意单个大写字母
. 匹配除了\n、\r以外的任何字符 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 匹配任何空白字符,包括空格、制表符、换页符等,相当于[ \f\n\r\t\v] \\d{3}\\s 以3个数字字符开头并以空白字符结尾的字符串
\\S 匹配任何非空白字符。相当于[^ \f\n\r\t\v] \\d\\S 以1个数字字符开头并以非空白字符结尾的字符串
  • 注:Java正则表达式默认是区分大小写的,但可以使用?i来表示不区分大小写或者使用Pattern.compile(regStr,Pattern.CASE_INSENSITIVE)。
    • (?i)abc:表示abc都不区分大小写。
    • a(?i)bc:表示bc不区分大小写。
    • a((?i)b)c:表示只有b不区分大小写。

2.3 选择匹配符

  • 表示在匹配某个字符串时是选择性的。
符号 含义 示例 解释
| 匹配|之前或之后的表达式 ab|cd ab或者cd

2.4 限定符

  • 用于指定其前面的字符和组合项连续出现多少次。
符号 含义 示例 解释
* 指定字符重复0次或n次 (abc)* 仅包含任意个abc的字符串
+ 指定字符重复1次或n次 m+(abc)* 以至少1个m开头,后接任意个abc的字符串
? 指定字符重复0次或1次 m+abc? 以至少1个m开头,后接ab或abc的字符串
{n} 只能输入n个字符 [abcd]{3} 由abcd中字母组成的任意长度为3的字符串
{n,} 指定至少n个匹配 [abcd]{3,} 由abcd中字母组成的任意长度不小于3的字符串
{n,m} 指定至少n个但不多于m个匹配 [abcd]{3,5} 由abcd中字母组成的任意长度不小于3且不大于5的字符串
  • 注:这里的”?”紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是“非贪心的”。“非贪心的“模式匹配搜索到的、尽可能短的字符串,而默认的“贪心的“模式匹配搜索到的、尽可能长的字符串。例如,在字符串”oooo”中,”o+?”只匹配单个”o”,而”o+”匹配所有”o”。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "oooo";
    String regStr = "o+?";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("找到:" + matcher.group(0));
    }
    }
    }
    image-20210523164551216
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "oooo";
    String regStr = "o+";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("找到:" + matcher.group(0));
    }
    }
    }
    image-20210523164622439

2.5 定位符

  • 规定要匹配的字符串出现的位置。
符号 含义 示例 解释
^ 指定起始字符 ^[0-9]+[a-z]* 以至少1个数字开头,后接任意个小写字母的字符串
$ 指定结束字符 ^[0-9]\\-[a-z]+$ 以1个数字开头后接连字符’-‘,并以至少1个小写字母结尾的字符串
\\b 匹配目标字符串的边界 han\\b 这里说的字符串的边界指的是被匹配字符串的最后或者是空格的子字符串的最后
\\B 匹配目标字符串的非边界 han\\B 和\\b的含义相反

2.6 捕获分组

常用分组构造形式 说明
(pattern) 非命名捕获。捕获匹配的子字符串。编号为零的第一个捕获是由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1开始自动编号
(?<name>pattern) 命名捕获。将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头,但可以使用单引号代替尖括号,例如(?’name’)
  • 测试非命名分组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "abcd1889sjia8907";
    String regStr = "(\\d\\d)(\\d\\d)";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("整体找到:" + matcher.group(0));
    System.out.println("第一个分组:" + matcher.group(1));
    System.out.println("第二个分组:" + matcher.group(2));
    }
    }
    }
    image-20210523154821459
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "abcd1889sjia8907";
    String regStr = "(\\d\\d)(\\d)(\\d)";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("整体找到:" + matcher.group(0));
    System.out.println("第一个分组:" + matcher.group(1));
    System.out.println("第二个分组:" + matcher.group(2));
    System.out.println("第三个分组:" + matcher.group(3));
    }
    }
    }
    image-20210523154922939
  • 测试命名分组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "abcd1889sjia8907";
    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("第一个分组:" + matcher.group(1));
    System.out.println("第一个分组(通过组名):" + matcher.group("g1"));
    System.out.println("第二个分组:" + matcher.group(2));
    System.out.println("第二个分组(通过组名):" + matcher.group("g2"));
    }
    }
    }
    image-20210523155211688

2.7 非捕获分组

常用分组构造形式 说明
(?:pattern) 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用”or”字符(|)组合模式部件的情况很有用。例如,”industr(?:y|ies)”是比”industry|industries”更经济的表达式
(?=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):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "industry industries industrabc";
    String regStr = "industr(?:y|ies)";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("整体找到:" + matcher.group(0));
    }
    }
    }
  • 测试(?=pattern):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "industry industries industrabc";
    String regStr = "industr(?=y|ies)";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("整体找到:" + matcher.group(0));
    }
    }
    }
    image-20210523163521873
  • 测试(?!=pattern):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class RegExpTest {
    public static void main(String[] args) {
    String content = "industry industries industrabc";
    String regStr = "industr(?!y|ies)";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("找到:" + matcher.group(0));
    }
    }
    }
    image-20210523163608019

2.8 反向引用

  • 正则表达式的几个概念:

    • 分组:可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。
    • 捕获:把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式。
    • 反向引用:圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\分组号,外部反向引用$分组号。
      • 例如要匹配连续2个相同数字:(\\d)\\1
      • 要匹配连续5个相同数字:(\\d)\\1{4}
      • 要匹配个位和千位相同,十位和百位相同的数字:(\\d)(\\d)\\2\\1
  • 反向引用案例:去掉”我我我….要要….学学..Java”中重复的字。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
        public static void main(String[] args) {
    String content = "我我我....要要....学学..Java";
    // 1.去掉重复的.
    String regStr = "\\.";
    Pattern pattern = Pattern.compile(regStr);
    Matcher matcher = pattern.matcher(content);
    content = matcher.replaceAll("");
    System.out.println(content);

    // 2.去掉重复的字
    regStr = "(.)\\1+";
    pattern = Pattern.compile(regStr);
    matcher = pattern.matcher(content);
    while (matcher.find()) {
    System.out.println("找到:" + matcher.group(0));
    }
    // 使用反向引用来替换匹配到的内容
    content = matcher.replaceAll("$1");
    System.out.println(content);
    }
    }
    image-20210523200335931