正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能以编程的方式,构造复杂的文本模式,并对输入的字符串进行搜索。

有别于其他语言中的\\(我要在正则表达式中插入一个普通的\,无特殊意义),在Java中,\\的意思是我要插入一个正则表达式的反斜线,其后的字符具有特殊意义。如果希望单独匹配一个普通的反斜线,需要写成\\\\(字符串转义为\\),希望在正则内匹配,则是\\。

以下是一些常见的正则符号

字符

C

字符C

\xhh

十六进制值为hh的字符(只能表示AscII)

\uhhhh

十六进制值为hhhh的Unicode字符

\t

制表符Tab

\n

换行符

\r

回车

\f

换页

\e

转义

字符类

.

任意一个字符

[abc]  [a|b|c]

任何a,b,c字符

[^abc]

除了a,b,c之外的任何字符

[a-zA-z]

从a-z或A-Z的任何字符(任何英文字母)

[abc[def]]

任何abcdef字符(可合并)

[a-z&&[abc]]

任何a,b,c字符(交集)

\s

空白符(空格、tab、回车、换行换页)

\S

非空白符

\d

数字字符

\D

非数字字符

\w

词字符([a-zA-Z0-9])

\W

非词字符

逻辑操作符

XY

Y跟在X后面

X|Y

X或Y

(X)

捕获分组,可以在表达式中用\i引用第i个捕获组

(?:X)

仅分组但不捕获,不占下标不记忆

边界匹配符

^

一行的起始

$

一行的结束

\b

词的边界

\B

非词的边界

\G

前一个匹配的结束

捕获组的作用是将符合X条件的内容抓住,后续通过捕获组的 \i 编号引用。

注意:

1、\i编号从1开始,\0代表匹配的整个字符串。

2、捕获组每次匹配时都会捕获,但通常只返回最后/当前一次匹配的内容。多次匹配需要循环遍历。

应用实例:

在字符串中查找连续重复数字:

String regex = "(\\d)\\1";

                            // (\\d) 捕获一个数字

                            // \\1    引用同一个数字

"11".matches(regex);        // true

"12".matches(regex);        // false

量词

量词描述了一个模式吸收输入文本的方式

*

匹配前一个字符或分组任意次(>=0)

+

匹配前一个字符或分组至少一次

匹配前一个字符或分组0次或一次

{n}

匹配前一个字符或分组恰好n次

{n,}

匹配前一个字符或分组至少n次

{n,m}

匹配前一个字符或分组n至m次

java的量词分为三种:贪婪型,勉强型,占有型。

贪婪型:为所有表达式发现尽可能多的匹配,即默认模式。如X?,X*,X+,X{n}。贪婪型量词优先匹配尽可能多的字符,若整体匹配失败,则逐个吐出字符进行回溯,直到匹配成功或尝试完所有可能。

勉强型:为所有表达式优先匹配正则所需的最小数量,用问号?来指定。具体到编码上就是在 贪婪型的后面加一个问号。如X??优先匹配0个X,如果0个无结果再匹配一个X。

占有型:使用+号指定,作用为拒绝回溯,常用于防止正则表达式失控,使正则执行更有效。

如X*+X,匹配XXXX的时候X*匹配全部X,最后一个X无字符匹配,返回false。

可以使用正则表达式的String函数

1、最简单途径:String类内建功能string.matches(redex)

如:”112233”.matches(“\\d+”)

2、split()方法,功能是将字符串从正则表达式匹配的地方切开(并舍弃匹配的部分)。

如: String s = "11a22b33c";
        String[] split = s.split("[abc]");
        for (String s1 : split) {
                System.out.println(s1);
        }

输出: 11

            22

            33

split还有一个重载的方法,允许传入一个int类型参数limit,限制分割的次数。

3、替换方法replaceFirst()和replaceAll()

注意:replace方法仅匹配字面字符串,不适配正则表达式。

replaceAll("\\d+", "X")

replaceFirst("\\d+", "X")

区别就是replaceAll全部匹配替换,而replaceFirst只替换第一个匹配。

Pattern和Matcher

比起功能有限的String类,我们更愿意构造功能强大的正则表达式对象。只需要导入java.util.regex包,然后用静态方法static Pattern.compile()编译正则表达式即可。它会根据输入的String类型的正则表达式生成一个Pattern对象,接着调用pattern.matcher()方法传入需要检索的字符串,生成一个Matcher对象,就可以更加灵活地使用正则表达式的匹配功能了。

