死磕Java正则表达式

前言

正则表达式是一种强大而灵活的文本处理工具。通过正则表达式,我们能够以编程的方式,构建复杂的文本模式,对输入的字符串进行搜索。一旦找到匹配的部分,我们就能随心所欲的对它们进行处理。

初学正则表达式时,它的语法是对初学者的一门考验,只有琢磨透了其真正的内涵,我们才能从本质上看到它确实是一种简洁、动态的语言。

万事开头难嘛

正则表达式不像我们平常看到的那么简单,它,真的很变态。通过复杂的文本模式而处理我们遇到的所有问题,这本身就很不可思议。首先看个简单的栗子,看看它神奇的魅力:

public class Main {
    public static void main(String[] args) {
        System.out.println("-1267".matches("-?\\d+"));
        System.out.println("1267".matches("-?\\d+"));
        System.out.println("+1267".matches("-?\\d+"));
        System.out.println("1267".matches("(-|\\+)?\\d+"));
    }
}

输出结果如下:

true
true
false
true

我们首先看到matches()方法里的表达式一脸懵逼吧,很正常,因为我第一次看到时也是。究竟这些表示什么含义呢?

首先这里要先普及一个小知识点,在其他语言中,\\表示我想要在正则表达式中插入一个普通的反斜杠,请不要给它任何特殊的意义,而在Java中,\\表示我要插入一个正则表达式的反斜杠,所以其后的字符具有特殊的意义。也就是说,在Java里,只有\\才表示转义,不同于其他语言\就能代表转义。

现在回到上面的这个栗子里来,-?\\d+表示的含义是可能有一个负号,后面紧跟着一个或多个数字,这听起来不就描述的是-1267了吗。后面三个输出含义同上,不同的是,最后一个使用到了|,它的含义是可能有一个负号或正好,后面紧跟着一个或多个数字。需要注意的是由于+在正则表达式中有特殊的含义,因此这里需要对其进行转义。

正则表达式语法

除了上面出现过的正则表达式以外,还有大量的语法:

字符说明
^一行的开始
$一行的结束
.匹配除“\r\n”以外的任意单个字符
+一次或多次匹配前面的字符或子表达式
*零次或多次匹配前面的字符或子表达式
零次或一次匹配前面的字符或子表达式
{n}恰好n次匹配前面的字符或子表达式
{n,}至少n次匹配前面的字符或子表达式
{n,m}至少n次,至多m次匹配前面的字符或子表达式
[abc]包含a、b、c中的任何字符
[^abc]除了a、b、c以外的任何字符
[a-zA-Z]从a到z或A到Z的任何字符
[abc[hij]]包含a、b、c、h、i、j中的任何字符
\s空白符(空格、tab、换行、换页、回车)
\S非空白符
\d数字[0-9]
\D非数字[0-9]
\w词字符[a-zA-Z]
\W非词字符[a-zA-Z]
XYY跟在X后面

这里并没有包含所有的正则表达式,若想查看所有的表达式可查看JDK文档中java.util.regex.Pattren包。

我们学习正则表达式并不是为了编写出最难理解的正则表达式,而是尽量编写能够完成任务的、最简单以及最必要的正则表达式。一旦真正开始使用正则表达式了,你就会发现,在编写新的表达式之前,你通常会参考代码中已经用到的正则表达式(引用自Thinking in Java)。

Pattern 和 Matcher

一般来说,比起功能有限的String类,我们更愿意构造功能强大的正则表达式对象。只需要导入java.util.regex包,然后用static Pattern.compile(String)方法来编译正则表达式即可。它会根据编译传入的参数(String类型的正则表达式)生成一个Pattern对象。接着把需要检索的字符串传入Pattern对象的matcher()方法。该方法会生成一个Matcher对象,该对象有许多方法可用。

代码解析:

public class Main{

    private static String str = "abcdabcefghabchijklabcmn";

    private static Pattern getPattern(String regex){
        return Pattern.compile(regex);
    }

