正则表达式


#Java 笔记


正则表达式, regular expression,可以用来搜索和处理文本。

语法

正则表达式中有一些字符代表着特定的含义,比如+表示匹配前面的字符/表达式0到多次。这种特定含义的字符,可以看做是正则表达式语言中的保留关键词,有些资料中也称作为元字符。有些元字符之间需要成对出现,例如 {}。元字符之前也可以搭配使用。有些元字符,在不同的使用场景中含义不同。

语法 说明
^ 匹配开始位置
$ 匹配结束位置
\ 转义
* 匹配前面的字符/表达式 0 次或多次
+ 匹配前面的字符/表达式 1 次或多次
? 匹配前面的字符/表达式 0 次或1次。在 *+?{m}{m,}{m,n} 之后出现时,代表匹配模式是非贪心的
. 默认模式下, 匹配\r\n外的所有单个字符。
{m} 匹配前面的字符/表达式 m 次。 m >=0 。默认是贪心匹配
{m, n} 匹配前面的字符/表达式 m 次到 n 次。 m >=0, n >= m。默认是贪心匹配
{m,} 匹配前面的字符/表达式至少 m 次。 m >=0。默认是贪心匹配
a|b 匹配 a 或者 b
a|xyz 匹配 a 或者 xyz
[xyz] 匹配 x 或者 y 或者 z
[^xyz] 匹配不是 x、y、z 的字符
[a-z] 匹配 a 到 z 之间的字符,包含 a、z。
[^a-z] 匹配不是 a 到 z 之间的字符,a、z 也不匹配。
[0-9] 匹配 0 到 9 之间的字符,包含 0、9。
(pattern) 匹配 pattern 对应的正则表达式,并生成捕获组
(?:pattern) 匹配 pattern 对应的正则表达式,生成捕获组
(?<groupName>pattern) 匹配 pattern 对应的正则表达式,并生成捕获组,组名是groupName
\1 反向引用前面的编号为1的捕获组。不会生成捕获组。类似的,还有\2\3等。
\b 匹配单词边界。在英文中,单词之间用空格隔开。单词边界是指单词和空格之间的部分。这是一个抽象的概念,因为单词和空格之间的部分是看不到的。
\B 匹配非单词边界。
\d 匹配数字字符。即[0-9]
\D 匹配非数字字符。即[^0-9]
\n 匹配换行符
\r 匹配回车符
\f 匹配换页符
\t 匹配制表符
\v 匹配垂直制表符
\s 配置空白符号。即[\n\r\f\t\v]
\S 匹配非空白符号
\w 匹配字母、数字、下划线。即[A-Za-z0-9_]
\W 不匹配字母、数字、下划线。即[^A-Za-z0-9_]
\x23 匹配 ascii 表中十六进制的 23 对应的字符。\x后必须是2个16进制字符。
\u1234 匹配 16 进制的 1234 对应的 unicode 字符。\u后必须是4个16进制字符。这是 UTF-16 的字符编码。表情符号以及一些生僻字,会需要两个\u才行。例如\ud83d\ude0a 对应 😊
其他字符 匹配对应的字符

贪心匹配 是尽可能多的匹配,非贪心匹配是尽可能少的匹配。

转义规则

在正则表达式中?代表匹配前面的字符/表达式0次或者1次。如果要匹配字符?,那么正则表达式处理引擎应该看到的是\?。我们在 Java 中书写的字符串形式的正则表达式应该是 \\?

要匹配字符\,Java 中要写成\\\\

注意,在其他一些编程语言中,匹配字符?,只需要\?即可, \\? 反而是错误的。

使用 String 的 matches 方法判断字符串是否匹配正则表达式

matches 返回 boolen 值。

示例: 判断字符串中是否有英文句号.

public class RegexTest {

