正则表达式

本文深入解析正则表达式的语法与应用,涵盖元字符、量词、分组、边界匹配等核心概念,同时介绍Java API中Pattern和Matcher类的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正则表达式是一串字符串,利用它可以方便地处理文本。

正则表达式中字符有两类:

1.普通字符:匹配其本身

2.元字符:有特殊含义的字符

1.语法:

1.1单个字符

语法解释
\r \n \t回车符,换行符,制表符
\0n,\0nn,\0mn八进制表示的字符,比如:\0141,十进制就是97
\xhh(后跟两位字符)十六进制表示的字符,比如:\x6A,十进制就是106
\uhhhh(后跟四位字符)Unicode编号表示字符
\x{h...h}Unicode编号增码字符

1.2字符组

语法解释
.默认模式下,它匹配出换行符以外的任意字符;在单行匹配模式下,匹配任意字符,包括换行符。
[abc]表示匹配abc中任意字符
[^abc]“^”只有放在字符数组开头才是元字符,才表示排出的意思,表示,匹配除abc以外的任意字符
[0-9a-z]“-”是一个元字符,表示连续多个字符;表示匹配 0到9、a到z的任意字符
[-0-9]-放在“[”后面不在是一个元字符,表示匹配  -或 0-9的任意字符
[.*]除了"^、-、[]、\"外,其他元字符在数组中就是普通字符;表示匹配.或*
[a-z&&[^de]]表示匹配a-z但不包括de
[[abc][def]]匹配abcdef中的任意字符
\d表示digit,匹配一个数字字符,等同于[0-9]
\w表示word,匹配一个单词字符,等同于[a-zA-Z0-9_](数字,字母下划线)
\s表示空白,匹配一个空白字符,等同于[\t\n\x0B\f\r]
\D匹配一个非数字字符,即[^\d];
\W匹配一个非单词字符,即[^\w];
\S

匹配一个非空白字符,即[^\s];

1.3量词

语法解释
x?、x??"?"是一个元字符,表示前面的字符可能出现,也可能不出现;表示:x出现0或1次;懒惰匹配
x*、x*?"*"表示前面字符零次或多次出现;表示:x出现0或多次;加?表示懒惰匹配
x+、x+?"+"表示前面字符一次或多次出现;表示:x出现1或多次;加?表示懒惰匹配
x{m,n}、x{m,n}?x出现m到n次;加?表示懒惰匹配
x{m,}、x{m,}?x出现m次或多次;加?表示懒惰匹配
x{n}、x{n}?x出现n次;加?表示懒惰匹配

贪婪匹配:

例:表达式:  a.*b去匹配aacabdsdsb时会匹配整个表达式;这就是贪婪匹配(默认是贪婪的)

懒惰匹配:

例:表达式:  a.*?b去匹配aacabdsdsb时会匹配aacab;这就是懒惰匹配(加?表示懒惰匹配)

1.4分组

表达式用()括起来表示分组,分组可以嵌套:

比如表达式:

a(bc)((de)(fg))

第0个分组:abcdefg

第一个分组:bc;

第二个分组:defg

第三个分组:de

第四个分组:fg

然后\加分组编号就是引用之前匹配到的分组

对于上述例子中:\1,就是引用表达式bc;\2就是引用表达式defg;这就叫Back引用。

上述为通过分组编号引用分组,也可以通过对分组起名字(?<name>X),通过名字来引用分组(\k<name>):

例:

a(?<first>bc)(?<second>(?<third>de)(?<fourth>fg))

\k<first>:就是引用的bc

\k<second>:就是引用的defg;

说到这应该了解到,既然分了组还能引用,那说明内存中存有分组的文本;那这样就会带来很大的开销。

那么就有不将其放入内存的方法,(?:)就是不将分组捕获;

语法解释
ab|cd表示匹配ac或cd
(http|ftp|file)表示匹配 http或ftp或file
a(bc)+d表示bc出现多次
<(\w+)>(.*)</\1>(\w+)为第一个分组,\1表示对它的引用;这个表达式可以看成两个<>中的内容一致
(?<name>X)给分组X起名为name;
\k<name>引用名为name的分组
(?:abc|def)匹配abc或def

1.5特殊边界匹配

^、$、\A、\Z、\z、\b都是特殊边界元字符

语法解释
^在字符数组开始处表示排除;在字符数组外,表示字符串的开始;多行模式下匹配行开始比如:^[^abc]:表示不以a、b、c开始
$匹配整个字符串的结束,如果字符串以换行符结束,$匹配的是换行符之前的边界;比如:abc$:表示匹配以abc或abc\r\n或abc\n结束;    多行模式下匹配行结束。
\A总是匹配整个字符串的开始边界
\Z总是匹配字符串的结束边界,如果以换行符结束,匹配换行符之前的边界;
\z总是匹配字符串结束的边界
\b匹配的是单词边界,例如:\bboy\b  对于字符串”boyfirend“,只会匹配到boy。

1.6环视边界匹配

语法解释
(?=...)要求右边的字符串为指定的表达式。比如:abc(?=def)要求c右边的表达式有def,比如abcdcf;abcd就不匹配
(?!...)要求右边的字符串不为指定的表达式。比如:abc(?!def)要求右边不是def的任何字符串
?<=...

要求右边字符串为指定字符串。比如:(?<=def)abc要求左边是def;

(?<!...)要求右边字符串不为指定字符串。

1.7转义与匹配模式

语法解释

(?i)

不区分大小写;比如:(?i)the可以匹配THE,也可以匹配The
(?m)开启多行模式,^匹配行开始,$匹配行结束
(?s)开启单行模式,.匹配多行模式,包括换行符
\.\*\?转义,匹配  .  *  ?
[.*^(){}]这些元字符在字符数组中就是普通字符,匹配.*^(){}中的任何一个
\\转义,匹配\
\Q\E\Q到\E之间的都视为普通字符

2.Java API:

正则表达式相关的类位于java.util.rege下,主要有两个类:

Pattern:表示正则表达式对象。

Matcher:将正则表达式应用于具体的字符串,通过它对字符串进行处理。

2.1表示正则表达式

划重点:正则表达式中的任何一个'\',在字符串中,需要替换为两个'\'

首先在Pattern类中:

    public static final int DOTALL = 0x20;
    public static final int MULTILINE = 0x08;
    public static final int CASE_INSENSITIVE = 0x02;
    public static final int LITERAL = 0x10;

这三个变量用来标识:单行模式,多行模式,忽略大小写模式,此模式下元字符将失去特殊意义。

将字符串编译成一个Pattern对象:

String regex = “abc(?=def)”;

Pattern pattern  =  Pattern.compile(regex); 

    public static Pattern compile(String regex) {
        return new Pattern(regex, 0);
    }
    public static Pattern compile(String regex, int flags) {
        return new Pattern(regex, flags);
    }

上述代码中的flag就是对应上述的标识 。

Pattern还有一个静态方法:

    public static String quote(String s) {
        int slashEIndex = s.indexOf("\\E");
        if (slashEIndex == -1)
            return "\\Q" + s + "\\E";

        StringBuilder sb = new StringBuilder(s.length() * 2);
        sb.append("\\Q");
        slashEIndex = 0;
        int current = 0;
        while ((slashEIndex = s.indexOf("\\E", current)) != -1) {
            sb.append(s.substring(current, slashEIndex));
            current = slashEIndex + 2;
            sb.append("\\E\\\\E\\Q");
        }
        sb.append(s.substring(current, s.length()));
        sb.append("\\E");
        return sb.toString();
    }

看到\\Q和\\E,应该基本猜出来quote方法就是将字符串中所有字符变为普通字符的作用。

2.2切分

    public String[] split(CharSequence input)//参数类型是String,StringBuilder,StringBuffer的父类

这个方法与String类下面的split()类似。

但还是有些区别:

1.Pattern接收的参数更广泛;

2.如果split方法传递的参数中有元字符或者长度大于1,就会先将regex编译为Pattern对象,再调用Pattern对象的split方法。

2.3验证

验证输入的文本是否符合定义的正则表达式。

String中有方法:

    public boolean matches(String regex) {
        return Pattern.matches(regex, this);
    }

可以看出就是调用了Pattern中的matches方法。

    public static boolean matches(String regex, CharSequence input) {
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(input);
        return m.matches();
    }

就是先调用Pattern的compile方法创建一个Pattern对象,再调用matcher创建一个Matcher对象,再调用Matcher中的matches方法。

         String regex = "\\d{10}";
         String string = "0123456789";
         System.out.println(string.matches(regex));

输出为true。

2.4查找

在Matcher方法中,有如下方法:

    public boolean find()
    public String group() {
        return group(0);
    }
    public String group(int group)

find方法就是需要进行匹配的字符串的起始位置进行匹配,或者如果先前调用过此方法,那么就从该位置上进行继续查找。找到就返回true,之后就可以通过start,end,group方法获取更多信息;

    public static void find(){
        String regex ="\\d{4}-\\d{2}-\\d{2}";
        Pattern pattern = Pattern.compile(regex);
        String str = "today is 2018-11-12,yesterday is 2018-11-11";
        Matcher matcher = pattern.matcher(str);
        while (matcher.find()){
            System.out.println("find:"+matcher.group()+" "+"postion:"+matcher.start()+"-"+matcher.end());
        }
    }

find:2018-11-12 postion:9-19

find:2018-11-11 postion:33-43

2.5替换

我们都知道String下面有多个替换方法:

    public String replace(char oldChar, char newChar)
    public String replace(CharSequence target, CharSequence replacement)

可以看出第一个replace接收的是字符;第二个接收的的CharSequence类型;

    public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }
    public String replaceFirst(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
    }

