十八、字符串(3)

本章概要

  • 正则表达式
    • 基础
    • 创建正则表达式
    • 量词
    • CharSequence
    • Pattern 和 Matcher
    • finde()
    • 组(Groups)
    • start() 和 end()
    • Pattern 标记
    • split()
    • 替换操作
    • reset()
    • 正则表达式与 Java I/0

正则表达式

很久之前,_正则表达式_就已经整合到标准 Unix 工具集之中,例如 sed、awk 和程序语言之中了,如 Python 和Perl(有些人认为正是正则表达式促成了 Perl 的成功)。而在 Java 中,字符串操作还主要集中于StringStringBufferStringTokenizer 类。与正则表达式相比较,它们只能提供相当简单的功能。

正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能够以编程的方式,构造复杂的文本模式,并对输入 String 进行搜索。一旦找到了匹配这些模式的部分,你就能随心所欲地对它们进行处理。初学正则表达式时,其语法是一个难点,但它确实是一种简洁、动态的语言。正则表达式提供了一种完全通用的方式,能够解决各种 String 处理相关的问题:匹配、选择、编辑以及验证。

基础

一般来说,正则表达式就是以某种方式来描述字符串,因此你可以说:“如果一个字符串含有这些东西,那么它就是我正在找的东西。”例如,要找一个数字,它可能有一个负号在最前面,那你就写一个负号加上一个问号,就像这样:

-?

要描述一个整数,你可以说它有一位或多位阿拉伯数字。在正则表达式中,用 \d 表示一位数字。如果在其他语言中使用过正则表达式,那你可能就能发现 Java 对反斜线 \ 的不同处理方式。在其他语言中,\\ 表示“我想要在正则表达式中插入一个普通的(字面上的)反斜线,请不要给它任何特殊的意义。”而在Java中,\\ 的意思是“我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。”例如,如果你想表示一位数字,那么正则表达式应该是 \\d。如果你想插入一个普通的反斜线,应该这样写 \\\。不过换行符和制表符之类的东西只需要使用单反斜线:\n\t

要表示“一个或多个之前的表达式”,应该使用 +。所以,如果要表示“可能有一个负号,后面跟着一位或多位数字”,可以这样:

-?\\d+

应用正则表达式最简单的途径,就是利用 String 类内建的功能。例如,你可以检查一个 String 是否匹配如上所述的正则表达式:

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

在这里插入图片描述

前两个字符串都满足对应的正则表达式,匹配成功。第三个字符串以 + 开头,这也是一个合法的符号,但与对应的正则表达式却不匹配。因此,我们的正则表达式应该描述为:“可能以一个加号或减号开头”。在正则表达式中,用括号将表达式进行分组,用竖线 | 表示或操作。也就是:

(-|\\+)?

这个正则表达式表示字符串的起始字符可能是一个 -+,或者二者都没有(因为后面跟着 ? 修饰符)。因为字符 + 在正则表达式中有特殊的意义,所以必须使用 \\ 将其转义,使之成为表达式中的一个普通字符。

String类还自带了一个非常有用的正则表达式工具——split() 方法,其功能是“将字符串从正则表达式匹配的地方切开。”

import java.util.Arrays;

public class Splitting {
    public static String knights =
            "Then, when you have found the shrubbery, " +
                    "you must cut down the mightiest tree in the " +
                    "forest...with... a herring!";

    public static void split(String regex) {
        System.out.println(Arrays.toString(knights.split(regex)));
    }

    public static void main(String[] args) {
        split(" "); // Doesn't have to contain regex chars
        split("\\W+"); // Non-word characters
        split("n\\W+"); // 'n' followed by non-words
    }
}

在这里插入图片描述

首先看第一个语句,注意这里用的是普通的字符作为正则表达式,其中并不包含任何特殊字符。因此第一个 split() 只是按空格来划分字符串。

第二个和第三个 split() 都用到了 \\W,它的意思是一个非单词字符(如果 W 小写,\\w,则表示一个单词字符)。通过第二个例子可以看到,它将标点字符删除了。第三个 split() 表示“字母 n 后面跟着一个或多个非单词字符。”可以看到,在原始字符串中,与正则表达式匹配的部分,在最终结果中都不存在了。

String.split() 还有一个重载的版本,它允许你限制字符串分割的次数。

