java.util.regex - Pattern.compile()的重要性?

Sid*_*rth 115 java regex

Pattern.compile()方法的重要性是什么?
为什么我需要在获取Matcher对象之前编译正则表达式字符串?

例如 :

String regex = "((\\S+)\\s*some\\s*";

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);
Run Code Online (Sandbox Code Playgroud)

Ala*_*ore 138

compile()总是在某个时刻调用该方法; 这是创建Pattern对象的唯一方法.所以问题是,你为什么要明确地称之为?一个原因是您需要对Matcher对象的引用,以便您可以使用其方法,例如group(int)检索捕获组的内容.获取Matcher对象的唯一方法是通过Pattern对象的matcher()方法,获取Pattern对象的唯一方法是通过该compile()方法.然后是find()方法,与matches()String或Pattern类中不重复的方法不同.

另一个原因是避免反复创建相同的Pattern对象.每次使用String中的一个正则表达式方法(或matches()Pattern中的静态方法)时,它都会创建一个新的Pattern和一个新的Matcher.所以这段代码片段:

for (String s : myStringList) {
    if ( s.matches("\\d+") ) {
        doSomething();
    }
}
Run Code Online (Sandbox Code Playgroud)

......完全等同于:

for (String s : myStringList) {
    if ( Pattern.compile("\\d+").matcher(s).matches() ) {
        doSomething();
    }
}
Run Code Online (Sandbox Code Playgroud)

显然,那是在做很多不必要的工作.事实上,编译正则表达式并实例化Pattern对象比执行实际匹配所需的时间更长.因此,将该步骤拉出循环通常是有意义的.您也可以提前创建Matcher,尽管它们并不是那么昂贵:

Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("");
for (String s : myStringList) {
    if ( m.reset(s).matches() ) {
        doSomething();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您熟悉.NET正则表达式,您可能想知道Java的compile()方法是否与.NET的RegexOptions.Compiled修饰符相关; 答案是不.Java的Pattern.compile()方法仅相当于.NET的Regex构造函数.指定Compiled选项时:

Regex r = new Regex(@"\d+", RegexOptions.Compiled); 
Run Code Online (Sandbox Code Playgroud)

......它直接编译的正则表达式CIL字节代码,允许其以更快的速度进行,但在先期处理和内存使用的显著成本-认为它是类固醇的正则表达式.Java没有等价物; 在幕后创建的模式String#matches(String)与您明确创建的模式之间没有区别Pattern#compile(String).

(编辑:我原来是说,所有的.NET regex对象缓存,这是不正确由于.NET 2.0,自动缓存只有像静态方法出现Regex.Matches(),而不是当你直接调用正则表达式的构造.REF)

  • 请注意,Matcher类不是线程安全的,不应跨线程共享.另一方面,Pattern.compile()是. (9认同)
  • @ sean.boyer这完全不是作者所说的. (4认同)
  • 虽然Matchers确实没有Pattern.compile那么昂贵,但我在一些场景中做了一些指标,其中发生了数千个正则表达式匹配,并且通过提前创建Matcher并通过匹配器重用它来进行额外的,非常显着的节省. .重启().在数千次调用的方法中避免在堆中创建新对象通常在CPU,内存和GC上都要轻得多. (3认同)

Tho*_*ung 37

Compile 解析正则表达式并构建内存中表示.与匹配相比,编译的开销很大.如果你反复使用一个模式,它将获得一些缓存编译模式的性能.

  • 另外,您可以通过传入额外的flags参数在编译期间指定case_insensitive,dot_all等标志 (6认同)

jjn*_*guy 17

编译时,PatternJava会进行一些计算,以便String更快地找到匹配项.(构建正则表达式的内存表示)

如果您要Pattern多次重复使用,您会发现Pattern每次创建新内容时性能都会大幅提升.

在仅使用Pattern一次的情况下,编译步骤似乎只是一行额外的代码,但事实上,它在一般情况下非常有用.

  • 当然你可以把它全部写在一行`Matcher matched = Pattern.compile(regex).matcher(text);`.引入单个方法有一些优点:参数有效地命名,很明显如何分解"模式"以获得更好的性能(或跨方法分割). (5认同)
  • 哈,我知道.这是个玩笑. (2认同)

Ali*_*ahi 5

这与性能和内存使用情况有关,如果需要大量使用,请编译并保留编译后的模式。regex的典型用法是验证用户输入(格式),并格式化用户的输出数据,在这些类中,保存编译后的模式似乎很合逻辑,因为通常它们会经常调用它。

下面是一个示例验证器,它真的很多:)

public class AmountValidator {
    //Accept 123 - 123,456 - 123,345.34
    private static final String AMOUNT_REGEX="\\d{1,3}(,\\d{3})*(\\.\\d{1,4})?|\\.\\d{1,4}";
    //Compile and save the pattern  
    private static final Pattern AMOUNT_PATTERN = Pattern.compile(AMOUNT_REGEX);


    public boolean validate(String amount){

         if (!AMOUNT_PATTERN.matcher(amount).matches()) {
            return false;
         }    
        return true;
    }    
}
Run Code Online (Sandbox Code Playgroud)

如@Alan Moore所述,如果您的代码中有可重用的正则表达式(例如在循环之前),则必须编译并保存模式以供重用。


apf*_*ger 5

Pattern.compile()允许多次重用正则表达式(它是线程安全的)。性能优势可能非常显着。

我做了一个快速基准测试:

    @Test
    public void recompile() {
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            Pattern.compile("ab").matcher("abcde").matches();
        }
        System.out.println("recompile " + Duration.between(before, Instant.now()));
    }

    @Test
    public void compileOnce() {
        var pattern = Pattern.compile("ab");
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            pattern.matcher("abcde").matches();
        }
        System.out.println("compile once " + Duration.between(before, Instant.now()));
    }
Run Code Online (Sandbox Code Playgroud)

compileOnce 的速度提高了3 倍到 4 倍。我想这很大程度上取决于正则表达式本身,但对于经常使用的正则表达式,我选择static Pattern pattern = Pattern.compile(...)