正则表达式入门

每种语言对正则的支持略有不一样, 这里咱们主要说的是Java对正则表达式的支持.html

什么是正则表达式

正则表达式(Regular Expression), 常简写为regex, 是定义搜索模式的一组字符串 (用一组字符描述了一个字符串规则). 一般用于字符串查找和字符串的校验.java

小试牛刀

判断手机号字符串的正则表达式.
笼统讲手机号的特色: 以1开头, 后面是10位数字. 因此咱们写一个简单判断是否知足这个条件的正则表达式:正则表达式

^1\d{10}$

其中, ^表明起始位置, $表明结尾位置. \d 表明是一个数字字符, 后面大括号里面的数字, 表明它前面的元素会出现10次.express

咱们能够简单用一行代码验证下:api

System.out.println("13436417560".matches("^1\\d{10}$"));

注, 因 Java 代码里面反斜杠起转义做用, 例如 \t 表明 tab, 因此在 Java 代码里, 两个反斜杠才能表示出一个反斜杠.oracle

正则表达式中经常使用字符

正则表达式中的转义

正则表达式经过反斜杠来进行转义. 例如:测试

点字符.表明任意字符, 而\.表明真正的小数点.
\d 表明一个数字字符, 而 \\d 表明一个反斜杠和一个字符d.
\\表明一个反斜杠字符.fetch

经常使用字符

经常使用字符 意义
x 普通字符 x
^ 表示字符串起始位置
$ 表示字符串结尾位置
\\ 反斜杠字符
\t tab字符
\n 换行字符
\r 回车字符
. 表明任意字符(默认不会匹配\r和\n, 须要配置才匹配)
[abc] 方括号表示其中的任意字符. 方括号中任意字符(多是a 或 b 或 c)
[^abc] 不在方括号中的任意字符
[a-zA-Z] 任意字母, 包括大写和小写
\d 任意数字 [0-9]
\D 任意非数字 [^0-9]
\s 任意空字符 [ \t\n\x0B\f\r]
\S 任意非空字符 [^\s]
\w 任意组成单词字符 [a-zA-Z_0-9]
\W 任意非组成单子字符 [^\w]
x? 表明x字符不存在或者存在1次(最多存在1次)
x* 表明x字符不存在或者存在任意次
x+ 表明x字符至少存在1次
x{5} x字符存在5次
x{3,5} x字符存在3到5次

正则表达式应用场景简介

字符串查找

正则表达式查找能够解决普通查找只能根据"特定文本"查找的问题.atom

示例:.net

假设如今有一堆 JSON 日志, 咱们须要查出 cabinX 开头的日志(X后面只可能跟数字): 假设日志以下:

..."from":"PEK", "cabin":"Y"...
..."from":"SHA", "cabin":"X"...
..."from":"XIY", "cabin":"X2"...
..."from":"XIY", "cabin":"Y2"...

咱们提取的正则表达式是:

cabin":"X\d*"

字符串校验

使用正则表达式能够校验文本是否符合必定规范. 例如一开始提到的手机号格式校验. 还有邮箱格式校验等. 也能够对身份证格式进行简单的校验.

字符串提取

咱们可使用正则表达式将文本中的一部分提取出来. 主要用到了正则表达式中的 capturing group 概念.

capturing group

正则表达式中可使用小括号将表达式分红多个捕获组. 分组之后, 能够经过捕获组号, 取出对应匹配的内容. 经过数左半括号便可得到组号. 例如:

((A)(B(C)))

上面正则表达式对应的捕获组信息以下: 捕获组号 | 对应内容 ---|--- 1 | ((A)(B(C))) 2 | (A) 3 | (B(C)) 4 | (C)

第0组老是表明整个正则表达式.

示例代码以下:

Pattern pattern = Pattern.compile("((A)(B(C)))");
Matcher matcher = pattern.matcher("XXXABCDEF");
if (matcher.find()) {
    for (int i = 0; i <= matcher.groupCount(); i++) {
        System.out.println("group " + i + " : " + matcher.group(i));
    }
}

输出为:

group 0 : ABC
group 1 : ABC
group 2 : A
group 3 : BC
group 4 : C

其余应用示例:

从大量日志中, 获取天天的车次号, 并计数.

named-capturing group

在Java中, Java7之后, 能够为 capturing-group 命名. 取的时候能够根据名字来取. 命名示例以下:

(?<Name>[Pp]attern)

经过在普通 capturing-group 的最前面, 添加 ?<xxx> 来指定名字. 使用示例以下:

public static String fetchOneNamedGroup(String src, String regex, String groupName) {
    Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
    Matcher matcher = pattern.matcher(src);
    if (matcher.find()) {
        return matcher.group(groupName);
    }
    return null;
}

public static void main(String[] args) throws Exception {
    String sourceType = fetchOneNamedGroup("sourceType:PC,name:test", "sourceType:(?<sourceType>\\w+)",
            "sourceType");
    System.out.println(sourceType);
}
// 结果是"PC"

