字符串文本匹配神器———Java正则表达式

Posted 活跃的咸鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字符串文本匹配神器———Java正则表达式相关的知识,希望对你有一定的参考价值。

什么是正则表达式?

正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去匹配符合规则的字符。

为什么要学正则表达式

对于正则表达式,相信很多人都知道,但是很多人的第一感觉就是难学,因为看第一眼时,觉得完全没有规律可寻,而且全是一堆各种各样的特殊符号,完全不知所云。

其实只是对正则不了解而以,了解了你就会发现,原来就这样啊因为正则所用的相关字符其实不多,也不难记,更不难懂,唯一难的就是组合起来之后,可读性比较差,不容易理解。但是你只要肯花个几个小时的时间去学习并练习一下你就学会了。

正则表达式虽然内容不多但是功能强大,对于匹配文本或字符串那是相当的高效。下面我们来看下面一段百度百科对java语言的描述。

Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。
Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点  。
Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。

如果我让你找出上述字符串中的所有的java和C++和Web,你会怎么做?传统方式可能会想到循环遍历通过判断字母的ASCII码值来输出。但是这样效率不高,因为如果让你查询大量的文本这将非常的慢。下面我们用java正则表达式来处理一下。

public class demo01 {
    public static void main(String[] args) {
        //要匹配的字符串
        String content="Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念," +
                "因此Java语言具有功能强大和简单易用两个特征。\\n" +
                "Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。\\n" +
                "Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点  。\\n" +
                "Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。";
        //匹配模式
        String regExp="[a-zA-Z+]+";
        //得到一个模式对象
        Pattern pattern = Pattern.compile(regExp);
        //创建匹配器
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到了:"+matcher.group(0));
        }
    }
}

输出:
找到了:Java
找到了:C++
找到了:C++
找到了:Java
找到了:Java
找到了:Java
找到了:Java
找到了:Web

我们通过正则表达式很容易的完成了我们的需求。
我们再来看几个需求
在这里插入图片描述
在这里插入图片描述
大家看完如果不知道正则表达式是不是没有什么思路没关系只要看完这篇文章你就会觉得不难实现了。

正则表达式底层实现

在讲正则表达式语法之前我们先通过debug来看看上诉java代码他们的底层是怎么运行的如何实现的?当我们把他的底层搞清楚之后我们再去学习正则表达式的语法和使用那将会如鱼得水。

matcher.find()方法分析

大家可能会觉得find方法返回类型一个是一个字符串但是这里的find方法返回的是一个Boolean值。
该方法在这里的作用是

  1. 根据指定的匹配规则找到对应的字符串,比如上面那段字符串,会找到第一个满足条件的字符串(java)。
  2. 找到后会将该子字符串开始的索引储存到matcher对象的一个group数组中group[0]=0,最后一个索引值+1存储到group[4]中
    在这里插入图片描述
    3.同时记录一个LastOld属性的值为上一个子字符串最后一个索引值+1的值,其目的是为了让下一次查找从该索引处继续查找。

matcher.group(0)分析
源码如下:

 public String group(int group) {
        if (first < 0)
            throw new IllegalStateException("No match found");
        if (group < 0 || group > groupCount())
            throw new IndexOutOfBoundsException("No group " + group);
        if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
            return null;
        return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
    }

group方法按照上面源码

getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();

得到 groups[0]=0 和 groups[1]=4 的记录的位置,从 content 开始截取子字符串返回[0,4)处的字符串也就是1998.

如果再次指向 find 方法.仍然安上面分析来执行找到下一个字符串C++的位置 在21和24然后将C++处索引的位置记录到group数组中为groups[0]=21 和 groups[1]=24。
在这里插入图片描述

然后group方法通过group索引的位置截取到对应的值。

分组情况的分析

既然可以通过group[0]来获取值那么能否通过group[1],group[2]…来获取值呢?答案是肯定可以的,那就要涉及到分组的概念,什么是分组?
我们来看下面一个例子。

