Статьи

Google Guava Cache с шаблонами регулярных выражений

Совсем недавно я увидел прекрасную презентацию о Google Guava, и мы пришли к выводу, что в нашем проекте было бы очень интересно использовать все функции Cache . Давайте посмотрим на класс Pattern регулярных выражений и его функцию компиляции . Довольно часто в коде мы видим, что каждый раз, когда используется регулярное выражение, программист неоднократно вызывает вышеупомянутую функцию Pattern.compile () с одним и тем же аргументом, таким образом компилируя одно и то же регулярное выражение снова и снова. Однако, что можно сделать, это кешировать результаты таких компиляций — давайте посмотрим на класс утилит RegexpUtils:

RegexpUtils.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package pl.grzejszczak.marcin.guava.cache.utils;
 
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
 
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import static java.lang.String.format;
 
public final class RegexpUtils {
 
    private RegexpUtils() {
        throw new UnsupportedOperationException("RegexpUtils is a utility class - don't instantiate it!");
    }
 
    private static final LoadingCache<String, Pattern> COMPILED_PATTERNS =
            CacheBuilder.newBuilder().build(new CacheLoader<String, Pattern>() {
                @Override
                public Pattern load(String regexp) throws Exception {
                    return Pattern.compile(regexp);
                }
            });
 
    public static Pattern getPattern(String regexp) {
        try {
            return COMPILED_PATTERNS.get(regexp);
        } catch (ExecutionException e) {
            throw new RuntimeException(format("Error when getting a pattern [%s] from cache", regexp), e);
        }
    }
 
    public static boolean matches(String stringToCheck, String regexp) {
        return doGetMatcher(stringToCheck, regexp).matches();
    }
 
    public static Matcher getMatcher(String stringToCheck, String regexp) {
        return doGetMatcher(stringToCheck, regexp);
    }
 
    private static Matcher doGetMatcher(String stringToCheck, String regexp) {
        Pattern pattern = getPattern(regexp);
        return pattern.matcher(stringToCheck);
    }
 
}

Как вы можете видеть, загрузочный кэш Guava с CacheBuilder используется для заполнения кеша новым скомпилированным шаблоном, если он не найден. Из-за кэширования скомпилированного шаблона, если компиляция уже состоялась, он больше не повторится (в нашем случае, так как у нас не было никакого набора срока действия). Теперь простой тест

GuavaCache.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package pl.grzejszczak.marcin.guava.cache;
 
import com.google.common.base.Stopwatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.grzejszczak.marcin.guava.cache.utils.RegexpUtils;
 
import java.util.regex.Pattern;
 
import static java.lang.String.format;
 
public class GuavaCache {
    private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCache.class);
    public static final String STRING_TO_MATCH = "something";
 
    public static void main(String[] args) {
        runTestForManualCompilationAndOneUsingCache(1);
        runTestForManualCompilationAndOneUsingCache(10);
        runTestForManualCompilationAndOneUsingCache(100);
        runTestForManualCompilationAndOneUsingCache(1000);
        runTestForManualCompilationAndOneUsingCache(10000);
        runTestForManualCompilationAndOneUsingCache(100000);
        runTestForManualCompilationAndOneUsingCache(1000000);
    }
 
    private static void runTestForManualCompilationAndOneUsingCache(int firstNoOfRepetitions) {
        repeatManualCompilation(firstNoOfRepetitions);
        repeatCompilationWithCache(firstNoOfRepetitions);
    }
 
    private static void repeatManualCompilation(int noOfRepetitions) {
        Stopwatch stopwatch = new Stopwatch().start();
        compileAndMatchPatternManually(noOfRepetitions);
        LOGGER.debug(format("Time needed to compile and check regexp expression [%d] ms, no of iterations [%d]", stopwatch.elapsedMillis(), noOfRepetitions));
    }
 
    private static void repeatCompilationWithCache(int noOfRepetitions) {
        Stopwatch stopwatch = new Stopwatch().start();
        compileAndMatchPatternUsingCache(noOfRepetitions);
        LOGGER.debug(format("Time needed to compile and check regexp expression using Cache [%d] ms, no of iterations [%d]", stopwatch.elapsedMillis(), noOfRepetitions));
    }
 
    private static void compileAndMatchPatternManually(int limit) {
        for (int i = 0; i < limit; i++) {
            Pattern.compile("something").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something1").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something2").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something3").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something4").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something5").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something6").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something7").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something8").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something9").matcher(STRING_TO_MATCH).matches();
        }
    }
 
    private static void compileAndMatchPatternUsingCache(int limit) {
        for (int i = 0; i < limit; i++) {
            RegexpUtils.matches(STRING_TO_MATCH, "something");
            RegexpUtils.matches(STRING_TO_MATCH, "something1");
            RegexpUtils.matches(STRING_TO_MATCH, "something2");
            RegexpUtils.matches(STRING_TO_MATCH, "something3");
            RegexpUtils.matches(STRING_TO_MATCH, "something4");
            RegexpUtils.matches(STRING_TO_MATCH, "something5");
            RegexpUtils.matches(STRING_TO_MATCH, "something6");
            RegexpUtils.matches(STRING_TO_MATCH, "something7");
            RegexpUtils.matches(STRING_TO_MATCH, "something8");
            RegexpUtils.matches(STRING_TO_MATCH, "something9");
        }
    }
 
}

Мы проводим серию тестов и проверяем время их выполнения. Обратите внимание, что результаты этих тестов не являются точными из-за того, что приложение не запускается изолированно, поэтому многочисленные условия могут повлиять на время выполнения. Мы заинтересованы в том, чтобы показать некоторую степень проблемы, а не точное время выполнения. Для заданного количества итераций (1,10,100,1000,10000,100000,1000000) мы либо компилируем 10 регулярных выражений, либо используем кеш Guava для извлечения скомпилированного шаблона, а затем сопоставляем их со строкой для сопоставления. Это журналы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [1] ms, no of iterations [1]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [35] ms, no of iterations [1]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [1] ms, no of iterations [10]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [0] ms, no of iterations [10]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [8] ms, no of iterations [100]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [3] ms, no of iterations [100]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [10] ms, no of iterations [1000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [10] ms, no of iterations [1000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [83] ms, no of iterations [10000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [33] ms, no of iterations [10000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [800] ms, no of iterations [100000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [279] ms, no of iterations [100000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [7562] ms, no of iterations [1000000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [3067] ms, no of iterations [1000000]

Вы можете найти источники здесь в каталоге Guava / Cache или перейти по URL https://bitbucket.org/gregorin1987/too-much-coding/src