Java正则表达式

1. 匹配规则

正则表达式的匹配规则是从左到右按规则匹配。

1.1 \ 匹配特殊字符

如果正则表达式有特殊字符,那就需要用\转义。

比如:

正则表达式a\&c,其中\&是用来匹配特殊字符&的,它能精确匹配字符串"a&c",但不能匹配"ac""a-c""a&&c"等。

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

1.2 匹配任意字符

我们可以使用 "." 来匹配一个任意的字符。

例如:String reg = "a.c"; 可以匹配如下字符串:

  • "abc",因为.可以匹配字符b
  • "a&c",因为.可以匹配字符&
  • "acc",因为.可以匹配字符c;

但是 "." 不能匹配 "ac""a&&c",因为"."匹配一个字符且仅限一个字符。

1.3 匹配数字

用 "." 可以匹配任意字符,范围太大了。而 \d 可以用来匹配 0~9 这样的数字,且仅匹配单个数字字符。

比如: String reg = "00\d"; 可以匹配如下字符串:

  • "007",因为\d可以匹配字符7
  • "008",因为\d可以匹配字符8

1.4 匹配常用字符

用 \w 可以匹配一个字母、数字、下划线。w的意思是 word。

比如:String reg = "java\w" 可以匹配如下字符串:

  • "javac",因为\w可以匹配英文字符c
  • "java9",因为\w可以匹配数字字符9;。
  • "java_",因为\w可以匹配下划线_

1.5 匹配空格字符

用 \s 可以匹配一个空格字符,空格字符包括 (" " 和 "\t" 即tab字符)。

比如:String reg = "a\sc";可以匹配如下字符串:

  • "a c",因为\s可以匹配空格字符;
  • "a c",因为\s可以匹配tab字符\t

1.6 匹配非数字

用 \d 可以匹配一个数字,相反,用 \D 可以匹配一个非数字。

类似的,\W 可以匹配 \w 不能匹配的字符。

类似的,\S 可以匹配 \s 不能匹配的字符。

例如:String reg = "00\D";可以匹配如下字符串:

  • "00A",因为\D可以匹配非数字字符A
  • "00#",因为\D可以匹配非数字字符#

1.7 重复匹配

比如一个 \d 可以匹配一个数字字符,但如果要匹配多个数字字符,该怎么进行匹配呢?

1.7.1 修饰符 * 可以匹配任意个字符,包括0个字符。

比如:String reg = "A\d*"; 可以匹配如下字符串:

  • A:因为\d*可以匹配0个数字;
  • A0:因为\d*可以匹配1个数字0
  • A380:因为\d*可以匹配多个数字380

1.7.2 修饰符 + 可以匹配至少一个字符。

比如:String reg = "A\d+"; 可以匹配如下字符串:

  • A0:因为\d+可以匹配1个数字0
  • A380:因为\d+可以匹配多个数字380

1.7.3 修饰符 ?可以匹配 0或1 个字符。

比如:String reg = "A\d?";可以匹配如下字符串:

  • A:因为\d?可以匹配0个数字;
  • A0:因为\d?可以匹配1个数字0

1.7.4 用 {n} 精确匹配 n 个字符。

比如:String reg = "A\d{3}";可以匹配如下字符串:

  • A380:因为\d{3}可以匹配3个数字380

1.7.5 用 {n, m} 精确匹配 n~m 个字符。

比如:String reg = "A\d{3,5}";可以匹配如下字符串:

  • A380:因为\d{3,5}可以匹配3个数字380
  • A3800:因为\d{3,5}可以匹配4个数字3800
  • A38000:因为\d{3,5}可以匹配5个数字38000

如果不设置上限,那么修饰符 {n,} 可以匹配至少 n 个字符。

Java正则表达式_第1张图片

Java正则表达式_第2张图片 2. 复杂匹配规则

2.1 匹配 开头 和 结尾

用正则表达式进行多行匹配时,我们用 表示开头,用 表示结尾。

例如,String reg = "^A\d{3}$",可以匹配"A001""A380"

2.2 匹配指定范围

使用 [......] 可以匹配范围内的字符。比如 [123456789] 可以匹配到 1~9 。

比如: String reg = "[123456789]\d{6,7}";可以匹配到不以0开头的一个7~8位的电话号码。

[......] 还有一种简便的写法,如:[1-9]。

比如:String reg = "[0-9a-fA-F]";可以匹配到字符串 "1A2b3c"。

比如:String reg = "[0-9a-fA-F]{6}";可以匹配到一个6位的 十六进制数。

[......] 还有一种排除法,即不包含指定范围的字符。

比如我们要匹配任意字符,但不包括数字,可以用 String reg = "[^1-9]{3}" 来进行匹配:

  • 可以匹配"ABC",因为不包含字符1~9
  • 可以匹配"A00",因为不包含字符1~9
  • 不能匹配"A01",因为包含字符1
  • 不能匹配"A05",因为包含字符5

2.3 或 | 规则匹配

|连接的两个正则表达式是 或 的规则。

比如:String reg = "AB|CD";可以匹配到 AB 、CD。

2.4 使用括号

现在我们想要匹配字符串learn javalearn phplearn go怎么办?一个最简单的规则是learn\sjava|learn\sphp|learn\sgo,但是这个规则太复杂了,可以把公共部分提出来,然后用(...)把子规则括起来表示成learn\\s(java|php|go)

Java正则表达式_第3张图片

 3. 分组匹配