public class demo02 {
    public static void main(String[] args) {
        String content="1243java 4567 c7890 python 1223";
        //找到4个连续的数字
        String regExp="(\\\\d\\\\d)(\\\\d\\\\d)";
        Pattern pattern = Pattern.compile(regExp);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()){
            System.out.println("找到了所有符合的值:"+matcher.group(0));
            System.out.println("找到了第一组的值:"+matcher.group(1));
            System.out.println("找到了第二组的值:"+matcher.group(2));
        }
    }
}

找到了所有符合的值:1243
找到了第一组的值:12
找到了第二组的值:43
找到了所有符合的值:4567
找到了第一组的值:45
找到了第二组的值:67
找到了所有符合的值:7890
找到了第一组的值:78
找到了第二组的值:90
找到了所有符合的值:1223
找到了第一组的值:12
找到了第二组的值:23

什么是分组,比如 (\\d\\d)(\\d\\d) ,正则表达式中有() 表示分组,第 1 个()表示第 1 组,第 2 个()表示第 2 组…

  1. 根据指定的规则 ,定位满足规则的子字符串(比如(12)(43))
  2. 找到后,将 子字符串的开始的索引记录到 matcher 对象的属性 int[] groups;
  • 2.1 groups[0] = 0 , 把该子字符串的结束的索引+1 的值记录到 groups[1] = 4
  • 2.2 记录 第1 组()匹配到的字符串12的索引存储到 groups[2] = 0 groups[3] = 2
  • 2.3 记录第 2 组()匹配到的字符串43的索引存储到 groups[4] = 2 groups[5] = 4
  • 2.4.如果有更多的分组… 以此类推
    在这里插入图片描述

总结如下

  1. 如果正则表达式有() 即分组
  2. 取出匹配的字符串规则如下
  3. group(0) 表示匹配到的子字符串
  4. group(1) 表示匹配到的子字符串的第一组字串
  5. group(2) 表示匹配到的子字符串的第 2 组字串
  6. … 但是分组的数不能越界.group(3)则会报异常

java.util.regex 包主要包括以下三个类:

Pattern 类:

pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。

常用方法

返回类型方法名和描述
Stringpattern() 返回编译此模式的正则表达式。
static Patterncompile(String regex) 将给定的正则表达式编译为模式。
static Patterncompile(String regex, int flags) 将给定的正则表达式编译为带有给定标志的模式。
Matchermatcher(CharSequence input) 创建一个匹配器,匹配给定的输入与此模式。
static booleanmatches(String regex, CharSequence input) 编译给定的正则表达式,并尝试匹配给定的输入。
String[]split(CharSequence input) 将给定的输入序列分成这个模式的匹配。
String[]split(CharSequence input, int limit) 将给定的输入序列分成这个模式的匹配。

Matcher 类:

Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。

常用方法

返回类型方法名和描述
intstart(int group) 返回给定组在上一个匹配操作期间捕获的子序列的开始索引。
intstart(String name) 返回给定捕获的子序列的初始索引 named-capturing group以前的匹配操作期间。
intend() 返回最后一个字符匹配后的偏移量。
intgroupCount() 返回此匹配器模式中捕获组的数量。
intend(int group) 返回在上次匹配操作期间由给定组捕获的子序列的最后一个字符之后的偏移量。
booleanfind() 尝试找到匹配模式的输入序列的下一个子序列。
booleanmatches() 尝试将整个区域与模式进行匹配。
Stringgroup() 返回与上一个匹配匹配的输入子序列。
Stringgroup(int group) 返回在上一次匹配操作期间由给定组捕获的输入子序列。
Stringgroup(String name) 返回给定捕获的输入子序列 named-capturing group以前的匹配操作期间。
StringreplaceAll(String replacement) 将与模式匹配的输入序列的每个子序列替换为给定的替换字符串。
StringreplaceFirst(String replacement) 将与模式匹配的输入序列的第一个子序列替换为给定的替换字符串。

PatternSyntaxException类:

PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

正则表达式语法

普通字符

普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。

