【问题标题】:Does Pattern.compile cache?Pattern.compile 缓存吗?
【发布时间】:2019-01-07 20:11:46
【问题描述】:

这可能是一个实现细节,但对于 Oracle 和 IBM JDK,至少缓存了编译模式,还是我们作为应用程序开发人员需要自己执行编译模式的缓存?

【问题讨论】:

    标签: java regex


    【解决方案1】:

    据我查看代码 (JDK 6) 所知,它不进行缓存,但一旦构造,Pattern 对象可以缓存在应用程序端并在多个线程之间共享。标准模式似乎是将其分配给最终的静态变量:

    private static final Pattern p = Pattern.compile(",");
    

    【讨论】:

      【解决方案2】:

      我不相信结果会被缓存,并且在codedocumentation 中没有此类行为的证据。自己实现这样的缓存(当然)会相对简单,但我会对这样的缓存有益的用例感兴趣。

      回复。下面的评论和String.split(),有一种不同的方法,即代码为琐碎的 1 或 2 字符模式与更复杂的正则表达式采用不同的路径。但它似乎仍然没有缓存。

      【讨论】:

      • 避免每次都解析正则表达式?如果我没记错的话,在 JDK 7 String.split() 中会缓存 Pattern 的简单情况。
      • 我知道它会避免每次都解析正则表达式,但我对正则表达式的经验是 a)我并不经常关心性能 b)如果我有,它很少是编译阶段有我很担心。 YMMV
      • 感谢它确实在 JDK 代码本身中提供了某种优化,这真是太糟糕了。它只是做一个新的 Pattern() 感谢您的链接。
      • @helpermethod Python 正则表达式引擎与使用 Java 编码的人有什么关系?
      • 我喜欢它在 Java 中的实现方式。大多数时候,开发人员不希望 Java 缓存正则表达式。如果是这样,您最终可能会出现泄漏或至少浪费一些内存。如果你想自己实现它,你可以这样做,而且这样做很简单。 Brian Agnew,您对一个有益的用例感兴趣。我自己有这个用例。我允许用户创建他们自己的正则表达式(我当然会验证它们)。用户使用这些正则表达式每秒测试数百万个事件字符串。不预编译和缓存这些将是一场巨大的灾难。
      【解决方案3】:

      我创建了一个类 CachedPattern,它可以缓存 Pattern 对象。 如果您运行 ma​​in 方法,您会看到 Java 的 Pattern 对象实际上是不同的实例,这也会消耗内存。

      import java.util.HashMap;
      import java.util.regex.Pattern;
      import org.eclipse.core.runtime.Assert;
      
      public class CachedPattern {
      
      public static void main(String[] args){
          Pattern p1 = Pattern.compile("abc");
          Pattern p2 = Pattern.compile("abc");
          Pattern p3 = Pattern.compile("abc");
          Pattern p4 = Pattern.compile("abc");
          Pattern p5 = Pattern.compile("abc");
      
          Pattern x1 =  CachedPattern.compile("abc");
          Pattern x2 =  CachedPattern.compile("abc");
          Pattern x3 =  CachedPattern.compile("abc");
          Pattern x4 =  CachedPattern.compile("abc");
          Pattern x5 =  CachedPattern.compile("abc");
          // are cached objects the same ? YES!
          Assert.isTrue(x1.equals(x2));
          Assert.isTrue(x1.equals(x3));
          Assert.isTrue(x1.equals(x4));
          Assert.isTrue(x1.equals(x5));
          // are non-cached objects the same ? NO!
          Assert.isTrue(p1.equals(p2)); //AssertionFailedException
      }
      
       private static HashMap<String, Pattern> cached = new HashMap<>();
      
       /**
        * This value must be unique, to make sure user won't use this inside "regex" variable,
        * so that objects without flags would be returned
        * For example if UNIQUE_HASH would be empty:
        *     compile(pattern = "abc1")
        *          VS.
        *     compile(pattern = "abc", flag = 1)
        * This would give same keys "abc1" and "abc1"
        */
       private static final String UNIQUE_HASH = "(())[]+@#$%^@!@#$%*";
      
       public static Pattern compile(String regex){
           if(cached.containsKey(regex)){
               return cached.get(regex);
           }
           Pattern p = Pattern.compile(regex);
           cached.put(regex, p);
           return p;
       }
       public static Pattern compile(String regex, int flags){
           String uniqueKey = regex + UNIQUE_HASH + flags;
           if(cached.containsKey(uniqueKey)){
               return cached.get(uniqueKey);
           }
           Pattern p = Pattern.compile(regex);
           cached.put(uniqueKey, p);
           return p;
       }
      
      }
      

      【讨论】:

        【解决方案4】:

        根据[Joshua_Bloch] Effective_Java

        有些对象的创建比其他的要昂贵得多。如果你要去 重复需要这样一个“昂贵的对象”,最好将它缓存起来以备不时之需 重用。不幸的是,当你创建这样一个 目的。假设你想写一个方法来判断一个字符串是否是 有效的罗马数字。这是使用常规执行此操作的最简单方法 表达式:

        // Performance can be greatly improved!
        static boolean isRomanNumeral(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
        + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
        }
        

        这个实现的问题在于它依赖于 String.matches 方法。虽然 String.matches 是最简单的方法 检查字符串是否匹配正则表达式,不适合重复 在性能关键的情况下使用。问题是它在内部创建 正则表达式的 Pattern 实例,并且只使用一次,之后 它变得有资格进行垃圾收集。创建 Pattern 实例 很昂贵,因为它需要将正则表达式编译成有限的 状态机。 为了提高性能,将正则表达式显式编译为 模式实例(不可变)作为类初始化的一部分,缓存它, 并为 isRomanNumeral 的每次调用重用相同的实例 方法:

        // Reusing expensive object for improved performance
        public class RomanNumerals {
        private static final Pattern ROMAN = Pattern.compile(
        "^(?=.)M*(C[MD]|D?C{0,3})"
        + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
        static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
        }}
        

        isRomanNumeral 的改进版本提供了显着的 如果频繁调用,性能会提高。在我的机器上,原始版本 8 个字符的输入字符串需要 1.1 μs,而改进的版本需要 0.17 μs,快了 6.5 倍

        【讨论】:

          【解决方案5】:

          它没有。如果您有性能敏感区域,您可能希望将模式对象保存为成员变量。

          但是,当您在函数中有正则表达式时,Clojure 或多或少会自动执行此操作。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-24
            • 1970-01-01
            • 2012-03-28
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多