【问题标题】:Pattern.compile() with an argumentPattern.compile() 带参数
【发布时间】:2015-06-18 11:28:05
【问题描述】:

假设我有这个代码:

public static boolean checkRegex(String regex, String value) {
        if (value == null) return false;
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(value);
        return matcher.matches();
    }
.......

checkRegex("^.+@.+\\..+$", "email@example.com");

我的正则表达式会在编译时编译一次还是在运行时编译多次?

【问题讨论】:

  • 为什么你认为它可能只编译一次?
  • 将编译多次。这不需要模式或匹配器类。
  • @SotiriosDelimanolis:从理论上的观点来看,这是可能的,因为不断传播;但这里很有可能有太多的副作用。
  • @CommuSoft 这是公共方法中的参数。编译时无法编译。
  • @laune:你可以在底部专门调用特定的checkRegex。这是在逻辑和函数式编程的上下文中经常使用的技术。换句话说,除了 checkRegex 之外,您还创建了第二个方法,它只接受一个参数,...省略了第一个命令,因为这些命令可以预先计算。

标签: java regex


【解决方案1】:

Pattern.compile(regex)只是一个对编译器没有特殊意义的方法,所以只能在运行时执行。虽然可以缓存已编译的模式(这在其他语言中完成,例如 Python),但 Sun/Oracle 的 Pattern 实现不会缓存已编译的 Pattern,这意味着每次执行 checkRegex 方法时(即使使用相同的正则表达式),您重新编译正则表达式并获得一个新的Pattern 实例。

顺便说一句,你的方法

public static boolean checkRegex(String regex, String value) {
    if (value == null) return false;
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(value);
    return matcher.matches();
}

可以改写为

public static boolean checkRegex(String regex, String value) {
    if (value == null) 
        return false;
    return value.matches(regex);
}

【讨论】:

    【解决方案2】:

    简答

    虽然从理论的角度来看也许是可能的,不;不是在编译时,这些会再次编译。

    但是Pattern 使用 享元 模式,这样一旦正则表达式被编译,它就会存储在内存中,因此不需要在运行时完全编译

    编译时间

    从理论上讲,编译器可能会执行所谓的constant propagation,从而在编译时解决问题。这可以在您调用的方法是最终的(或者被调用者在编译时已知)等情况下完成。

    如果有人编译您的方法并检查 Java 字节码,它会编译为:

    public static boolean checkRegex(java.lang.String, java.lang.String);
        Code:
           0: aload_1       
           1: ifnonnull     6
           4: iconst_0      
           5: ireturn       
           6: aload_0       
           7: invokestatic  #15                 // Method java/util/regex/Pattern.compile:(Ljava/lang/String;)Ljava/util/regex/Pattern;
          10: astore_2      
          11: aload_2       
          12: aload_1       
          13: invokevirtual #16                 // Method java/util/regex/Pattern.matcher:(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;
          16: astore_3      
          17: aload_3       
          18: invokevirtual #17                 // Method java/util/regex/Matcher.matches:()Z
          21: ireturn       
    
      public static void main(java.lang.String[]);
        Code:
           0: ldc           #18                 // String ^.+@.+\..+$
           2: ldc           #19                 // String email@example.com
           4: invokestatic  #20                 // Method checkRegex:(Ljava/lang/String;Ljava/lang/String;)Z
           7: pop
           ...
    

    如您所见,这些方法被简单地转换为 Java 字节码,并使用两个参数调用该方法。

    一些编译器(主要是用于函数式和逻辑编程语言的编译器)允许程序特化:如果某个调用是使用常量完成的,编译器可以创建该方法的副本并解析已知的内容在编译时。然而,这是一个权衡,因为引入大量专门的方法会减少大型代码库。

    享元运行时模式

    如果你深入研究java.util.regex.Pattern的Java字节码,你会看到:

    private static final HashMap<String, CharPropertyFactory> map;
    
    static CharProperty charPropertyFor(String name) {
            // <editor-fold defaultstate="collapsed" desc="Compiled Code">
            /* 0: getstatic     java/util/regex/Pattern$CharPropertyNames.map:Ljava/util/HashMap;
             * 3: aload_0
             * 4: invokevirtual java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
             * 7: checkcast     java/util/regex/Pattern$CharPropertyNames$CharPropertyFactory
             * 10: astore_1
             * 11: aload_1
             * 12: ifnonnull     19
             * 15: aconst_null
             * 16: goto          23
             * 19: aload_1
             * 20: invokevirtual java/util/regex/Pattern$CharPropertyNames$CharPropertyFactory.make:()Ljava/util/regex/Pattern$CharProperty;
             * 23: areturn
             *  */
            // </editor-fold>
        }
    

    注意HashMap,所以这意味着Pattern 存储正则表达式的片段及其对应的微DFA,这种行为称为Flyweight。这意味着您只需编译一次正则表达式。显然您仍然需要执行查找,所以这不是免费优化,但它肯定会有所帮助。

    【讨论】:

    • 我现在明白了。我的错误印象是编译器在遇到带有字符串表达式的 Pattern.compile() 时会进行优化。我不确定它是否对变量这样做......
    • This means you only compile the regex once. 不,您引用的代码来自 CharPropertyNames 类,它用作某些属性 \p 构造的 工厂(它存储节点模板,这些模板是请求时克隆)。 Pattern 根本不缓存字符串和生成的编译对象。
    • 顺便说一句,您引用的 SO 帖子描述了 Pattern 和 Matcher 解耦的事实,因此编译的正则表达式可以在不同的 Matcher 中重用。然而,程序员负责管理编译后的 Pattern。
    • 顺便说一下,这里是Pattern.compile(String) (Java 8)的源代码,你最终会到达私有方法Pattern.compile(),继续编译正则表达式。 Pattern 由 Node 对象组成,它们通过next 字段链接在一起,因此您不能重复使用 Node 对象,很少有例外。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-15
    • 1970-01-01
    • 2013-10-20
    • 1970-01-01
    • 2014-06-18
    • 1970-01-01
    相关资源
    最近更新 更多