字符描述
[abc]匹配 [abc] 中的所有字符 abcdefg 匹配到abc
[^abc]匹配除了 [abc] 中字符的所有字符 abcdefg 匹配到defg
[A-Z][A-Z] 表示一个区间,匹配所有大写字母,[a-z] 表示所有小写字母。
[\\w]匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
[\\s\\S]\\s 是匹配所有空白符,包括换行,\\S 非空白符,不包括换行。
.匹配除换行符(\\n、\\r)之外的任何单个字符,相等于 [^\\n\\r]。
[0-9]匹配0-9中的任意一个数字

非打印字符

非打印字符也可以是正则表达式的组成部分。下表列出了表示非打印字符的转义序列:

字符描述
\\cx匹配由x指明的控制字符。例如, \\cM 匹配一个 Control-M 或回车符。x 的值必须为A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。
\\f匹配一个换页符。等价于 \\x0c 和 \\cL。
\\n匹配一个换行符。等价于 \\x0a 和 \\cJ。
\\r匹配一个回车符。等价于 \\x0d 和 \\cM。
\\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \\f\\n\\r\\t\\v]。注意Unicode 正则表达式会匹配全角空格符。
\\S匹配任何非空白字符。等价于 [^ \\f\\n\\r\\t\\v]。
\\t匹配一个制表符。等价于 \\x09 和 \\cI。
\\v匹配一个垂直制表符。等价于 \\x0b 和 \\cK。

定位符

字符描述
^匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \\n 或 \\r 之后的位置匹配。
$匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \\n 或 \\r 之前的位置匹配。
\\b匹配一个单词边界,即字与空格间的位置。
\\B非单词边界匹配。

特殊字符

