正则表达式

正则表达式

正则表达式是一种用来匹配字符串强有力的武器。Java内置了强大的正则表达式的支持。
本章我们会详细介绍如何在Java程序中使用正则表达式
image

正则表达式简介

正则表达式可以用字符串来描述规则,并用来匹配字符串。例如,判断手机号,我们用正则表达式\d{11}:

boolean isValidMobileNumber(String s) {
    return s.matches("\\d{11}");
}

使用正则表达式的好处有哪些?
一个正则表达式就是一个描述规则的字符串,所以,只需要编写正确的规则,我们就可以让正则表达式引擎去判断目标字符串是否符合规则

正则表达式是一套标准,它可以用于任何语言。Java标准库的java.util.regex包内置了正则表达式引擎,在Java程序中使用正则表达式非常简单。

把正则表达式20\d\d转换为Java字符串就变成了20\\d\\d,注意**Java字符串用\表示**。

最后,用正则表达式匹配一个字符串的代码如下:

public class Main {
    public static void main(String[] args) {
        String regex = "20\\d\\d";
        System.out.println("2019".matches(regex)); // true
        System.out.println("2100".matches(regex)); // false
    }
}

可见,使用正则表达式,不必编写复杂的代码来判断,只需给出一个字符串表达的正则规则即可

小结

  • 正则表达式是用字符串描述的一个匹配规则,使用正则表达式可以快速判断给定的字符串是否符合匹配规则。
  • Java标准库java.util.regex内建了正则表达式引擎。

匹配规则

正则表达式的匹配规则是从左到右按规则匹配。我们首先来看如何使用正则表达式来做精确匹配

对于正则表达式abc来说,它只能精确地匹配字符串"abc",不能匹配"ab","Abc","abcd"等其他任何字符串。

如果正则表达式有特殊字符,那就需要用\转义。例如,正则表达式a&c,其中&是用来匹配特殊字符&的,它能精确匹配字符串"a&c",但不能匹配"ac"、"a-c"、"a&&c"等。

要注意正则表达式在Java代码中也是一个字符串,所以,对于正则表达式a&c来说,对应的Java字符串是"a\&c",因为\也是Java字符串的转义字符,两个\实际上表示的是一个\。

  • 如果想匹配非ASCII字符,例如中文,那就用\u####的十六进制表示,例如:a\u548cc匹配字符串"a和c",中文字符和的Unicode编码是548c。
  • 匹配任意字符,精确匹配实际上用处不大,因为我们直接用String.equals()就可以做到。大多数情况下,我们想要的匹配规则更多的是模糊匹配。我们可以用.匹配一个任意字符,.匹配一个字符且仅限一个字符
  • 匹配数字,用.可以匹配任意字符,这个口子开得有点大。如果我们只想匹配0~9这样的数字,可以用\d匹配,\d仅限单个数字字符
  • 匹配常用字符,用\w可以匹配一个字母、数字或下划线,w的意思是word。\w不能匹配#、空格等字符
  • 匹配空格字符,用\s可以匹配一个空格字符,注意空格字符不但包括空格,还包括tab字符(在Java中用\t表示)
  • 匹配非数字,用\d可以匹配一个数字,而\D则匹配一个非数字。类似的,\W可以匹配\w不能匹配的字符,\S可以匹配\s不能匹配的字符,这几个正好是反着来的。
  • 重复匹配
    • 修饰符*可以匹配任意个字符,包括0个字符
    • 修饰符+可以匹配至少一个字符。
    • 修饰符?可以匹配0个或一个字符。
    • 如果我们想精确指定n个字符怎么办?用修饰符{n}就可以。
    • 如果我们想指定匹配n~m个字符怎么办?用修饰符{n,m}就可以。
    • 如果没有上限,那么修饰符{n,}就可以匹配至少n个字符。

小结

  • 单个字符的匹配规则如下:
    image
  • 多个字符的匹配规则如下:
    image

复杂匹配规则

  • 匹配开头和结尾
    用正则表达式进行多行匹配时,我们^表示开头,$表示结尾。例如,^A\d{3}$,可以匹配"A001"、"A380"。
  • 匹配指定范围
    使用[...]可以匹配范围内的字符,把所有字符全列出来太麻烦,[...]还有一种写法,直接写[1-9]就可以。[...]还有一种排除法,即不包含指定范围的字符。假设我们要匹配任意字符,但不包括数字,可以写[^1-9]{3}.
  • 或规则匹配
    用|连接的两个正则规则是或规则,例如,AB|CD表示可以匹配AB或CD。java|php
    它可以匹配"java"或"php",但无法匹配"go"。要把go也加进来匹配,可以改写为java|php|go
  • 使用括号
    现在我们想要匹配字符串learn java、learn php和learn go怎么办?一个最简单的规则是learn\sjava|learn\sphp|learn\sgo,但是这个规则太复杂了,可以把公共部分提出来,然后用(...)把子规则括起来表示成learn\s(java|php|go)

