6.5 正则表达式

Posted weststar

tags:

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

正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等操作。String类也提供了几个特殊的方法:
(1)boolean matches(String regex):判断该字符串是否匹配指定的正则表达式
(2)String replaceAll(String regex,String replacement):将该字符串匹配的regex的子串替换成replacement
(3)String replaceFirst(String regex,String replacement):将该字符串第一个匹配的regex的子串替换成replacement
(4)String[] split(String regex):以regex作为分割符,将字符串分割为多个字串
上面的这些特殊方法都依赖于Java提供的正在表达式支持,除此之外,Java还提供了Pattern和Matcher两个类专门用于提供正则表达式支持。

一、创建正则表达式

1、正则表达式支持的合法字符

正则表达式就是一个用于匹配字符串的模板,可以匹配一批字符串,所以创建正则表达式就是创建一个特殊的字符串。

字符 解释
x 字符x(x代表任何合法字符)
mnn 八进制0mnn所表示的字符
xhh 十六进制0xhh所表示的字符
uhhhh 十六进制0xhhhh所表示的Unicode字符
制表符(‘u0009‘)
新行(换行)符‘u000A‘
回车符(‘u000D‘)
f 换页符(‘u000C‘)
a 报警符(‘u0007‘)
e Escape符(‘u001B‘)
cx x对应的控制符。例如:cM匹配Ctr-M.x必须是a-z或A-Z

2、特殊用途字符

除此之外,正则表达式还有一些特殊字符,如果需要匹配这些特殊字符,就必须先将这些字符转义,也就是在前面加一个反斜线()

特殊字符 说明
^ 匹配输入字符串的开始位置。要匹配 "^" 字符本身,请使用 "^"
$ 匹配输入字符串的结尾位置。要匹配$字符本身,请使用 "$"
() 标记一个子表达式的开始和结束位置。要匹配小括号,请使用"反斜线+左圆括号"和"反斜线+右圆括号"
[] 用于确定中括号表达式的开始和结束位置。要匹配中括号,请使用"反斜线+左方括号"和"反斜线+右方括号"
{} 用于标记前面子表式出现的频度。要匹配大括号,请使用 "{" 和 "}"
. 匹配除了换行符( )以外的任意一个字符。要匹配小数点本身,请使用 "."
? 指定前面子表达式可以出现零次或一次。要匹配 "?" 字符本身,请使用 "?"
+ 指定前面子表达式可以出现一次或多次。要匹配 "+" 字符本身,请使用 "+"
* 指定前面子表达式可以出现零次或多次。要匹配*字符本身,请使用 "*"
| 指定两项之间任选一项。匹配 "|" 本身,请使用|
用于转义下一个字符或指定八进制、十六进制字符。如果需要匹配字符请用‘\‘

上面多个字符拼接起来,可以创建一个正则表达式,例如:

"u0041\\"——匹配A"u0061	"——匹配a<制表符>
"\?\["——匹配?[

3、预定义字符

”通配符“是可以匹配多个字符的特殊字符。正则表达式中”通配符“远远超出了普通通配符的功能,它被称为预定义字符。

预定义字符 说明
. 可以匹配任何字符
d 匹配0-9的所有数字
D 匹配非数字
s 匹配所有的空白字符,包括空格、制表符、回车符、换行符
S 匹配所有非空白字符
w 匹配所有单词字符,包括0-9,26个英文字符和下划线
W 匹配所有非单词字符

记忆:.是省略的意思,可以匹配任何字符;d——digital数字;s——space空白;w——word单词(数字,字母、下划线)。大写代表匹配恰好相反的字符。
举例:

c\wt//可以匹配catcbtcctc0tc_t等一批字符
\d\d\d-\d\d\d-\d\d\d\d  匹配000-000-0000形式的电话号码

4、方括号表达式

在一些特殊情况,例如只想匹配a~f的字母,或匹配除ab之外的所有小写字母,或者匹配中文字符,此时就需要方括号表达式。

方括号表达式 说明
表示枚举 例如[abc],表示abc任何一个字符
表示范围- 例如[a-f]表示a-f任何一个字符;[u0041-u0056]表示十六进制符u0041-u0056范围的字符。范围和枚举结合使用,[a-cx-z]表示a-c、x-z范围内的字符
表示求否^ 例如[^abc],表示非abc的任意字符;[^a-f]表示非a~f范围内的任意字符
表示"与"运算:&& [a-z&&[def]],求a-z与[def]之间的交集,表示d、e、f
表示"并"运算 并运算与前面的枚举类似,例如[a-d[m-p]]表示[a-dm-p]

5、正则表达式的圆括号表达式

圆括号表达式用于将多个表达式组成一个子表达式,圆括号中可以使用或运算符(|)。例如((public)|(private)|(protected))

6、边界匹配符

边界匹配 含义
^ 一行的开始
$ 一行的结束
 单词的边界
B 非单词的边界
A 输入的开头
G 前一个匹配的结尾
 输入的结尾,仅用于最后的结束符
z 输入的结尾

7、数量表达式

前面匹配一个000-000-0000的电话号码时,使用了//d//d//d-//d//d//d-//d//d//d//d的正则表达式,这种看起来比较繁琐。实际上正则表达式提供的数量标识符可以很好地解决这种问题。数量表达式有三种模式:
(1)Greedy(贪婪)模式:表示符默认采用贪婪模式,除非另有表示。贪婪模式会一直匹配下去,直至无法匹配为止。
(2)Reluctant(勉强)模式:用后缀?表示,它只会匹配最少的字符。也称为最小匹配模式
(3)Possessive(占用)模式:用加号(+)后缀表示,目前Java支持占用模式,通常比较少用。

贪婪模式 勉强模式 占用模式 说明
X? X?? X?+ X表达式出现零次或一次
X* X*? X*+ X表达式出现零次或多次
X+ X+? X++ X表达式出现一次或多次
X{n} X{n}? X{n}+ X表达式出现n次
X{n,} X{n,}? X{n,}+ X表达式最少出现n次
X{n,m} X{n,m}? X{n,m}+ X表达式最少出现n次,最多出现m次

关于贪婪模式与勉强模式的对比:

String str="hello ,java!";
System.out.println(str.replaceFirst("w*","D"));//输出D ,java!
System.out.println(str.replaceFirst("w*?","D"));//输出Dhello ,java

二、使用正则表达式

一旦程序中定义了正则表达式,就可以直接利用Pattern和Matcher来使用正则表达式
Pattern对象是正则表达式编译后在内存中的表示形式,因此正则表达式字符串必须鲜卑编译为Pattern对象,然后再利用Pattern对象创建对应的Matcher对象。执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可共享一个Pattern对象。
典型的调用顺序:

//将一个字符串编译成Pattern对象
Pattern p=Pattern.compile("a*b");
//使用Pattern对象创建Matcher对象
Matcher m=p.matcher("aaaab");
boolean b=m.matches();//返回true

上面定义的Pattern对象可以多次重复使用,如果某个正则表达式仅需使用一次,可以直接使用Pattern类的静态matches()方法,此方法自动把指定字符串编译成匿名的Pattern对象,并执行匹配:

boolean b=Pattern.matches("a*b","aaaaab");//输出true

上面语句等效为前面的三条语句。但这种语句每次都需要重新编译新的Pattern对象,不能重复利用已编译的Pattern对象,所以效率不高。
Pattern是不可变类,可供多个并发线程安全使用。
Matcher类提供以下几个常用方法:
(1)find():返回字符串中是否包含与Pattern匹配的子串。
(2)group():返回上一次与Pattern匹配的子串。
(3)start():返回上一次与Pattern匹配的子串在目标字符串中开始的位置。
(4)end():返回上一次与Pattern匹配的子串在目标字符串中的结束位置+1.
(5)lookingAt():返回目标字符串前面部分与Pattern是否匹配。
(6)matches():返回整个目标字符串与Pattern是否匹配。
(7)reset():将现有的Matcher对象应用于一个新的字符串序列。
注意:在Pattern、Matcher类中常常会看到一个CharSequence接口,该接口代表一个字符序列,其CharBuffer、String、StringBuffer、StringBuilder都是它的实现类。简单来说,CharSequence代表一个各种表示形式的字符串。

2.1 find()和group()方法

通过Matcher类的find()、group()方法可以从目标字符串中依次取出指定的子串(匹配正则表达式的子串),例如互联网的网络爬虫,它们可以自动从网页中识别出所以电话号码。下面程序中示范了如何从大段字符串中找出电话号码:

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class  FindGroup
{
    public static void main(String[] args) 
    {
        //使用字符串模拟从网络上得到网页的源码
        var str = "我想求购一本《疯狂Java讲义》,尽快联系我13500006666"
            + "交朋友,电话号码是13611125565"
            + "出售二手电脑,联系方式15899903312";
        // 创建一个Pattern对象,并用它建立一个Matcher对象
        // 该正则表达式只抓取13X和15X段的手机号,
        // 实际要抓取哪些电话号码,只要修改正则表达式即可。
        Pattern p=Pattern.compile("((13\d)|(15\d))\d{8}");
        Matcher m=p.matcher(str);
        //将所有符合正则表达式的子串全部输出
        while(m.find())
        {
            System.out.println(m.group());
        }
    }
}
---------- 运行Java捕获输出窗 ----------
13500006666
13611125565
15899903312

输出完成 (耗时 0 秒) - 正常终止

从上面的运行结果来看,find()方法依次查找字符串中与Pattern匹配的子串,一旦找到了对应的子串,下次调用find()方法将接着往下找。
find()方法还可以传入一个int类型的参数,带int类型参数的find()方法将从该int索引处向下搜索。

2.2 star()和end()方法

用于确定子串在目标字符中的位置,如下程序所示:

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class StartEnd
{
    public static void main(String[] args)
    {
        //创建一个Pattern对象,并用它建立它的Matcher对象
        var regStr="Java is very easy!";
        System.out.println("目标字符串是:"+regStr);
        Matcher m=Pattern.compile("\w+").matcher(regStr);
        while(m.find())
        {
            System.out.println(m.group()+"子串起始位置:"+m.start()+",其结束位置:"+m.end());
        }
    }
}
---------- 运行Java捕获输出窗 ----------
目标字符串是:Java is very easy!
Java子串起始位置:0,其结束位置:4
is子串起始位置:5,其结束位置:7
very子串起始位置:8,其结束位置:12
easy子串起始位置:13,其结束位置:17

输出完成 (耗时 0 秒) - 正常终止

上面程序使用find()和group()方法逐项取出目标字符串与指定正则表达式匹配的子串,并使用start()、end()方法返回子串在目标字符串中的位置。

2.3 matches()和lookingAt()方法以及reset()方法

1、lookingAt():返回目标字符串前面部分与Pattern是否匹配。
2、matches():返回整个目标字符串与Pattern是否匹配。
区别在于matches()方法要求整个字符串和Pattern对象完全匹配时才返回true,而lookingAt()只要字符串以Pattern开头就返回true
3、reset():将现有的Matcher对象应用于一个新的字符串序列。

import java.util.regex.*;
public class MatchesTest
{
    public static void main(String[] args)
    {

        String[] mails =
        {
            "kongyeeku@163.com",
            "kongyeeku@gmail.com",
            "ligang@crazyit.org",
            "wawa@abc.xx"
        };
        var mailRegEx = "\w{3,20}@\w+\.(com|org|cn|net|gov)";
        var p=Pattern.compile(mailRegEx);
        Matcher matcher=null;
        for(var mail:mails)
        {
            if(matcher==null)
            {
                matcher=p.matcher(mail);
            }
            else
            {
                matcher.reset(mail);
            }
            String result=mail+(matcher.matches()?"是":"不是")+
                "一个有效的邮件地址!";
            System.out.println(result);
        }
    }
}
---------- 运行Java捕获输出窗 ----------
kongyeeku@163.com是一个有效的邮件地址!
kongyeeku@gmail.com是一个有效的邮件地址!
ligang@crazyit.org是一个有效的邮件地址!
wawa@abc.xx不是一个有效的邮件地址!

输出完成 (耗时 0 秒) - 正常终止

从上面创建了一个邮箱地址的Pattern,接着用这个Pattern与多个邮件地址进行匹配。当程序的Mathcer为null时,程序调用Parttern对象的matcher()方法来创建一个Matcher对象,一旦Matcher对象被创建,程序就调用Matcher对象的reset()方法将该Matcher应用于新的字符序列。
从某个角度来说,Matcher类的matches()、lookingAt()和String类的equals()、startsWith()有点类似,区别在于String类的equals()、startsWith()都是与字符串进行比较,而Matcher类的matches()、lookingAt()都是于正则表达式进行匹配。
事实上,String类也提供了matches()方法,该方法返回该字符串是否匹配指定的正则表达式。例如:
"kongyeeku@163.com".matches(\w{3,20}@\w+\.(com|org|cn|net|gov));//返回true

2.4 正则表达式的对目标字符串的分割、查找、替换等操作

import java.util.regex.*;
public class ReplaceTest 
{
    public static void main(String[] args) 
    {
        String[] msgs =
        {
            "Java has regular expressions in 1.4",
            "regular expressions now expressing in Java",
            "Java represses oracular expressions"
        };
        var p=Pattern.compile("re\w*");
        Matcher matcher=null;
        for(var i=0;i<msgs.length;i++)
        {
            if(matcher==null)
                matcher=p.matcher(msgs[i]);
            else
                matcher.reset(msgs[i]);
            System.out.println(matcher.replaceAll("哈哈:)"));
        }
    }
}
---------- 运行Java捕获输出窗 ----------
Java has 哈哈:) exp哈哈:) in 1.4
哈哈:) exp哈哈:) now exp哈哈:) in Java
Java 哈哈:) oracular exp哈哈:)

