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

Posted

技术标签:

【中文标题】java.util.regex - Pattern.compile() 的重要性?【英文标题】:java.util.regex - importance of Pattern.compile()? 【发布时间】:2010-12-15 18:23:02 【问题描述】:

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

【问题讨论】:

好吧,如果实现(如在 JDK 1.7 中)只是新模式(正则表达式,0)的快捷方式,那么重要性几乎为零。也就是说,真正重要的不是静态方法本身,而是可以保存以供以后使用的新模式的创建和返回。也许还有其他实现,其中静态方法采用新路由并缓存 Pattern 对象,这将是 Pattern.compile() 重要性的真实案例! 答案强调了分离模式和匹配类的重要性(这可能是问题所要问的),但没有人回答为什么我们不能只使用构造函数new Pattern(regex) 而不是静态编译函数. marcolopes 评论在现场。 【参考方案1】:

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(...)

【讨论】:

【参考方案2】:

Pattern 类是正则表达式引擎的入口点。您可以通过 Pattern.matches() 和 Pattern.comiple() 使用它。 #这两者的区别。 ma​​tches()- 用于快速检查文本(字符串)是否匹配给定的正则表达式 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()+ " ");
     

【讨论】:

【参考方案3】:

与“Pattern.compile”类似,还有“RECompiler.compile”[来自 com.sun.org.apache.regexp.internal],其中: 1。模式 [a-z] 的编译代码中包含“az” 2。模式 [0-9] 的编译代码中包含“09” 3。模式 [abc] 的编译代码中包含“aabbcc”。

因此,编译后的代码是概括多种情况的好方法。因此,而不是有不同的代码处理情况 1,2 和 3 。问题减少到与编译代码中当前和下一个元素的 ascii 进行比较,因此是对。 因此 一个。在 a 和 z 之间带有 ascii 的任何东西都在 a 和 z 之间 乙。 'a 和 a 之间带有 ascii 的任何东西都绝对是 'a'

【讨论】:

【参考方案4】:

这是性能和内存使用的问题,如果您需要大量使用它,请编译并保留已编译的模式。 正则表达式的典型用法是验证用户输入(格式),以及为用户格式化输出数据,在这些类中,保存已编译的模式,看起来很合乎逻辑,因为它们通常调用很多。

下面是一个示例验证器,它确实被称为很多:)

public class AmountValidator 
    //Accept 123 - 123,456 - 123,345.34
    private static final String AMOUNT_REGEX="\\d1,3(,\\d3)*(\\.\\d1,4)?|\\.\\d1,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 所提到的,如果您的代码中有可重用的正则表达式(例如在循环之前),您必须编译并保存模式以供重用。

【讨论】:

【参考方案5】:

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

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

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() 之类的静态方法中,而不是在您直接调用 Regex 构造函数时发生。@987654321 @)

【讨论】:

然而,这并不能解释这种 TRIVIAL 方法对 Pattern 类的重要性!我一直认为静态方法 Pattern.compile 不仅仅是一个简单的指向 new Pattern(regex, 0); 的快捷方式。我期待一个编译模式的缓存......我错了。也许创建缓存比创建新模式更昂贵??! 请注意,Matcher 类不是线程安全的,不应跨线程共享。另一方面 Pattern.compile() 是。 TLDR; “... [Pattern.compile(...)] 将正则表达式直接编译为 CIL 字节码,使其执行速度更快,但在前期处理和内存使用方面的成本很高” 虽然 Matcher 确实不像 Pattern.compile 那样昂贵,但我在发生数千个正则表达式匹配的情况下做了一些指标,并且通过创建 Matcher 节省了额外的非常显着的成本提前并通过 matcher.reset() 重用它。避免在调用数千次的方法中在堆中创建新对象通常对 CPU、内存和 GC 的负担要轻得多。 @Volksman 这是不安全的一般建议,因为 Matcher 对象不是线程安全的。它也与问题无关。但是,是的,您可以 reset 一个 Matcher 对象,该对象一次只能由一个线程使用,以减少分配。【参考方案6】:

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

【讨论】:

另外,您可以在编译期间指定诸如 case_insensitive、dot_all 等标志,方法是传入一个额外的标志参数【参考方案7】:

当您编译@​​987654321@ 时,Java 会进行一些计算以更快地在Strings 中找到匹配项。 (构建正则表达式的内存表示)

如果您要多次重复使用Pattern,您会发现与每次创建一个新的Pattern 相比,性能会大幅提升。

在只使用一次Pattern 的情况下,编译步骤看起来就像是多出一行代码,但实际上在一般情况下它会很有帮助。

【讨论】:

当然可以写在一行Matcher matched = Pattern.compile(regex).matcher(text);。与引入单一方法相比,这样做有一些优势:参数被有效命名,并且很明显如何分解Pattern 以获得更好的性能(或跨方法拆分)。 似乎您总是对 Java 非常了解。他们应该雇用你为他们工作......

以上是关于java.util.regex - Pattern.compile() 的重要性?的主要内容,如果未能解决你的问题,请参考以下文章

RE validation in Java EE(java.util.regex.Pattern)

RE validation in Java EE(java.util.regex.Pattern)

如何使用protobuf序列化java.util.regex.Pattern?

正则表达式变得疯狂:java.util.regex.Pattern 匹配器进入高 CPU 循环

java.util.regex中的pattern 和 matcher的用法

Java.util.regex中matcher 和pattern的用法