小结
复杂匹配规则主要有:
image

分组匹配

我们前面讲到的(...)可以用来把一个子规则括起来,这样写learn\s(java|php|go)就可以更方便地匹配长字符串了。实际上(...)还有一个重要作用,就是分组匹配

我们来看一下如何用正则匹配区号-电话号码这个规则。利用前面讲到的匹配规则,写出来很容易:\d{3,4}\-\d{6,8}
虽然这个正则匹配规则很简单,但是往往匹配成功后,下一步是提取区号和电话号码,分别存入数据库。于是问题来了:如何提取匹配的子串

当然可以用String提供的indexOf()和substring()这些方法,但它们从正则匹配的字符串中提取子串没有通用性,下一次要提取learn\s(java|php)还得改代码。

正确的方法是用(...)先把要提取的规则分组,把上述正则表达式变为(\d{3,4})\-(\d{6,8})
现在问题又来了:匹配后,如何按括号提取子串
现在我们没办法用String.matches()这样简单的判断方法了,必须引入java.util.regex包,用Pattern对象匹配,匹配后获得一个Matcher对象,如果匹配成功,就可以直接从Matcher.group(index)返回子串
image
要特别注意,Matcher.group(index)方法的参数用1表示第一个子串,2表示第二个子串。如果我们传入0会得到什么呢?答案是010-12345678,即整个正则匹配到的字符串

Pattern
我们在前面的代码中用到的正则表达式代码是String.matches()方法,而我们在分组提取的代码中用的是java.util.regex包里面的Pattern类和Matcher类。实际上这两种代码本质上是一样的,因为String.matches()方法内部调用的就是Pattern和Matcher类的方法。

但是反复使用String.matches()对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern对象。完全可以先创建出一个Pattern对象,然后反复使用,就可以实现编译一次,多次匹配
image
使用Matcher时,必须首先调用matches()判断是否匹配成功,匹配成功后,才能调用group()提取子串

利用提取子串的功能,我们轻松获得了区号和号码两部分。

小结
正则表达式用(...)分组可以通过Matcher对象快速提取子串

  • group(0)表示匹配的整个字符串;
  • group(1)表示第1个子串,group(2)表示第2个子串,以此类推。

非贪婪匹配

小结

  • 正则表达式匹配默认使用贪婪匹配,可以使用?表示对某一规则进行非贪婪匹配
  • 注意区分?的含义:\d??。我们再来看这个正则表达式(\d??)(9*),注意\d?表示匹配0个或1个数字,后面第二个?表示非贪婪匹配,因此,给定字符串"9999",匹配到的两个子串分别是""和"9999",因为对于\d?来说,可以匹配1个9,也可以匹配0个9,但是因为后面的?表示非贪婪匹配,它就会尽可能少的匹配,结果是匹配了0个9。
  • 不管啥模式,首先整体要满足匹配

搜索和替换

  • 分割字符串
    使用正则表达式分割字符串可以实现更加灵活的功能。String.split()方法传入的正是正则表达式。我们来看下面的代码:
"a b c".split("\\s"); // { "a", "b", "c" }
"a b  c".split("\\s"); // { "a", "b", "", "c" }
"a, b ;; c".split("[\\,\\;\\s]+"); // { "a", "b", "c" }
  • 搜索字符串
    使用正则表达式还可以搜索字符串,我们来看例子:
import java.util.regex.*;
public class Main {
    public static void main(String[] args) {
        String s = "the quick brown fox jumps over the lazy dog.";
        Pattern p = Pattern.compile("\\wo\\w");
        Matcher m = p.matcher(s);
        while (m.find()) {
            String sub = s.substring(m.start(), m.end());
            System.out.println(sub);
        }
    }
}

我们获取到Matcher对象后,不需要调用matches()方法(因为匹配整个串肯定返回false),而是反复调用find()方法,在整个串中搜索能匹配上\wo\w规则的子串,并打印出来。这种方式比String.indexOf()要灵活得多,因为我们搜索的规则是3个字符:中间必须是o,前后两个必须是字符[A-Za-z0-9_]

  • 替换字符串
    使用正则表达式替换字符串可以直接调用String.replaceAll(),它的第一个参数是正则表达式,第二个参数是待替换的字符串。我们还是来看例子:
    image
  • 反向引用
    如果我们要把搜索到的指定字符串按规则替换,比如前后各加一个xxxx这个时候,使用replaceAll()的时候,我们传入的第二个参数可以使用$1、$2来反向引用匹配到的子串。例如:
    image
    它实际上把任何4字符单词的前后用xxxx括起来。实现替换的关键就在于" $1 ",它用匹配的分组子串([a-z]{4})替换了$1

小结
使用正则表达式可以:

  • 分割字符串:String.split()
  • 搜索子串:Matcher.find()
  • 替换字符串:String.replaceAll()

posted on 2022-03-10 10:42  朴素贝叶斯  阅读(106)  评论(0编辑  收藏  举报

导航