注意, capturing-group 的名字必须知足条件: [A-Za-z][A-Za-z0-9]*

贪婪匹配 vs 最小匹配

咱们要从Hello World中获取H和l以及之间的字符 Hel. 因而, 咱们写了以下正则表达式:

H.*l

可是结果却并非咱们想要的:

Hello Worl

仔细分析发现, Hello Worl 这个结果也一样知足咱们的正则表达式.

如今问题在于咱们能够认为 Hel 是咱们要的结果(最小匹配), 也能够认为 Hello Worl 是第一个H和最后一个l之间的字符(贪婪匹配).

正则表达式默认状况下是贪婪匹配模式. 想要最小匹配, 只须要在贪婪匹配模式符后面加一个?, 便可转化为最小匹配.

例如:

咱们使用H.*?l的结果就是最小匹配:

Pattern pattern = Pattern.compile("H.*?l");
Matcher matcher = pattern.matcher("Hello World");
if (matcher.find()) {
    System.out.println(matcher.group(0));
}
// 结果: Hel

Java里面的正则表达式

Java里面经过 java.util.regex.Pattern 实现了对正则的支持.

Java里面的正则和grep命令里面的就略有不一样, 例如在Java里面, 星号 * 表明0个或多个, 而在 grep 里面, * 表明任意内容.

.* 多行匹配

默认状况下, . 在Java不会匹配换行符. 也就是 .* 只能匹配一行. 若是须要经过 .* 匹配多行状况, 能够开启 DOTALL mode.

示例:

public static String fetchGroup1(String src, String regex) {
    Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
    Matcher matcher = pattern.matcher(src);
    if (matcher.find()) {
        return matcher.group(1);
    }
    return null;
}

String 中正则表达式相关方法

String 中有几个直接正则表达式的方法, 使用方便, 同时能够用于测试.

boolean matches (String regex)
String replaceAll (String regex, String replacement)
String replaceFirst (String regex, String replacement)
String[] split (String regex)
String[] split (String regex, int limit)

注意, replace, replaceFirst 和 replaceAll 的区别.

痛点

Java字符串经过反斜杠来标示特殊字符, 而正则表达式一样经过反斜杠来专业, 致使代码中的正则表达式可读性极差.

正则表达式中的特殊结构体(不经常使用)

(?:pattern) 非获取匹配

匹配pattern但不获取匹配结果, 也就是说这是一个非获取匹配. 尽管匹配, 将此group匹配结果不保存, 不做为最终结果返回.

示例:

https://stackoverflow.com/questions/tagged/regex
正则表达式: (https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?
匹配结果:
Match "https://stackoverflow.com/questions/tagged/regex"
     Group 1: "https"
     Group 2: "stackoverflow.com"
     Group 3: "/questions/tagged/regex"
若是并不在乎用的什么协议, 可是 http/https/ftp 协议是使用括号括起来并用了|链接符. 若是想把这个group的结果给舍弃了, 则经过费获取匹配:
(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?
结果是:
Match "https://stackoverflow.com/questions/tagged/regex"
     Group 1: "stackoverflow.com"
     Group 2: "/questions/tagged/regex"

参考: https://stackoverflow.com/questions/3512471/what-is-a-non-capturing-group-what-does-do

(?=pattern) (?!pattern) (?<=pattern) (?<!pattern) 预查匹配
  • (?=pattern) 正向确定预查, 判断当前匹配后面是否有 pattern 所述字符串.
  • (?!pattern) 正向否认预查, 判断当前匹配后面是否不包含 pattern 所述字符串.
  • (?<=pattern) 反向确定预查, 判断当前匹配前面是否有 pattern 所述字符串.
  • (?<!pattern) 反向否认预查, 判断当前匹配前面是否不包含 pattern 所述字符串.

预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配以后当即开始下一次匹配的搜索,而不是从包含预查的字符以后开始。

参考:

bar(?=bar)     finds the 1st bar ("bar" which has "bar" after it)
bar(?!bar)     finds the 2nd bar ("bar" which does not have "bar" after it)
(?<=foo)bar    finds the 1st bar ("bar" which has "foo" before it)
(?<!foo)bar    finds the 2nd bar ("bar" which does not have "foo" before it)

https://stackoverflow.com/questions/2973436/regex-lookahead-lookbehind-and-atomic-groups

使用经验

  • 尽可能少些长正则, 由于难以维护
  • 在写稍长的正则表达式时, 能够分段写, 写一段测一段
  • 若是正则很是复杂, 并且麻烦, 就要考虑是不是正则适合的场景, 须要考虑使用其余方式来实现.

特殊示例

System.out.println("www".replaceAll("a?", "替换"));

// 结果是: 替换w替换w替换w替换

参考

https://docs.oracle.com/javase/9/docs/api/java/util/regex/Pattern.html#sum

https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines

https://www.cnblogs.com/exmyth/p/7774918.html

https://blog.csdn.net/qq_19865749/article/details/77478489

相关文章
相关标签/搜索