Java 正则表达式


(注:下面的所有格式都是指阅读格式,字符串格式需要注意\的转义,例如\d在字符串中表示为 "\\d",表示普通字符 \ 的阅读格式为 \\,而字符串中表示为 "\\\\"

创建正则表达式

元字符

([{\^-=$!|]})?*+.

两种方式可以将元字符作为普通字符来处理

  1. \<metacharacter>
  2. \Q<metacharacter>\E
元字符说明
()标记子表达式的开始和结束位置
[]中括号表达式的开始和结束位置
\转义下一个字符
^匹配一行的开头
$匹配一行的结尾
-表示前后两个字符的范围
=
!
\|指定两项之间任选一项
?指定前面子表达式可出现 0次或 1 次
*指定前面子表达式可出现 0 次或多次
+指定前面子表达式可出现 1 次或多次
.匹配处 \n 之外的任何字符

转义字符:

字符说明
\0mnn八进制数 0mnn 所表示的字符
\xhh十六进制值 0xhh 所表示的字符
\uhhhh十六进制值 0xhhhh 所表示的 Unicode 字符

\uhhhh 代表要匹配的 Unicode 字符,或者 "\x{" + Integer.toHexString(codePoint) + "}";\u6771 匹配的就是一个汉字“東”。

ASCII 码的字符可以如下表示:

十六进制八进制含义
\x20\032空格
\x23\035#

单个字符的匹配集合

预定义的单字符匹配集
模式对应的单字符匹配集
·任意字符
\ddigit 数字:[0-9]
\D[^0-9]
\sspace 空白字符:空格\x0B、制表符\t、回车符\r、换页符\f、换行符\n
\S[^\s]
\wword 所有的单词字符:[a-zA-Z_0-9]
\W[^\w]
方括号表达式

单字符匹配可以用 [<匹配集合>],比如 [a-z&&[^bc]]。其中

  • - 表示范围
  • ^表示这是一个取反的匹配集合
  • && 表示取交集
  • [a-z[A-Z]] 表示取并集
  • &&[^<要减去的集合>] 配合使用可以达到减法的效果。

数量标识符

数量标识符有三类模式:

  • 贪婪模式(默认):一直匹配下去,知道无法匹配为止
  • 勉强模式:用后缀 ? 表示,只会匹配最少的字符
  • 占有模式:用后缀 + 表示,不懂
贪婪模式(默认)勉强模式占有模式含义
X?X??X?+0 或 1
X*X*?X*+0 或更多
X+X+?X++1 或更多
X{n}X{n}?X{n}+n 次
X{n,}X{n,}?X{n,}+n 或更多
X{n,m}X{n,m}?X{n,m}+n~m 次
零长度匹配

前两行中的 *? 允许 零长度匹配。这种匹配的标志就是匹配的起始和结束索引相同。在零长度匹配之后,end == first,如果继续从 end 处开始,那么会陷入死循环,所以 Matcher 的 find 方法中会有一步检查:

int nextSearchIndex = last;
if (nextSearchIndex == first)
    nextSearchIndex++;

零长度匹配示例:

Enter your regex: a?
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.

Enter your regex: a*
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.

Enter your regex: a*
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "" starting at index 3 and ending at index 3.
I found the text "aaaa" starting at index 4 and ending at index 8.
I found the text "" starting at index 8 and ending at index 8.
I found the text "" starting at index 9 and ending at index 9.

量词除了可以指定它左边的单个字符的数量,还可以和“单字符匹配集”、“匹配组”配合使用。

贪婪模式、勉强模式、占有模式的区别

贪婪量词的匹配顺序是,首先匹配量词吞下能匹配的最大字符串,然后判断整个表达式是否匹配,如果不匹配,量词就回退最后一个字符,继续判断整个表达式是否匹配,一直到量词允许的最少字符。比如,.*foo 对输入字符串 xfooxxxxxxfoo 进行匹配时,.* 首先吞下整个字符串,此时匹配失败,因为 .* 后面的 foo 找不到匹配项。所以贪婪量词就会逐个字符地回退,直到 .* 只吞下 xfooxxxxxx 时,匹配成功。

不情愿量词的匹配顺序是,首先不情愿量词吞下能匹配的最小字符串,然后判断整个表达式是否匹配,如果不匹配,就不情愿地吞下下一个字符,继续判断整个表达式是否匹配,一直到不情愿量词吞下允许的最大字符。比如,.*?foo 对输入字符串 xfooxxxxxxfoo 进行匹配时,首先吞下零长度,此时匹配失败(因为.*? 后的 foo 没找到匹配),所以不情愿量词就逐个字符地吞下,吞下 x 时,匹配成功,然后从这次匹配的末尾继续匹配。

占有量词的匹配顺序是,首先匹配量词吞下能匹配的最大字符串,然后判断整个表达式是否匹配,如果不匹配,占有量词不能回退字符,直接结束匹配。比如,.*+foo 对输入字符串 xfooxxxxxxfoo 进行匹配时,.*+ 首先吞下整个字符串,此时匹配失败,因为 .* 后面的 foo 找不到匹配项,因为占有量词不能回退吞下的字符串,所以返回不匹配。

Enter your regex: .*foo  // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.

占有量词:

Enter your regex: a*+foo
Enter input string to search: bbaaafoocc
I found the text "aaafoo" starting at index 2 and ending at index 8.

Enter your regex: a*+afoo
Enter input string to search: bbaaafoocc
No match found.

捕获群组:圆括号表达式

群组可以把多个字符组成一个单元,在表达式中通过一系列的括弧来创建群组,群组中的字符集合可以被保存在内存中,被取出来使用。

群组的序号 可以通过数左括号的序号来判断,比如对于表达式 ((A)(B(C))),有 4 个群组:

  1. ((A)(B©))
  2. (A)
  3. (B©)
  4. ©

Matcher 对象的 groupCount 方法可以返回群组数,上面的例子的返回值就是 4。群组中有一个特殊的序号,第 0 号群组,它代表整个表达式。以 (? 开头的群组是 非捕获群组,它们不会被捕获,不会被计算进表达式的群组总数中。

群组的序号可以用于后续的操作中,比如 Matcher 有如下实例方法:

  • public int start(int group): 某个群组的起始索引
  • public int end (int group): 某个群组的结束索引
  • public String group (int group): 某个群组的字符内容

群组的 反向引用,是指表达式中,在群组之后可以引用前面捕获的群组的内容,通常用 \<要引用的群组序号> 来实现。下例中,(\d\d) 匹配两个数字并构成一个捕获群组,\1 引用的这两个数字。

Enter your regex: (\d\d)\1
Enter input string to search: 1212
I found the text "1212" starting at index 0 and ending at index 4.

Enter your regex: (\d\d)\1
Enter input string to search: 1234
No match found.

边界匹配

前面描述的匹配可以发生在输入字符串的任意位置,可以在开头,可以在结尾,可以在中间。边界匹配器可以更精确地限定要匹配的位置。

边界匹配符描述
^行首
$行尾
\bboundary 单词边界
\B非单词边界
\A输入的开始处
\G上一次匹配的结束索引
\Z输入的结束处,不包括终止符
\z输入的结束处

比如,\bdog\b 匹配单词,\bdog\B 匹配单词前缀,\Bdog\b 匹配单词后缀。

下面的例子中,第二个 dog 没有被匹配,因为它并不是开始于前一个匹配的末尾处。

Enter your regex: \Gdog
Enter input string to search: dog dog
I found the text "dog" starting at index 0 and ending at index 3.

使用正则表达式

  1. 将正则表达式编译为 Pattern 对象,Pattern 对象为正则表达式编译后在内存中的表现形式。
  2. 从 Pattern 对象创建 Matcher 对象,执行匹配所涉及的状态保留在 Matcher 中。

Pattern 为不可变类,线程安全。

Pattern 类编译正则表达式

获取 Pattern 对象

Pattern 的静态方法 compile 接受一个用于匹配的正则表达式来创建 Pattern 对象,同时可以指定一系列 标记位 来设定匹配的特殊模式。

标记位描述
Pattern.CASE_INSENSITIVE不区分大小写
Pattern pattern = Pattern.compile("[a-z]$", Pattern.CASE_INSENSITIVE);

除了在 compile 方法中添加参数来设置特殊模式,还可以在正则表达式中 嵌入标记位

标记位等价的嵌入表达式
Pattern.CASE_INSENSITIVE(?i)
Enter your regex: (?i)foo
Enter input string to search: FOOfooFoOfoO
I found the text "FOO" starting at index 0 and ending at index 3.
I found the text "foo" starting at index 3 and ending at index 6.
I found the text "FoO" starting at index 6 and ending at index 9.
I found the text "foO" starting at index 9 and ending at index 12.

获取 Matcher 对象

Pattern 类常用的方法是 matcher(input) 来获取 Matcher 对象,也可以直接使用静态方法来匹配一次:

Pattern.matches("\\d","1"); // true

分割方法

split(input): String[]:利用正则表达式分割输入的字符串。

Pattern p = Pattern.compile("\\d");
String[] items = p.split("one9two4three7four1five");
for(String s : items) {
    System.out.print(s + " "); // one two three ...
}

当无匹配时,返回原来的字符串作为数组的唯一元素。

如果第一次匹配结果的起始索引和结束索引都为 0(零长度匹配),就忽略这次结果。如果第一次匹配结果的起始索引为 0,但结束索引不为 0(非零长度匹配),那么数组的第一个元素为空字符串。其他的匹配过程允许零长度匹配。

split(CharSequence input, int limit): String[]

  • 当 limit > 0 时,返回的数组大小不会超过 limit。最多进行 limit - 1 次匹配。如果进行到了第 limit - 1 次匹配,那么将上次匹配的 end 索引开始的所有字符存为结果数组的第 limit 个元素。
  • 当 limit <= 0 时,返回数组大小没有长度限制,匹配可以进行任意次。
  • 当 limit == 0 时,数组最后的空字符串都会被丢弃。
if (limit == 0)
while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
    resultSize--;
示例:

```java
String REGEX = "a*";
String input = "abaaef";
Pattern p = Pattern.compile(REGEX);
String[] items = p.split(input, -2);
for(String s : items) {
    System.out.print("\"" + s + "\" "); // "" "" "b" "" "e" "f" ""
```

Matcher 类执行匹配

判断是否匹配:

  • matches(): boolean:最严格的,判断字符串 整体 是否匹配
  • lookingAt(): boolean:判断字符串 前面部分 是否匹配
  • find(): boolean:最宽松,判断 字符串中 是否存在匹配
Pattern p = Pattern.compile("b");
System.out.println(p.matcher("abaaef").lookingAt()); // false
System.out.println(p.matcher("baaef").lookingAt()); // true

取出子串:

group(): String // 返回上一次匹配的子串,默认为 group(0)

获取索引:

方法名描述
public int start()上一次匹配的起始索引
public int end()上一次匹配的结束索引

重置:

reset(CharSequence) // 清除之前的匹配状态,并设置新的测试字符
reset() // 清除之前的匹配状态

更简单的使用:String 的方法

String 类中提供了若干方法用于正则表达式操作:

matches(regex): boolean // 判断是否匹配指定的 regex
replaceAll(regex, replacement): String // 将该字符串中所有匹配 regex 的字串替换
replaceFirst(regex, replacement): String // 第一个匹配的替换
split(regex): String[] // 以 regex 为分隔符,把字符串分割成多个子串

String 的 replace 方法其实是使用 Matcher 来实现的:

// java.lang.String
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
        this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

测试程序

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

public class RegexTestHarness {

    public static void main(String[] args){
        Console console = System.console();
        if (console == null) {
            System.err.println("No console.");
            System.exit(1);
        }
        while (true) {

            Pattern pattern =
            Pattern.compile(console.readLine("%nEnter your regex: "));

            Matcher matcher =
            pattern.matcher(console.readLine("Enter input string to search: "));

            boolean found = false;
            while (matcher.find()) {
                console.format("I found the text" +
                    " \"%s\" starting at " +
                    "index %d and ending at index %d.%n",
                    matcher.group(),
                    matcher.start(),
                    matcher.end());
                found = true;
            }
            if(!found){
                console.format("No match found.%n");
            }
        }
    }
}

官方资料

  1. The Java™ Tutorials
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值