输出完成 (耗时 0 秒) - 正常终止

String类也提供了replaceAll()、replaceFirst()、split()方法。下面程序示范直接使用String类提供的正则表达式的功能来进行替换和分割。


import java.util.*;
public class StringReg
{
    public static void main(String[] args)
    {
        String[] msgs =
        {
            "Java has regular expressions in 1.4",
            "regular expressions now expressing in Java",
            "Java represses oracular expressions"
        };
        for (var msg : msgs)
        {
            System.out.println(msg.replaceFirst("re\w*", "哈哈:)"));
            System.out.println(Arrays.toString(msg.split(" ")));
        }
    }
}
---------- 运行Java捕获输出窗 ----------
Java has 哈哈:) expressions in 1.4
[Java, has, regular, expressions, in, 1.4]
哈哈:) expressions now expressing in Java
[regular, expressions, now, expressing, in, Java]
Java 哈哈:) oracular expressions
[Java, represses, oracular, expressions]

输出完成 (耗时 0 秒) - 正常终止

以上是关于6.5 正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

text 正则表达式片段

markdown 正则表达式模式片段

正则表达式匹配特定的 URL 片段而不是所有其他 URL 可能性

循环通过 python 正则表达式匹配

asp.net 使用正则表达式验证包含打开/关闭括号片段的属性字符串

攻破难啃的骨头-正则表达式(转)