    public static void main(String[] args) {
        String regex = "(\\s|\\S)*\\.(\\s|\\S)*";
        System.out.println( "".matches(regex) );             // false
        System.out.println( "你好".matches(regex) );          // false
        System.out.println( "你好.".matches(regex) );         // true
        System.out.println( "你好 .".matches(regex) );        // true
        System.out.println( "你好 . 世界".matches(regex) );    // true
        System.out.println( "你好\n .".matches(regex) );      // true
    }
}

\s 用于匹配空白字符,\S 匹配非空白字符。\.用于匹配英文逗号.

示例: 判断字符串不为空,且只含有数字

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[0-9]+";
        System.out.println( "".matches(regex) );           // false
        System.out.println( "你好".matches(regex) );        // false
        System.out.println( "0".matches(regex) );          // true
        System.out.println( "012".matches(regex) );        // true
        System.out.println( "129".matches(regex) );        // true
        System.out.println( "129.".matches(regex) );       // false
    }
    
}

示例: 判断字符串是整数或小数, 小数点后最多8位

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[0-9]+\\.?[0-9]{0,8}";
        System.out.println( "".matches(regex) );                // false
        System.out.println( "你好".matches(regex) );            // false
        System.out.println( "0".matches(regex) );               // true
        System.out.println( "012".matches(regex) );             // true
        System.out.println( "129".matches(regex) );             // true
        System.out.println( "129.".matches(regex) );            // true
        System.out.println( "0129.".matches(regex) );           // true
        System.out.println( "129.000".matches(regex) );         // true
        System.out.println( "129.000111".matches(regex) );      // true
        System.out.println( "129.000111222".matches(regex) );   // false
        System.out.println( "129x00222".matches(regex) );       // false
    }

}

012 也返回 true,在一些场景下,是不符合预期的。

示例: 判断字符串含有小数点,且小数点前后都是数字,且小数点前若有多个数字,最高位不能是0

public class RegexTest {

    public static void main(String[] args) {
        String regex = "(([1-9]+[0-9]*)|0)\\.[0-9]+";
        System.out.println( "".matches(regex) );                // false
        System.out.println( "你好".matches(regex) );            // false
        System.out.println( "0".matches(regex) );               // false
        System.out.println( "012".matches(regex) );             // false
        System.out.println( "129".matches(regex) );             // false
        System.out.println( "129.".matches(regex) );            // false
        System.out.println( "0129.".matches(regex) );           // false
        System.out.println( "129.000".matches(regex) );         // true
        System.out.println( "129.000111".matches(regex) );      // true
        System.out.println( "129.000111222".matches(regex) );   // true
        System.out.println( "129x00222".matches(regex) );       // false
    }

}

示例: 判断字符串为空,或者仅由字母abc中的一个或多个组成

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]*";
        System.out.println( "".matches(regex) );         // true
        System.out.println( "a".matches(regex) );        // true
        System.out.println( "ab".matches(regex) );       // true
        System.out.println( "abc".matches(regex) );      // true
        System.out.println( "abccba".matches(regex) );   // true
        System.out.println( "abc-cba".matches(regex) );  // false
        System.out.println( "ABC".matches(regex) );      // false
    }

}

使用 Pattern.matches 方法判断字符串是否匹配正则表达式

String 的 matches 方法,底层调用的 Pattern.matches 方法。

Pattern.matches 方法源码:

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

使用示例:

import java.util.regex.Pattern;

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]*";
        System.out.println( Pattern.matches(regex, "a") );    // true
        System.out.println( Pattern.matches(regex, "123") );  // false
    }

}

使用 Pattern.compile 编译正则表达式

示例:

import java.util.regex.Pattern;

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]*";
        Pattern pattern = Pattern.compile(regex);

        System.out.println( pattern.matcher("a").matches() );    // true
        System.out.println( pattern.matcher("123").matches() );  // false
    }

}

使用 Matcher 找到匹配正则表达式的字符串

示例: 使用 find(), start(), end()