将上文split方法用pattern重写。

        String s = "11a22b33c";

        Pattern pattern = Pattern.compile("\\d+");

        Matcher matcher = pattern.matcher(s);

        while (matcher.find()){

            System.out.println(matcher.group());

        }

find方法尝试在输入序列中找到与模式匹配的下一个子序列。此方法从该匹配器的区域起始位置开始,或者,如果之前调用该方法成功且匹配器自那之后未被重置,则从上次匹配未匹配到的第一个字符开始。如果匹配成功,则可以通过start、end和group方法获取更多信息。当且仅当输入序列的一个子序列与此匹配器的模式相匹配时,结果为 true,如果没有匹配,则为false。可以向find方法中传入一个int类型参数表示起始下标,默认为0(开头)。

start()和end()方法分别返回匹配的起始索引和结束索引,group()方法返回捕获组的内容,默认为第0个组,即整个匹配字符串。可以向这三个方法中传递分组编号i,将会返回对应分组的信息。

matcher.matches()方法匹配一整个字符串,成功返回true,失败返回false。

matcher.lookingAt()方法与matches类似,不过不要求匹配整个字符串,如果有从头开始的子串符合正则,则输出true,并可以使用start,end,group查看详细信息。

matcher.reset()方法可以让现有Matcher对象应用于一个新的字符序列。

总结:

find()可以在输入的任意位置定位正则表达式,而lookingAt()和matches()只有在正则表达式和输入的最开始处就开始匹配才会成功。matches()只有在整个输入都匹配正则表达式时才会成功,而lookingAt()只要输入的第一部分匹配就会成功。

Pattern标记

Pattern类的compile()方法还有一个重载版本public static Pattern compile(String regex, int flags),它接受一个标记参数flag来调整匹配的行为,对正则表达式进行调整。

flags 用 int 类型存储,每个 flag 常量是 2 的幂,通过位或运算组合。

如:Pattern pattern = Pattern.compile("\\d+",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

编译标记

效果

Pattern.CANON_EQ

0x80

完全规范等价匹配

Pattern.CASE_INSENSITIVE(?i)

0x02

忽略大小写(仅ASCII)

Pattern.COMMENTS(?x)

0x04

忽略空格符和#开头的行,用于写正则注释

Pattern.DOTALL(?s)

0x20

.匹配包括换行符在内的所有字符(默认情况不包含换行符)

Pattern.MULTILINE(?m)

0x08

多行模式时^和$分别匹配每一行的开头和结尾

Pattern.UNICODE_CASE(?u)

0x40

配合CASE_INSENSITIVE,忽略Unicode字符大小写

Pattern.UNIX_LINES(?d)

0x01

只有\n作为行终止符

标记也可以直接在正则表示中使用,只需要将上表括号内的字符插入到正则表达式中希望它起作用的地方就行。

拥有了Pattern和Matcher工具之后,我们就可以将上文提到的split和replaceAll等方法进行升级。

split

Pattern类的split方法接收CharSequence字符序列,而不是String,泛用性更强,可以拆分任意字符序列。此外,它不会去除末尾的空字符串。

在多次使用同一正则表达式时,使用Pattern 预编译复用性能更好。

Pattern p = Pattern.compile(",");

for (String s : list) {

    p.split(s);   // 复用同一个 Pattern,正则只编译一次

}



// String.split 每次都要编译正则

for (String s : list) {

    s.split(","); // 每次重新编译正则

}

replace替换操作

public Matcher appendReplacement(StringBuffer sb, String replacement)

appendReplacement方法执行渐进式替换,允许调用其他方法来生成或处理replacement,使我们能够通过编程的方式来将目标分割为组,并分别匹配替换。替换后的字符串存入StringBuffer或StringBuilder。

public Matcher appendTail(StringBuffer sb)

在执行了一次或多次appendReplacement后,可以调用appendTail将输入字符串剩余部分全部复制到StringBuffer中。

CharSequence字符序列接口

接口CharSequence抽象出了字符序列的一般定义,实现类有CharBuffer、String、StringBuffer、StringBuilder,能让方法接收多种字符来源。多数正则表达式都接受CharSequence类型的参数。

一般化定义:

interface CharSequence {

charAt(int i);

length();

subSequence(int start, int end);

toString();

}

此外,Java还实现了:

isEmpty(),

chars(), 返回IntStream流表示Unicode码点(BMP内)

codePoints(),返回IntStream流表示Unicode码点(包含emoji和生僻字,更全面)

compare(), 用字典序比较两个序列,返回int(-1,0,1)。

更多推荐