    public static void main(String[] args) {
        Pattern pattern = getPattern("abc");
        Matcher matcher = pattern.matcher(str);
        while (matcher.find()){
            System.out.println("Match " + matcher.group() + " at positions " + matcher.start() + "-" + (matcher.end()-1));
        }
        
        //matches()要求整个序列都匹配
        System.out.println("matches(): " + matcher.matches());

        //matches()要求整个序列都匹配
        System.out.println("lookingAt(): " + matcher.lookingAt());
    }
}

输出结果如下:

Match abc at positions 0-2
Match abc at positions 4-6
Match abc at positions 11-13
Match abc at positions 19-21
matches(): false
lookingAt(): true

从这里栗子中我们可以看出当调用getPattern(String)传入我们的正则表达式作为参数,会返回一个Pattern对象,通过调用该对象的matcher(String)方法并传入我们需要匹配的字符串后,从而构造了一个Matcher对象。

上面我们用到了Matcher对象的find()group()start()end()matches()lookingAt()等方法。其中find()方法可用于在代码中的str变量中查找多个匹配,该方法就像迭代器那样前向遍历该str字符串。若调用该方法传入一个int类型参数,如find(int),表示从该参数位置作为搜索的起点开始匹配。其中group()返回整个匹配的值。

matches()方法用来判断整个输入字符串是否匹配正则表达式模式,而lookingAt()则用来判断该字符串从开始部分能否匹配正则表达式模式。

组是用括号划分的正则表达式。组号为0表示整个表达式,组号1表示被第一对括号括起来的组,依次类推。
如正则表达式A(B(C))D。其中有三个组:组0是ABCD,组1是BC,组2是C

我们可以通过group(int)获得与组相关的信息,group()返回的是第0组,因此从上面代码中可以看出第0组输出的就是我们的正则表达式。

替换操作

正则表达式特别便于替换文本。它也提供了许多方法:

  1. replaceFirst(String, String)以第一个参数字符串替换掉第一个匹配成功的部分为第二个参数。
  2. replaceAll(String, String)以第一个参数字符串替换所有匹配成功的部分为第二个参数。
  3. appendReplacement(StringBuffer, String)执行渐进式的替换,将被匹配的字符串从头一直到匹配处的字符存入的StringBuffer变量中,该方法只会处理到最后一个匹配的字符串。通过该方法允许我们在使用的过程中调用其他方法来生成我们最终想要的处理结果。
  4. appendTail(StringBuffer)可用于在执行了一次或多次appendReplacement(StringBuffer, String)方法后,调用此方法可将输入字符串剩下的部分复制到StringBuffer中。

还是需要栗子才能懂得更彻底点:

public class Main{
    private static String str = "hello, everyone. my name is zhang haiyang, i am from southwest petroleum university.";

    private static Pattern getPattern(String regex){
        return Pattern.compile(regex);
    }

    public static void main(String[] args) {
        System.out.println(str.replaceFirst(", {1}","!!"));

        System.out.println(str.replaceAll(", {1}","!!"));

        Matcher matcher = getPattern("m").matcher(str);
        StringBuffer stringBuffer = new StringBuffer();
        while (matcher.find()){
            matcher.appendReplacement(stringBuffer, matcher.group().toUpperCase());
        }
        matcher.appendTail(stringBuffer);
        System.out.println("stringBuffer: " + stringBuffer);
    }
}

输出结果显示:

hello!!everyone. my name is zhang haiyang, i am from southwest petroleum university.
hello!!everyone. my name is zhang haiyang!!i am from southwest petroleum university.
stringBuffer: hello, everyone. My naMe is zhang haiyang, i aM froM southwest petroleuM university.

其中我们使用appendReplacement()方法替换了字符串str中的所有小写字母m,并将它转换成大写后写入stringBuffer中。

最后

正则表达式能够为我们构建复杂的文本模式解决大量问题,但是有时候使用复杂的正则表达式返回会降低我们程序的性能,这里我只是使用了简单的正则表达式来说明其中方法的使用。在过去,Java对于字符串操作的支持相当不完善,不过随着近几个版本的升级,我们可以看到,Java已经从其他语言中吸取了许多成熟的经验,现在,它对字符串操作的支持已经很完善了。

更多文章请关注我的个人博客:www.zhyocean.cn

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值