find() 返回是否找到匹配正则表达式的字符串。start()、end() 返回所匹配的字符串的位置。

第一次执行 find() 时,会从字符串初始位置开始查找。在找到了匹配的字符串后,会变更下次查找的初始位置。

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]+";
        String data = "a123bc";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.printf(
                    "start: %d, end: %s, string: %s\n",
                    matcher.start(),
                    matcher.end(),
                    data.substring(matcher.start(), matcher.end())
            );
        }
    }

}

执行结果:

start: 0, end: 1, string: a
start: 4, end: 6, string: bc

理解【捕获组】

在正则表达式中,可以应该能括号对匹配的内容进行分组。我们可以利用这个机制来提取我们需要的内容。

例如,正则表达式 ((a)(b(c))),会匹配 abc 这样的连续字符。组编号和内容如下:

组编号 内容
1 ((a)(b(c)))
2 (a)
3 (b(c))
4 (c)

示例: 获取捕获组的数量和每个组的内容

代码示例1:

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "((a)(b(c)))";
        String data = "abc-ab";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);
        while (matcher.find()) {
            System.out.println("groupCount: " + matcher.groupCount());  // 获取分组数量
            System.out.println("group(0): " + matcher.group(0));        // 0 不代表捕获组, 代表匹配的字符串
            System.out.println("group(1): " + matcher.group(1));
            System.out.println("group(2): " + matcher.group(2));
            System.out.println("group(3): " + matcher.group(3));
            System.out.println("group(4): " + matcher.group(4));
        }

    }

}

执行结果:

groupCount: 4
group(0): abc
group(1): abc
group(2): a
group(3): bc
group(4): c

注意, group(0) 是匹配的字符串,不算在捕获组中。

代码示例2:

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "((a)(b(c)))[a-z]*";
        String data = "abc-abcd";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);
        while (matcher.find()) {
            System.out.println("-------");
            System.out.println("groupCount: " + matcher.groupCount());
            System.out.printf("start: %d, end: %s\n", matcher.start(), matcher.end());
            System.out.println("group(0): " + matcher.group(0));
            System.out.println("group(1): " + matcher.group(1));
            System.out.println("group(2): " + matcher.group(2));
            System.out.println("group(3): " + matcher.group(3));
            System.out.println("group(4): " + matcher.group(4));
        }

    }

}

执行结果:

groupCount: 4
start: 0, end: 3
group(0): abc
group(1): abc
group(2): a
group(3): bc
group(4): c
-------
groupCount: 4
start: 4, end: 8
group(0): abcd
group(1): abc
group(2): a
group(3): bc
group(4): c

代码示例3:

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "<h2>(.*)</h2>";
        String data = "你好<h2>世界</h2>";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);
        while (matcher.find()) {
            System.out.println(matcher.group(1));
        }
    }

}

执行结果:

世界

代码示例4:

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]+";
        String data = "a123bc";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);
        System.out.println( matcher.groupCount() ); 
    }

}

执行结果:

0

示例: 命名捕获组

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "(?<num>[0-9]+)";
        String data = "123-789";
        Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.println( matcher.group() );
            System.out.println( matcher.group("num") );
            System.out.println("----");
        }
    }

}

运行结果:

123
123
----
789
789
----

示例: 反向引用

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "([a-z]*)-\\1";
        String data = "abc-abcd@mail.com";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);
        while (matcher.find()) {
            System.out.println("-------");
            System.out.println("groupCount: " + matcher.groupCount());
            System.out.println("group(0): " + matcher.group(0));
            System.out.println("group(1): " + matcher.group(1));
        }

    }

}

执行结果:

-------
groupCount: 1
group(0): abc-abc
group(1): abc

String 的 replaceFirst、replaceAll 的特殊用法:调整捕获组位置

replaceFirst、replaceAll 的第一个参数是正则表达式,第二个参数是要替换的内容。但是第2个参数比较特殊, 在这里面$1代表第1个捕获组的内容,$2代表第2个捕获组的内容,依次类推。

