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


118

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);

2
好吧,如果实现(如在JDK 1.7中)仅仅是对新Pattern(regex,0)的简短说明,则重要性几乎没有。也就是说,真正的重要性不是静态方法本身,而是新的Pattern的创建和返回,可以将其保存以备后用。也许在其他实现中,静态方法采用新的路由并缓存Pattern对象,这将是Pattern.compile()重要性的真实案例!
marcolopes 2014年

答案突出了分离模式和匹配类的重要性(这可能是问题所在),但是没有人回答为什么我们不能只使用构造函数new Pattern(regex)而不是静态编译函数。marcolopes评论已当场。
kon psych

Answers:


144

compile()总是在某个时候调用该方法。这是创建Pattern对象的唯一方法。所以问题是,为什么要显式地调用它?原因之一是您需要对Matcher对象的引用,以便可以使用其方法,例如group(int)检索捕获组的内容。保留Matcher对象的唯一方法是通过Pattern对象的matcher()方法,而保留Pattern对象的唯一方法是通过compile()方法。然后是与String或Pattern类find()不同的方法,该方法不同于matches()

另一个原因是避免一遍又一遍地创建相同的Pattern对象。每次您使用String中正则表达式支持的方法之一(或matches()Pattern中的静态方法)时,它都会创建一个新的Pattern和一个Matcher。因此,此代码段:

for (String s : myStringList) {
    if ( s.matches("\\d+") ) {
        doSomething();
    }
}

...完全等于:

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

显然,这做了很多不必要的工作。实际上,与执行实际匹配相比,编译正则表达式和实例化Pattern对象要花很长时间。因此,将这一步骤从循环中拉出来通常是有意义的。您也可以提前创建Matcher,尽管它们并不那么昂贵:

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

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

Regex r = new Regex(@"\d+", RegexOptions.Compiled); 

...它将正则表达式直接编译为CIL字节码,从而使其执行得更快,但是在前期处理和内存使用方面却付出了高昂的代价-将其视为正则表达式的类固醇。Java没有等效的功能。在幕后创建的模式String#matches(String)与您使用显式创建的模式之间没有区别Pattern#compile(String)

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


1
但是,这并不能解释在类类上使用这样的TRIVIAL方法的重要性!我一直认为静态方法Pattern.compile不仅仅是对新Pattern(regex,0)的简单SHORTCUT; 我期待着CACHE的编译模式...我错了。也许创建缓存比创建新模式更昂贵?
marcolopes 2014年

9
请注意,Matcher类不是线程安全的,不应在线程之间共享。另一方面,Pattern.compile()是。
gswierczynski 2014年

1
TLDR;“ ... [Pattern.compile(...)]将正则表达式直接编译为CIL字节码,从而使其执行得更快,但是在前期处理和内存使用方面却付出了高昂的代价”
sean.boyer

3
虽然确实Matchers并不比Pattern.compile贵,但我在发生数千个正则表达式匹配的情况下做了一些度量,并且通过提前创建Matcher并通过Matcher重用它可以节省很多额外的钱。重启()。在CPU,内存以及GC上,避免使用称为数千次的方法在堆中创建新对象通常要轻得多。
Volksman

@Volksman不安全的一般建议,因为Matcher对象不是线程安全的。这也与问题无关。但是可以的,您可以使用reset一个Matcher对象,该对象一次只能被一个线程使用,以减少分配。
AndrewF

40

编译会解析正则表达式并构建一个内存表示形式。与匹配相比,编译的开销非常大。如果您反复使用模式,则缓存已编译模式将获得一些性能。


7
另外,您可以在编译过程中指定像CASE_INSENSITIVE,dot_all等标志,通过传递一个额外的标志参数
山姆·巴纳姆

17

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

如果要重复使用Pattern多次,那么Pattern每次创建新的性能都会得到极大的提高。

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


5
当然,您可以全部写在一行中Matcher matched = Pattern.compile(regex).matcher(text);。与引入单个方法相比,这样做有很多优点:有效地命名了参数,并且显而易见的是,如何将参数Pattern分解为更好的性能(或拆分为多个方法)。
Tom Hawtin-抢险活动

1
好像您对Java的了解一样。他们应该雇用您为他们工作...
jjnguy

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;
    }    
}

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


2

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()));
    }

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


0

预编译正则表达式可提高速度。重复使用Matcher可以使您略有加速。如果该方法被频繁调用,而又在循环内被调用,则总体性能肯定会提高。


0

与“ Pattern.compile”类似,存在“ RECompiler.compile” [来自com.sun.org.apache.regexp.internal],其中:
1.模式[az]的已编译代码中包含'az'2 .用于以下代码的已
编译代码模式[0-9]的内容
为'09'。3.模式[abc]的编译代码的内容为'aabbcc'。

因此,编译后的代码是概括多种情况的好方法。因此,代替具有不同的代码处理情况1,2和3。通过与已编译代码中当前元素和下一个元素的ascii进行比较,从而减少了问题,因此成对出现。因此
。在a和z之间具有ascii的任何事物都在a和z
b 之间 。在“ a”和“ a”之间具有ascii的任何内容绝对是“ a”


0

Pattern类是正则表达式引擎的入口点。您可以通过Pattern.matches()和Pattern.comiple()使用它。#两者之间的差异。 matchs() -用于快速检查文本(字符串)是否与给定的正则表达式 comiple() -创建Pattern的引用。因此可以多次使用正则表达式来匹配多个文本。

以供参考:

public static void main(String[] args) {
     //single time uses
     String text="The Moon is far away from the Earth";
     String pattern = ".*is.*";
     boolean matches=Pattern.matches(pattern,text);
     System.out.println("Matches::"+matches);

    //multiple time uses
     Pattern p= Pattern.compile("ab");
     Matcher  m=p.matcher("abaaaba");
     while(m.find()) {
         System.out.println(m.start()+ " ");
     }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.