用正则表达式进行替换操作时,你可以只替换第一处匹配,也可以替换所有的匹配:

public class Replacing {
    static String s = Splitting.knights;

    public static void main(String[] args) {
        System.out.println(
                s.replaceFirst("f\\w+", "located"));
        System.out.println(
                s.replaceAll("shrubbery|tree|herring", "banana"));
    }
}

在这里插入图片描述

第一个表达式要匹配的是,以字母 f 开头,后面跟一个或多个字母(注意这里的 w 是小写的)。并且只替换掉第一个匹配的部分,所以 “found” 被替换成 “located”。

第二个表达式要匹配的是三个单词中的任意一个,因为它们以竖线分割表示“或”,并且替换所有匹配的部分。

稍后你会看到,String 之外的正则表达式还有更强大的替换工具,例如,可以通过方法调用执行替换。而且,如果正则表达式不是只使用一次的话,非 String 对象的正则表达式明显具备更佳的性能。

创建正则表达式

我们首先从正则表达式可能存在的构造集中选取一个很有用的子集,以此开始学习正则表达式。正则表达式的完整构造子列表,请参考JDK文档 java.util.regex 包中的 Pattern类。

表达式含义
B指定字符B
\xhh十六进制值为0xhh的字符
\uhhhh十六进制表现为0xhhhh的Unicode字符
\t制表符Tab
\n换行符
\r回车
\f换页
\e转义(Escape)

当你学会了使用字符类(character classes)之后,正则表达式的威力才能真正显现出来。以下是一些创建字符类的典型方式,以及一些预定义的类:

表达式含义
.任意字符
[abc]包含abc的任何字符(和`a
[^abc]abc之外的任何字符(否定)
[a-zA-Z]az或从AZ的任何字符(范围)
[abc[hij]]abchij中的任意字符(与`a
[a-z&&[hij]]任意hij(交)
\s空白符(空格、tab、换行、换页、回车)
\S非空白符([^\s]
\d数字([0-9]
\D非数字([^0-9]
\w词字符([a-zA-Z_0-9]
\W非词字符([^\w]

这里只列出了部分常用的表达式,你应该将JDK文档中 java.util.regex.Pattern 那一页加入浏览器书签中,以便在需要的时候方便查询。

逻辑操作符含义
XYY跟在X后面
XYXY
(X)捕获组(capturing group)。可以在表达式中用\i引用第i个捕获组

下面是不同的边界匹配符:

边界匹配符含义
^一行的开始
$一行的结束
\b词的边界
\B非词的边界
\G前一个匹配的结束

作为演示,下面的每一个正则表达式都能成功匹配字符序列“Rudolph”:

public class Rudolph {
    public static void main(String[] args) {
        for (String pattern : new String[]{
                "Rudolph",
                "[rR]udolph",
                "[rR][aeiou][a-z]ol.*",
                "R.*"}) {
            System.out.println("Rudolph".matches(pattern));
        }
    }
}

在这里插入图片描述

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

量词

量词描述了一个模式捕获输入文本的方式:

  • 贪婪型
    量词总是贪婪的,除非有其他的选项被设置。贪婪表达式会为所有可能的模式发现尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。
  • 勉强型
    用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也被称作懒惰的、最少匹配的、非贪婪的或不贪婪的。
  • 占有型
    目前,这种类型的量词只有在 Java 语言中才可用(在其他语言中不可用),并且也更高级,因此我们大概不会立刻用到它。当正则表达式被应用于 String 时,它会产生相当多的状态,以便在匹配失败时可以回溯。而“占有的”量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更高效。
贪婪型勉强型占有型如何匹配
X?X??X?+一个或零个X
X*X*?X*+零个或多个X
X+X+?X++一个或多个X
X{n}X{n}?X{n}+恰好nX
X{n,}X{n,}?X{n,}+至少nX
X{n,m}X{n,m}?X{n,m}+X至少n次,但不超过m

应该非常清楚地意识到,表达式 X 通常必须要用圆括号括起来,以便它能够按照我们期望的效果去执行。例如:

abc+

看起来它似乎应该匹配1个或多个abc序列,如果我们把它应用于输入字符串abcabcabc,则实际上会获得3个匹配。然而,这个表达式实际上表示的是:匹配ab,后面跟随1个或多个c。要表明匹配1个或多个完整的字符串abc,我们必须这样表示:

(abc)+

你会发现,在使用正则表达式时很容易混淆,因为它是一种在 Java 之上的新语言。

CharSequence

接口 CharSequenceCharBufferStringStringBufferStringBuilder 类中抽象出了字符序列的一般化定义:

interface CharSequence {
    char charAt(int i);   
    int length();
    CharSequence subSequence(int start, int end);
    @Override
    String toString(); 
}

因此,这些类都实现了该接口。多数正则表达式操作都接受 CharSequence 类型参数。

PatternMatcher

通常,比起功能有限的 String 类,我们更愿意构造功能强大的正则表达式对象。只需导入 java.util.regex包,然后用 static Pattern.compile() 方法来编译你的正则表达式即可。它会根据你的 String 类型的正则表达式生成一个 Pattern 对象。接下来,把你想要检索的字符串传入 Pattern 对象的 matcher() 方法。matcher() 方法会生成一个 Matcher 对象,它有很多功能可用(可以参考 java.util.regext.Matcher 的 JDK 文档)。例如,它的 replaceAll() 方法能将所有匹配的部分都替换成你传入的参数。

作为第一个示例,下面的类可以用来测试正则表达式,看看它们能否匹配一个输入字符串。第一个控制台参数是将要用来搜索匹配的输入字符串,后面的一个或多个参数都是正则表达式,它们将被用来在输入的第一个字符串中查找匹配。在Unix/Linux上,命令行中的正则表达式必须用引号括起来。这个程序在测试正则表达式时很有用,特别是当你想验证它们是否具备你所期待的匹配功能的时候。

import java.util.regex.*;

public class TestRegularExpression {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println(
                    "Usage:\njava TestRegularExpression " +
                            "characterSequence regularExpression+");
            System.exit(0);
        }
        System.out.println("Input: \"" + args[0] + "\"");
        for (String arg : args) {
            System.out.println(
                    "Regular expression: \"" + arg + "\"");
            Pattern p = Pattern.compile(arg);
            Matcher m = p.matcher(args[0]);
            while (m.find()) {
                System.out.println(
                        "Match \"" + m.group() + "\" at positions " +
                                m.start() + "-" + (m.end() - 1));
            }
        }
    }
}

在这里插入图片描述

还可以在控制台参数中加入“(abc){2,}”,看看执行结果。

Pattern 对象表示编译后的正则表达式。从这个例子可以看到,我们使用已编译的 Pattern 对象上的 matcher() 方法,加上一个输入字符串,从而共同构造了一个 Matcher 对象。同时,Pattern 类还提供了一个static方法:

static boolean matches(String regex, CharSequence input)

该方法用以检查 regex 是否匹配整个 CharSequence 类型的 input 参数。编译后的 Pattern 对象还提供了 split() 方法,它从匹配了 regex 的地方分割输入字符串,返回分割后的子字符串 String 数组。

通过调用 Pattern.matcher() 方法,并传入一个字符串参数,我们得到了一个 Matcher 对象。使用 Matcher 上的方法,我们将能够判断各种不同类型的匹配是否成功:

boolean matches() 
boolean lookingAt() 
boolean find() 
boolean find(int start)

其中的 matches() 方法用来判断整个输入字符串是否匹配正则表达式模式,而 lookingAt() 则用来判断该字符串(不必是整个字符串)的起始部分是否能够匹配模式。

find()

Matcher.find() 方法可用来在 CharSequence 中查找多个匹配。例如:

import java.util.regex.*;

public class Finding {
    public static void main(String[] args) {
        Matcher m = Pattern.compile("\\w+")
                .matcher(
                        "Evening is full of the linnet's wings");
        while (m.find()) {
            System.out.print(m.group() + " ");
        }
        System.out.println();
        int i = 0;
        while (m.find(i)) {
            System.out.print(m.group() + " ");
            i++;
        }
    }
}

在这里插入图片描述

模式 \\w+ 将字符串划分为词。find() 方法像迭代器那样向前遍历输入字符串。而第二个重载的 find() 接收一个整型参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果可以看出,后一个版本的 find() 方法能够根据其参数的值,不断重新设定搜索的起始位置。

组(Groups)

组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为 0 表示整个表达式,组号 1 表示被第一对括号括起来的组,以此类推。因此,下面这个表达式,

A(B(C))D

中有三个组:组 0 是 ABCD,组 1 是 BC,组 2 是 C

Matcher 对象提供了一系列方法,用以获取与组相关的信息:

  • public int groupCount() 返回该匹配器的模式中的分组数目,组 0 不包括在内。
  • public String group() 返回前一次匹配操作(例如 find())的第 0 组(整个匹配)。
  • public String group(int i) 返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则将返回 null
  • public int start(int group) 返回在前一次匹配操作中寻找到的组的起始索引。
  • public int end(int group) 返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。

下面是正则表达式组的例子:

import java.util.regex.*;

public class Groups {
    public static final String POEM =
            "Twas brillig, and the slithy toves\n" +
                    "Did gyre and gimble in the wabe.\n" +
                    "All mimsy were the borogoves,\n" +
                    "And the mome raths outgrabe.\n\n" +
                    "Beware the Jabberwock, my son,\n" +
                    "The jaws that bite, the claws that catch.\n" +
                    "Beware the Jubjub bird, and shun\n" +
                    "The frumious Bandersnatch.";

    public static void main(String[] args) {
        Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$")
                .matcher(POEM);
        while (m.find()) {
            for (int j = 0; j <= m.groupCount(); j++) {
                System.out.print("[" + m.group(j) + "]");
            }
            System.out.println();
        }
    }
}

在这里插入图片描述

这首诗来自于 Lewis Carroll 所写的 Through the Looking Glass 中的 “Jabberwocky”。可以看到这个正则表达式模式有许多圆括号分组,由任意数目的非空白符(\\S+)及随后的任意数目的空白符(\\s+)所组成。目的是捕获每行的最后3个词,每行最后以 \$ 结束。不过,在正常情况下是将 \$ 与整个输入序列的末端相匹配。所以我们一定要显式地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记 (?m) 来完成(模式标记马上就会介绍)。

start()end()

在匹配操作成功之后,start() 返回先前匹配的起始位置的索引,而 end() 返回所匹配的最后字符的索引加一的值。匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用 start()end() 将会产生 IllegalStateException。下面的示例还同时展示了 matches()lookingAt() 的用法 :

import java.util.regex.*;

public class StartEnd {
    public static String input =
            "As long as there is injustice, whenever a\n" +
                    "Targathian baby cries out, wherever a distress\n" +
                    "signal sounds among the stars " +
                    "... We'll be there.\n" +
                    "This fine ship, and this fine crew ...\n" +
                    "Never give up! Never surrender!";

    private static class Display {
        private boolean regexPrinted = false;
        private String regex;

        Display(String regex) {
            this.regex = regex;
        }

        void display(String message) {
            if (!regexPrinted) {
                System.out.println(regex);
                regexPrinted = true;
            }
            System.out.println(message);
        }
    }

    static void examine(String s, String regex) {
        Display d = new Display(regex);
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(s);
        while (m.find()) {
            d.display("find() '" + m.group() +
                    "' start = " + m.start() + " end = " + m.end());
        }
        if (m.lookingAt()) // No reset() necessary
        {
            d.display("lookingAt() start = "
                    + m.start() + " end = " + m.end());
        }
        if (m.matches()) // No reset() necessary
        {
            d.display("matches() start = "
                    + m.start() + " end = " + m.end());
        }
    }

    public static void main(String[] args) {
        for (String in : input.split("\n")) {
            System.out.println("input : " + in);
            for (String regex : new String[]{"\\w*ere\\w*",
                    "\\w*ever", "T\\w+", "Never.*?!"}) {
                examine(in, regex);
            }
        }
    }
}

在这里插入图片描述

注意,find() 可以在输入的任意位置定位正则表达式,而 lookingAt()matches() 只有在正则表达式与输入的最开始处就开始匹配时才会成功。matches() 只有在整个输入都匹配正则表达式时才会成功,而 lookingAt() 只要输入的第一部分匹配就会成功。

Pattern 标记

Pattern 类的 compile() 方法还有另一个版本,它接受一个标记参数,以调整匹配行为:

Pattern Pattern.compile(String regex, int flag)

其中的 flag 来自以下 Pattern 类中的常量

编译标记效果
Pattern.CANON_EQ当且仅当两个字符的完全规范分解相匹配时,才认为它们是匹配的。例如,如果我们指定这个标记,表达式\u003F就会匹配字符串?。默认情况下,匹配不考虑规范的等价性
Pattern.CASE_INSENSITIVE(?i)默认情况下,大小写不敏感的匹配假定只有US-ASCII字符集中的字符才能进行。这个标记允许模式匹配不考虑大小写(大写或小写)。通过指定UNICODE_CASE标记及结合此标记。基于Unicode的大小写不敏感的匹配就可以开启了
Pattern.COMMENTS(?x)在这种模式下,空格符将被忽略掉,并且以#开始直到行末的注释也会被忽略掉。通过嵌入的标记表达式也可以开启Unix的行模式
Pattern.DOTALL(?s)在dotall模式下,表达式.匹配所有字符,包括行终止符。默认情况下,.不会匹配行终止符
Pattern.MULTILINE(?m)在多行模式下,表达式^$分别匹配一行的开始和结束。^还匹配输入字符串的开始,而$还匹配输入字符串的结尾。默认情况下,这些表达式仅匹配输入的完整字符串的开始和结束
Pattern.UNICODE_CASE(?u)当指定这个标记,并且开启CASE_INSENSITIVE时,大小写不敏感的匹配将按照与Unicode标准相一致的方式进行。默认情况下,大小写不敏感的匹配假定只能在US-ASCII字符集中的字符才能进行
Pattern.UNIX_LINES(?d)在这种模式下,在.^$的行为中,只识别行终止符\n

在这些标记中,Pattern.CASE_INSENSITIVEPattern.MULTILINE 以及 Pattern.COMMENTS(对声明或文档有用)特别有用。请注意,你可以直接在正则表达式中使用其中的大多数标记,只需要将上表中括号括起来的字符插入到正则表达式中,你希望它起作用的位置即可。

你还可以通过“或”(|)操作符组合多个标记的功能:

import java.util.regex.*;

public class ReFlags {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("^java",
                Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
        Matcher m = p.matcher(
                "java has regex\nJava has regex\n" +
                        "JAVA has pretty good regular expressions\n" +
                        "Regular expressions are in Java");
        while (m.find()) {
            System.out.println(m.group());
        }
    }
}

在这里插入图片描述

在这个例子中,我们创建了一个模式,它将匹配所有以“java”、“Java”和“JAVA”等开头的行,并且是在设置了多行标记的状态下,对每一行(从字符序列的第一个字符开始,至每一个行终止符)都进行匹配。注意,group() 方法只返回已匹配的部分。

split()

split()方法将输入 String 断开成 String 对象数组,断开边界由正则表达式确定:

String[] split(CharSequence input) 
String[] split(CharSequence input, int limit)

这是一个快速而方便的方法,可以按照通用边界断开输入文本:

import java.util.regex.*;
import java.util.*;

public class SplitDemo {
    public static void main(String[] args) {
        String input = "This!!unusual use!!of exclamation!!points";
        System.out.println(Arrays.toString(Pattern.compile("!!").split(input)));
        // Only do the first three:     
        System.out.println(Arrays.toString(Pattern.compile("!!").split(input, 3)));
    }
}

在这里插入图片描述

第二种形式的 split() 方法可以限制将输入分割成字符串的数量。

替换操作

正则表达式在进行文本替换时特别方便,它提供了许多方法:

  • replaceFirst(String replacement) 以参数字符串 replacement 替换掉第一个匹配成功的部分。
  • replaceAll(String replacement) 以参数字符串 replacement 替换所有匹配成功的部分。
  • appendReplacement(StringBuffer sbuf, String replacement) 执行渐进式的替换,而不是像 replaceFirst()replaceAll() 那样只替换第一个匹配或全部匹配。这是一个非常重要的方法。它允许你调用其他方法来生成或处理 replacementreplaceFirst()replaceAll() 则只能使用一个固定的字符串),使你能够以编程的方式将目标分割成组,从而具备更强大的替换功能。
  • appendTail(StringBuffer sbuf) 在执行了一次或多次 appendReplacement() 之后,调用此方法可以将输入字符串余下的部分复制到 sbuf 中。

下面的程序演示了如何使用这些替换方法。开头部分注释掉的文本,就是正则表达式要处理的输入字符串:

import java.util.regex.*;
import java.nio.file.*;
import java.util.stream.*;

/*! Here's a block of text to use as input to 
    the regular expression matcher. Note that we 
    first extract the block of text by looking for 
    the special delimiters, then process the     
    extracted block. !*/

public class TheReplacements {
    public static void main(String[] args) throws Exception {
        String s = Files.lines(
                        Paths.get("D:\\onJava\\test\\src\\main\\java\\com\\example\\test\\TheReplacements.java"))
                .collect(Collectors.joining("\n"));
        // Match specially commented block of text above:     
        Matcher mInput = Pattern.compile(
                "/\\*!(.*)!\\*/", Pattern.DOTALL).matcher(s);
        if (mInput.find()) {
            s = mInput.group(1); // Captured by parentheses
        }
        // Replace two or more spaces with a single space:     
        s = s.replaceAll(" {2,}", " ");
        // Replace 1+ spaces at the beginning of each     
        // line with no spaces. Must enable MULTILINE mode:     
        s = s.replaceAll("(?m)^ +", "");
        System.out.println(s);
        s = s.replaceFirst("[aeiou]", "(VOWEL1)");
        StringBuffer sbuf = new StringBuffer();
        Pattern p = Pattern.compile("[aeiou]");
        Matcher m = p.matcher(s);
        // Process the find information as you     
        // perform the replacements:     
        while (m.find()) {
            m.appendReplacement(sbuf, m.group().toUpperCase());
        }
        // Put in the remainder of the text:     
        m.appendTail(sbuf);
        System.out.println(sbuf);
    }
}

在这里插入图片描述

此处使用上一章介绍过的 Files 类打开并读入文件。Files.lines() 返回一个 Stream 对象,包含读入的所有行,Collectors.joining() 在每一行的结尾追加参数字符序列,最终拼接成一个 String 对象。

mInput 匹配 /*!!*/ 之间的所有文字(注意分组的括号)。接下来,将存在两个或两个以上空格的地方,缩减为一个空格,并且删除每行开头部分的所有空格(为了使每一行都达到这个效果,而不仅仅是删除文本开头部分的空格,这里特意开启了多行模式)。

这两个替换操作所使用的的 replaceAll()String 对象自带的方法,在这里,使用此方法更方便。注意,因为这两个替换操作都只使用了一次 replaceAll(),所以,与其编译为 Pattern,不如直接使用 StringreplaceAll() 方法,而且开销也更小些。

replaceFirst() 只对找到的第一个匹配进行替换。此外,replaceFirst()replaceAll() 方法用来替换的只是普通字符串,所以,如果想对这些替换字符串进行某些特殊处理,这两个方法时无法胜任的。如果你想要那么做,就应该使用 appendReplacement() 方法。该方法允许你在执行替换的过程中,操作用来替换的字符串。在这个例子中,先构造了 sbuf 用来保存最终结果,然后用 group() 选择一个组,并对其进行处理,将正则表达式找到的元音字母替换成大些字母。

一般情况下,你应该遍历执行所有的替换操作,然后再调用 appendTail() 方法,但是,如果你想模拟 replaceFirst()(或替换n次)的行为,那就只需要执行一次替换,然后调用 appendTail() 方法,将剩余未处理的部分存入 sbuf 即可。

同时,appendReplacement() 方法还允许你通过 \$g 直接找到匹配的某个组,这里的 g 就是组号。然而,它只能应付一些简单的处理,无法实现类似前面这个例子中的功能。

reset()

通过 reset() 方法,可以将现有的 Matcher 对象应用于一个新的字符序列:

import java.util.regex.*;

public class Resetting {
    public static void main(String[] args) throws Exception {
        Matcher m = Pattern.compile("[frb][aiu][gx]")
                .matcher("fix the rug with bags");
        while (m.find()) {
            System.out.print(m.group() + " ");
        }
        System.out.println();
        m.reset("fix the rig with rags");
        while (m.find()) {
            System.out.print(m.group() + " ");
        }
    }
}

在这里插入图片描述

使用不带参数的 reset() 方法,可以将 Matcher 对象重新设置到当前字符序列的起始位置。

正则表达式与 Java I/O

到目前为止,我们看到的例子都是将正则表达式用于静态的字符串。下面的例子将向你演示,如何应用正则表达式在一个文件中进行搜索匹配操作。JGrep.java 的灵感源自于 Unix 上的 grep。它有两个参数:文件名以及要匹配的正则表达式。输出的是每行有匹配的部分以及匹配部分在行中的位置。

import java.util.regex.*;
import java.nio.file.*;

public class JGrep {
    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.out.println(
                    "Usage: java JGrep file regex");
            System.exit(0);
        }
        Pattern p = Pattern.compile(args[1]);
        // Iterate through the lines of the input file:    
        int index = 0;
        Matcher m = p.matcher("");
        for (String line : Files.readAllLines(Paths.get(args[0]))) {
            m.reset(line);
            while (m.find()) {
                System.out.println(index++ + ": " +
                        m.group() + ": " + m.start());
            }
        }
    }
}

在这里插入图片描述

Files.readAllLines() 返回一个 List<String> 对象,这意味着可以用 for-in 进行遍历。虽然可以在 for 循环内部创建一个新的 Matcher 对象,但是,在循环体外创建一个空的 Matcher 对象,然后用 reset() 方法每次为 Matcher 加载一行输入,这种处理会有一定的性能优化。最后用 find() 搜索结果。

这里读入的测试参数是 JGrep.java 文件,然后搜索以 [Ssct] 开头的单词。

如果想要更深入地学习正则表达式,你可以阅读 Jeffrey E. F. Friedl 的《精通正则表达式(第2版)》。网络上也有很多正则表达式的介绍,你还可以从 Perl 和 Python 等其他语言的文档中找到有用的信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/105242.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法通关村第三关-青铜挑战数组专题

本期大纲 线性表基础线性表概念数组概念 数组的基本操作数组创建和初始化查找一个元素增加一个元素修改一个元素删除一个元素 小题一道 - - 单调数组问题小题一道 - - 数组合并问题小结 线性表基础 线性表概念 我们先搞清楚几个基本概念&#xff0c;在很多地方会看到线性结构…

cmd命令快速打开MATLAB

文章目录 复制快捷方式添加 -nojvm打开 复制快捷方式 添加 -nojvm 打开 唯一的缺点是无法使用plot&#xff0c;这一点比不上linux系统&#xff0c;不过打开速度还是挺快的。

计算机网络 第四章网络层

文章目录 1 网络层的功能2 数据交换方式&#xff1a;电路交换3 数据交换方式&#xff1a;报文交换4 数据交换方式&#xff1a;分组交换5 数据交换方式&#xff1a;数据报方式6 数据交换方式&#xff1a;虚电路方式及各种方式对比7 路由算法及路由协议8 IP数据报的概念和格式9 I…

疫情集中隔离

系列文章目录 进阶的卡莎C++_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(…

WebSocket协议:5分钟从入门到精通

一、内容概览 WebSocket的出现&#xff0c;使得浏览器具备了实时双向通信的能力。本文由浅入深&#xff0c;介绍了WebSocket如何建立连接、交换数据的细节&#xff0c;以及数据帧的格式。此外&#xff0c;还简要介绍了针对WebSocket的安全攻击&#xff0c;以及协议是如何抵御类…

EasyAR使用

EazyAR后台管理&#xff0c;云定位服务 建模 需要自行拍摄360度视频&#xff0c;后台上传&#xff0c;由EazyAR工作人员完成构建。 标注数据 需要在unity安装EazyAR插件&#xff0c;在unity场景编辑后&#xff0c;上传标注数据。 uinity标注数据 微信小程序中使用&#x…

TCP 协议的可靠传输机制是怎样实现的?

TCP 协议是一种面向连接的、可靠的、基于字节流的传输层协议。 1 它通过以下几种方法来保证数据传输的可靠性&#xff1a; 检验和&#xff1a;TCP 在发送和接收数据时&#xff0c;都会计算一个检验和&#xff0c;用来检测数据是否在传输过程中发生了错误或损坏。如果检验和不匹…

CSS中 通过自定义属性(变量)动态修改元素样式(以 el-input 为例)

传送门&#xff1a;CSS中 自定义属性&#xff08;变量&#xff09;详解 1. 需求及解决方案 需求&#xff1a;通常我们动态修改 div 元素的样式&#xff0c;使用 :style 和 :class 即可&#xff1b;但想要动态修改 如&#xff1a;Element-ui 中输入框&#xff08;input&#x…

ElasticSearch安装、插件介绍及Kibana的安装与使用详解

ElasticSearch安装、插件介绍及Kibana的安装与使用详解 1.安装 ElasticSearch 1.1 安装 JDK 环境 因为 ElasticSearch 是用 Java 语言编写的&#xff0c;所以必须安装 JDK 的环境&#xff0c;并且是 JDK 1.8 以上&#xff0c;具体操作步骤自行百度 安装完成查看 java 版本 …

【代码随想录01】数组总结

抄去吧&#xff0c;保存去吧&#xff01;

ES SearchAPI----Query DSL语言

文章目录 Getting Startedmatch_all查询全部sort排序from\size分页_source指定字段 match匹配查询match_phrase短语匹配multi_match多字段匹配range范围查询bool复合查询must必须匹配&#xff0c;可贡献得分must_not必须不匹配&#xff0c;可贡献得分should可有可无&#xff0c…

GoLong的学习之路(八)语法之Map

文章目录 Map初始化方式判断某个键是否存在map的遍历对value值遍历。对key值遍历 使用delete()函数删除键值对按照指定顺序遍历map元素为map的切片值为切片类型的map 做个题吧 Map 哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合&#xff0c;其中所有的…

MFI芯片I2C地址转换(写读转7位传入API接口)

是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务 MFI芯片I2C地址转换(写读转7位传入API接口&#xff09; #define MFI_I2C_CHIP_ADDR 0x10// 芯片写/读 0x20/0x21(写/读) 七位地址 0x10 //zk 使用读地址…

ubuntu18.4(后改为20.4)部署chatglm2并进行基于 P-Tuning v2 的微调

下载驱动 NVIDIA显卡驱动官方下载地址 下载好对应驱动并放在某个目录下&#xff0c; 在Linux系统中安装NVIDIA显卡驱动前,建议先卸载Linux系统自带的显卡驱动nouveau。 禁用nouveau 首先&#xff0c;编辑黑名单配置。 vim /etc/modprobe.d/blacklist.conf 在文件的最后添加…

Windows客户端下pycharm配置跳板机连接内网服务器

问题&#xff1a;实验室服务器仅限内网访问&#xff0c;无法在宿舍&#xff08;外网&#xff09;访问实验室的所有内部服务器&#xff0c;但同时实验室又提供了一个外网可以访问的跳板机&#xff0c;虽然可以先ssh到跳板机再从跳板机ssh到内网服务器&#xff0c;但这种方式不方…

Unity DOTS系列之Filter Baking Output与Prefab In Baking核心分析

最近DOTS发布了正式的版本, 我们来分享一下DOTS里面Baking核心机制&#xff0c;方便大家上手学习掌握Unity DOTS开发。今天给大家分享的Baking机制中的Filter Baking Output与Prefab In Baking。 对啦&#xff01;这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础…

Linux区分文件类型,file指令,目录权限,umask掩码,共享文件,Linux中的一些有趣指令

file指令&#xff0c;Linux区分文件类型&#xff0c;目录权限&#xff0c;umask掩码&#xff0c;共享文件&#xff0c;Linux中的一些有趣指令 1.Linux中是如何区分文件类型的2. file指令3.目录权限4.umask掩码5.粘滞位6.Linux中的一些有趣指令 所属专栏&#xff1a;Linux学习❤…

海外广告投放保姆级教程,如何使用Quora广告开拓新流量市场?

虽然在Quora 上学习广告相对容易&#xff0c;但需要大量的试验和错误才能找出最有效的方法。一些广告技巧可以让您的工作更有效率。这篇文章将介绍如何有效进行quora广告投放与有价值的 Quora 广告要点&#xff0c;这将为您节省数万美元的广告支出和工作时间&#xff01;往下看…

中国技术的对外输出:Telegram也开始搞小程序应用了

Telegram 宣布为其开发者提供了一项“能够在其中运行迷你应用”的新功能&#xff08; 迷你应用即 Mini App&#xff0c;下文中以“小程序”代替&#xff09;。 在 Telegram 的博客中&#xff0c;开发人员介绍可以使用 JavaScript 构建自己的迷你应用 在一篇博客文章中&#xf…

原型制作的软件 Experience Design mac( XD ) 中文版软件特色

​XD是一个直观、功能强大的UI/UX开发工具&#xff0c;旨在设计、原型、用户之间共享材料以及通过数字技术进行设计交互。Adobe XD提供了开发网站、应用程序、语音界面、游戏界面、电子邮件模板等所需的一切。xd mac软件特色 体验设计的未来。 使用 Adobe XD 中快速直观、即取即…