如果要替换为字符串$1,要写成\\$1

代码示例:

public class RegexTest {

    public static void main(String[] args) {
        String regex = "([abc]+)-([a-z]+)";
        System.out.println( "ab-ccc".replaceFirst(regex, "$2-$1"));
        System.out.println( "ab-ccc".replaceFirst(regex, "\\$2-$1"));
    }

}

执行结果:

ccc-ab
$2-ab

贪心匹配与非贪心匹配

贪心匹配 是尽可能多的匹配,非贪心匹配是尽可能少的匹配。

*+?{m}{m,}{m,n} 默认是贪心匹配。当?出现在他们后面时,代表非贪心匹配

示例:贪心匹配

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]+";
        String data = "a123bc";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.printf(
                    "start: %d, end: %s, string: %s\n",
                    matcher.start(),
                    matcher.end(),
                    data.substring(matcher.start(), matcher.end())
            );
        }
    }

}

执行结果:

start: 0, end: 1, string: a
start: 4, end: 6, string: bc

示例: 非贪心匹配

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]+?";
        String data = "a123bc";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.printf(
                    "start: %d, end: %s, string: %s\n",
                    matcher.start(),
                    matcher.end(),
                    data.substring(matcher.start(), matcher.end())
            );
        }
    }

}

执行结果:

start: 0, end: 1, string: a
start: 4, end: 5, string: b
start: 5, end: 6, string: c

使用 String 的 replaceAll 替换掉符合正则表达式的字符串

replaceAll 会替换掉所有符合正则表达式的子字符串。

代码示例:

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]+";
        System.out.println( "ab123abc".replaceAll(regex, "替换后"));
    }

}

执行结果:

替换后123替换后

使用 String 的 replaceFirst 替换掉符合正则表达式的字符串

replaceFirst 会替换掉第一个符合正则表达式的子字符串。

代码示例:

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[abc]+";
        System.out.println( "ab123abc".replaceFirst(regex, "替换后"));
    }

}

执行结果:

替换后123abc

使用 Pattern.DOTALL 让英文句号.支持匹配\r\n

默认是不匹配换行符的。

代码示例:

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = ".*";
        String data = "aaaa\nbbbbb";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);

        if (matcher.find()) {
            System.out.println( matcher.group() );
        }
    }

}

执行结果:

aaaa

Pattern.compile 设置模式为 Pattern.DOTALL 时,则支持匹配\r\n

代码示例:

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = ".*";
        String data = "aaaa\nbbbbb";
        Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        Matcher matcher = pattern.matcher(data);

        if (matcher.find()) {
            System.out.println( matcher.group() );
        }
    }

}

执行结果:

aaaa
bbbbb

使用 ^$ 匹配开始位置和结束位置

示例: 使用 ^ 匹配开始位置

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "^[0-9]+";
        String data = "123abc456";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.println( matcher.group() );
        }
    }

}

执行结果:

123

只匹配到了 123, 没有匹配到 456。

###示例: 使用 $ 匹配结束位置

代码示例1

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[0-9]+$";
        String data = "123abc456";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.println( matcher.group() );
        }
    }

}

执行结果:

456

代码示例2

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[0-9]+$";
        String data = "123abc456\n789";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.println( matcher.group() );
        }
    }

}

执行结果:

789

为什么没有匹配到 456 ?因为默认情况下结束位置是指整个文本的结束位置,而不是每一行的结束位置。

若要匹配到456,可以指定Pattern.MULTILINE来实现。见下面的示例。

示例: 使用Pattern.MULTILINE$匹配每一行的结束位置

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

public class RegexTest {

    public static void main(String[] args) {
        String regex = "[0-9]+$";
        String data = "123abc456\n789";
        Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(data);

        while (matcher.find()) {
            System.out.println( matcher.group() );
        }
    }

}

执行结果:

456
789


( 本文完 )