前面我们讲到 (......) 可以用来把一个子规则括起来,这样写 learn\s(java|php|go) 就可以更方便的匹配长字符串了。

实际上 (......) 还有一个重要作用,就是分组匹配。

比如:我们想要使用正则来匹配一个 区号-电话号,我们可以用 String reg = "\d{3, 4}\-\d{6, 8}";这个正则很简单,但是在实际开发中,往往需要在匹配成功之后,还要进行下一步,也就是对提取的 区号 和 电话号码,进行分别存储。那么问题来了,我们如何提取匹配的子串呢?·

正确的方法是,用(...)先把要提取的规则分组,把上述正则表达式变为:

(\d{3,4})\-(\d{6,8})

那么匹配之后,我们如何按括号分组,来提取子串呢?

我们没办法用String.matches()这样简单的判断方法了,而是必须引入java.util.regex包,用Pattern对象匹配,匹配后获得一个Matcher对象,如果匹配成功,就可以直接从Matcher.group(index)返回子串:

public class Main {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
        Matcher m = p.matcher("010-12345678");
        if (m.matches()) {
            String g1 = m.group(1);
            String g2 = m.group(2);
            System.out.println(g1);
            System.out.println(g2);
        } else {
            System.out.println("匹配失败!");
        }
    }
}

//输出结果
010
12345678

注意!Matcher.group(index)方法的参数用1表示第一个子串,2表示第二个子串。如果我们传入0则是整个正则匹配到的字符串。

Pattern

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

其实反复使用String.matches()对同一个正则表达式进行多次匹配效率较低,因为每次都会创建出一样的Pattern对象。而我们完全可以先创建出一个Pattern对象,然后反复使用,就可以实现编译一次,多次匹配了:

import java.util.regex.*;

public class Main {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
        pattern.matcher("010-12345678").matches(); // true
        pattern.matcher("021-123456").matches(); // false
        pattern.matcher("022#1234567").matches(); // false
        // 获得Matcher对象:
        Matcher matcher = pattern.matcher("010-12345678");
        if (matcher.matches()) {
            String whole = matcher.group(0); // "010-12345678", 0表示匹配的整个字符串
            String area = matcher.group(1); // "010", 1表示匹配的第1个子串
            String tel = matcher.group(2); // "12345678", 2表示匹配的第2个子串
            System.out.println(area);
            System.out.println(tel);
        }
    }
}

注意: 使用Matcher时,必须首先调用matches()判断是否匹配成功,匹配成功后,才能调用group()提取子串。

4. 非贪婪匹配

在了解非贪婪匹配前,我们先来一起看一个简单的问题:

给定一个字符串表示的数字,判断该数字末尾0的个数。例如:

  • "123000":3个0
  • "10100":2个0
  • "1001":0个0

可以很容易地写出该正则表达式:(\d+)(0*),Java代码如下:

import java.util.regex.*;

public class Main {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("(\\d+)(0*)");
        Matcher matcher = pattern.matcher("1230000");
        if (matcher.matches()) {
            System.out.println("group1=" + matcher.group(1)); // "1230000"
            System.out.println("group2=" + matcher.group(2)); // ""
        }
    }
}

//输出结果
打印的第二个子串是个空字符串 “”。

而我们期望的匹配结果是:

Java正则表达式_第4张图片

但实际的分组匹配结果是这样的:

 其实通过观察,这个结果是合理的。因为 \d+ 确实可以匹配后面的任意个0。这是因为正则表达式默认使用的是 贪婪匹配的模式即:任何一个规则,它总是尽可能多地向后匹配,因此,\d+总是会把后面的0包含进来。

而要让 \d+ 尽量的少匹配后面的字符,让 0* 尽量的多匹配,我们就必须让 \d+ 使用贪婪匹配模式。在规则 \d+ 后面加一个 ? ,就可以表示非贪婪匹配了。

5. 搜索和替换

5.1 分割字符串

使用正则表达式,分割字符串,可以实现更加灵活的功能。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" }

如果我们想让用户输入一组标签,然后把标签提取出来,因为用户的输入往往是不规范的,这时,使用合适的正则表达式,就可以消除多个空格、混合,;这些不规范的输入,直接提取出规范的字符串。

5.2 搜索字符串

使用正则表达式,可以搜索字符串,我们看代码实现:

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);
        }
    }
}

//输出结果
row
fox
dog

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

5.3 字符串替换

使用正则表达式替换字符串可以直接调用String.replaceAll(),它的第一个参数是正则表达式,第二个参数是待替换的字符串。我们看下代码实现:

public class Main {
    public static void main(String[] args) {
        String s = "The     quick\t\t brown   fox  jumps   over the  lazy dog.";
        String r = s.replaceAll("\\s+", " ");
        System.out.println(r); // "The quick brown fox jumps over the lazy dog."
    }
}

//输出结果
The quick brown fox jumps over the lazy dog.

5.4 反向引用

如果我们把从字符串中搜索到的指定内容,按一定规则进行替换。

比如: 在匹配到的内容前后加一个xxxx,这个时候,使用replaceAll()的时候,我们传入的第二个参数可以使用$1$2来反向引用匹配到的子串:

public class Main {
    public static void main(String[] args) {
        String s = "the quick brown fox jumps over the lazy dog.";
        String r = s.replaceAll("\\s([a-z]{4})\\s", " $1 ");
        System.out.println(r);
    }
}

实现替换的关键就在于" $1 ",它用匹配的分组子串([a-z]{4})替换了$1 

你可能感兴趣的:(Android,Java,正则表达式,java,前端)