replaceAll以及replaceFirst是将参数看作正则表达式;

replaceAll是替换所有找的字符串;

replaceFirst是替换第一个找到的;

上面的两个方法中的参数replacement还可以通过“$”进行捕获分组。例如:

         String regx1 ="(\\d{4})-(\\d{2})-(\\d{2})";
         String str1 = "today is 2018-11-12";
         System.out.println(str1.replaceAll(regx1,"$1/$2/$3"));

输出为:today is 2018/11/12;

通过上面的源码我们可以看出是调用了Matcher中的replaceAll或replaceFirst方法。

3.常见表达式:

3.1邮编

邮编就是独立的六位数字:

(?<!\d)\d{6}(?!=\d);

验证结果:

public static final Pattern ZIP_REGX= Pattern.compile("(?<!\\d)\\d{6}(?!\\d)");
    public static void findZipCode(String string){
        Matcher matcher = ZIP_REGX.matcher(string);
        while (matcher.find()){
            System.out.println(matcher.group());
        }
    }

"我的邮编是725300,电话是18992580310"

725300

3.2电话号码

独立的十一位数字,以1开头,第二位取3、5、7、8、9(可能是这些吧),后面的数字无限制:

(?<!\d)1[3,5,7,8,9]\d{9}(?!\d)

验证:

public class PhoneCode {
    public static final Pattern PHONE_NUMBER = Pattern.compile("(?<!\\d)1[3,5,7,8,9]\\d{9}(?!\\d)");
    public static void findMobilePhone(String string){
        Matcher matcher = PHONE_NUMBER.matcher(string);
        while (matcher.find()){
            System.out.println(matcher.group());
        }
    }
}

 "我的邮编是725300,电话是18992580310"

18992580310

3.3时间和日期

时间一般就是

yyyy-mm-dd

(?!\d)\d{4}-(0[1-9] | 1[0-2])-(0[1-9] | [12]\d | 3[01]);

验证:

public class DataCode {
    private static final Pattern DATA_NUMBER=Pattern.compile("(?<!\\d)\\d{4}-(0?[0-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])(?!\\d)");
    public static void findData(String string){
        Matcher matcher = DATA_NUMBER.matcher(string);
        while (matcher.find()){
            System.out.println(matcher.group());
        }
    }
}

今天是2018-11-12;2018-13-32这是一个错误的日期

2018-11-12

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值