字符描述
$匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\\n’ 或 ‘\\r’。要匹配 $ 字符本身,请使用 \\$。
()标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用\\ ( 和 \\)。
*匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \\*。
+匹配前面的子表达式一次或多次或非贪婪匹配。要匹配 + 字符,请使用 \\+。
?匹配前面的子表达式0次或1次。要匹配 ? 字符,请使用 \\?。
.匹配除换行符 \\n 之外的任何单字符。要匹配 . ,请使用 \\. 。
[标记一个中括号表达式的开始。要匹配 [,请使用 \\[。
\\将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\\n’ 匹配换行符。序列 ‘\\’ 匹配 “”,而 ‘(’ 则匹配 “(”。
^匹配输入字符串的开始位置,除非在方括号表达式中使用,当该符号在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合。要匹配 ^ 字符本身,请使用 \\^。
{标记限定符表达式的开始。要匹配 {,请使用\\ {。
|指明两项之间的一个选择。要匹配|请使用 \\’|’

修饰符

修饰符含义描述
iignore - 不区分大小写将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。
gglobal - 全局匹配 查找所有的匹配项。
mmulti line - 多行匹配使边界字符 ^$ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。
s特殊字符圆点 . 中包含换行符 \\n默认情况下的圆点 . 是 匹配除换行符 \\n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \\n。

限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。

正则表达式的限定符有:

字符描述
*匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
?匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。
{n}n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,}n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
{n,m}m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。

元字符

字符描述
\\

将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'\\n' 匹配一个换行符。序列 '\\\\' 匹配 "\\" 而 "\\(" 则匹配 "("。

(pattern)

匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '\\(' 或 '\\)'。

(?:pattern)

匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。

(?=pattern)

正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?!pattern)

正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?<=pattern)反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。
(?<!pattern)反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。
x|y

匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。

[xyz]

字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。

\\num

匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\\1' 匹配两个连续的相同字符。

\\n

标识一个八进制转义值或一个向后引用。如果 \\n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。

\\nm

标识一个八进制转义值或一个向后引用。如果 \\nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \\nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \\nm 将匹配八进制转义值 nm。

\\nml

如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。

\\un

匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \\u00A9 匹配版权符号 (?)。

常用正则表达式

  • 匹配中文字符的正则表达式: [\\u4e00-\\u9fa5]

  • 匹配双字节字符(包括汉字在内):[^\\x00-\\xff]

  • 匹配空行的正则表达式:\\n[\\s| ]*\\r

  • 匹配html标记的正则表达式:/<(.*)>.*<\\/\\1>|<(.*) \\/>/

  • 匹配首尾空格的正则表达式:(^\\s*)|(\\s*$)

  • 匹配IP地址的正则表达式:/(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)/g //

  • 匹配Email地址的正则表达式:\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*

  • 匹配网址URL的正则表达式:http://(/[\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?

  • sql语句:^(select|drop|delete|create|update|insert).*$

  • 非负整数:^\\d+$

  • 正整数:^[0-9]*[1-9][0-9]*$

  • 非正整数:^((-\\d+)|(0+))$

  • 负整数:^-[0-9]*[1-9][0-9]*$

  • 整数:^-?\\d+$

  • 非负浮点数:^\\d+(\\.\\d+)?$

  • 正浮点数:^((0-9)+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*))$

  • 非正浮点数:^((-\\d+\\.\\d+)?)|(0+(\\.0+)?))$

  • 负浮点数:^(-((正浮点数正则式)))$

  • 英文字符串:^[A-Za-z]+$

  • 英文大写串:^[A-Z]+$

  • 英文小写串:^[a-z]+$

  • 英文字符数字串:^[A-Za-z0-9]+$

  • 英数字加下划线串:^\\w+$

  • E-mail地址:^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$

  • URL:^[a-zA-Z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\s*)?$ 或者:^http:\\/\\/[A-Za-z0-9]+\\.[A-Za-z0-9]+[\\/=\\?%\\-&_~@[\\]\\':+!]*([^<>\\"\\"])*$

  • 邮政编码:^[1-9]\\d{5}$

  • 中文:^[\\u0391-\\uFFE5]+$

  • 电话号码:^((\\(\\d{2,3}\\))|(\\d{3}\\-))?(\\(0\\d{2,3}\\)|0\\d{2,3}-)?[1-9]\\d{6,7}(\\-\\d{1,4})?$

  • 手机号码:^((\\(\\d{2,3}\\))|(\\d{3}\\-))?13\\d{9}$

  • 双字节字符(包括汉字在内):^\\x00-\\xff

  • 匹配首尾空格:(^\\s*)|(\\s*$)(像vbscript那样的trim函数)

  • 匹配HTML标记:<(.*)>.*<\\/\\1>|<(.*) \\/>

  • 匹配空行:\\n[\\s| ]*\\r

  • 提取信息中的网络链接:(h|H)(r|R)(e|E)(f|F) *= *('|")?(\\w|\\\\|\\/|\\.)+('|"| *|>)?

  • 提取信息中的邮件地址:\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*

  • 提取信息中的图片链接:(s|S)(r|R)(c|C) *= *('|")?(\\w|\\\\|\\/|\\.)+('|"| *|>)?

  • 提取信息中的IP地址:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)

  • 提取信息中的中国手机号码:(86)*0*13\\d{9}

  • 提取信息中的中国固定电话号码:(\\(\\d{3,4}\\)|\\d{3,4}-|\\s)?\\d{8}

  • 提取信息中的中国电话号码(包括移动和固定电话):(\\(\\d{3,4}\\)|\\d{3,4}-|\\s)?\\d{7,14}

  • 提取信息中的中国邮政编码:[1-9]{1}(\\d+){5}

  • 提取信息中的浮点数(即小数):(-?\\d*)\\.?\\d+

  • 提取信息中的任何数字 :(-?\\d*)(\\.\\d+)?

  • IP地址:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)

  • 电话区号:/^0\\d{2,3}$/

  • 腾讯QQ号:^[1-9]*[1-9][0-9]*$

  • 帐号(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

  • 中文、英文、数字及下划线:^[\\u4e00-\\u9fa5_a-zA-Z0-9]+$

以上是关于字符串文本匹配神器———Java正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

python 正则表达式

python基础学习(十三)

python基础学习笔记(十三)

java正则表达式

如何测试文本片段是不是是 Quoted-printable 编码的

Java学习之正则表达式