Uma visão geral do desempenho das expressões regulares em Java
1. Visão geral
Neste tutorial rápido, mostraremos como funciona o mecanismo de correspondência de padrões. Também apresentaremos diferentes maneiras de otimizarregular expressions em Java.
Para obter uma introdução ao uso deregular expressions, consulteto this article here.
2. O mecanismo de correspondência de padrões
O pacotejava.util.regex usa um tipo de mecanismo de correspondência de padrões chamadoNondeterministic Finite Automaton (NFA). It’s considered nondeterministic because while trying to match a regular expression on a given string, each character in the input might be checked several times against different parts of the regular expression.
Em segundo plano, o mecanismo mencionado acima usabacktracking. Esse algoritmo geral tenta esgotar todas as possibilidades até declarar falha. Considere o seguinte exemplo para entender melhor oNFA:
"tra(vel|ce|de)m"
Com a entradaString “travel“, o mecanismo primeiro procurará por “tra” e o encontrará imediatamente.
Depois disso, ele tentará corresponder a “vel” a partir do quarto caractere. Isto irá corresponder, então irá avançar e tentar corresponder a “m“.
Isso não corresponde e, por esse motivo, ele voltará ao quarto caractere e procurará “ce“. Novamente, isso não vai combinar, então ele vai voltar novamente para a quarta posição e tentar com “de“. Essa string também não corresponderá e, portanto, voltará para o segundo caractere na string de entrada e tentará pesquisar por outro “tra“.
Com a última falha, o algoritmo retornará a falha.
Com o último exemplo simples, o mecanismo teve que retroceder várias vezes ao tentar corresponder a entradaString à expressão regular. Por causa disso,it’s important to minimize the amount of backtracking that it does.
3. Maneiras de otimizarRegular Expressions
3.1. Evitar recompilação
Expressões regulares em Java são compiladas em uma estrutura de dados interna. Essa compilação é o processo demorado.
Cada vez que invocamos o métodoString.matches(String regex) , a expressão regular especificada é recompilada:
if (input.matches(regexPattern)) {
// do something
}
Como podemos ver, toda vez que a condição é avaliada, a expressão regex é compilada.
Para otimizar, é possível compilar o padrão primeiro e, em seguida, criar umMatcher para encontrar as coincidências no valor:
Pattern pattern = Pattern.compile(regexPattern);
for(String value : values) {
Matcher matcher = pattern.matcher(value);
if (matcher.matches()) {
// do something
}
}
Uma alternativa para a otimização acima é usar a mesma instânciaMatcher com seu métodoreset():
Pattern pattern = Pattern.compile(regexPattern);
Matcher matcher = pattern.matcher("");
for(String value : values) {
matcher.reset(value);
if (matcher.matches()) {
// do something
}
}
Devido ao fato deMatcher não ser thread-safe, devemos ter cuidado com o uso dessa variação. Pode ser provavelmente perigoso em cenários multiencadeados.
Para resumir, em todas as situações em que temos certeza de que há apenas um usuário deMatcher em qualquer momento, não há problema em reutilizá-lo comreset. De resto, basta reutilizar o pré-compilado.
3.2. Trabalhando com alternância
Como acabamos de verificar na última seção, o uso inadequado de alternâncias pode ser prejudicial ao desempenho. É importante colocar as opções com maior probabilidade de acontecer na frente para que possam ser correspondidas mais rapidamente.
Além disso, temos que extrair padrões comuns entre eles. Não é o mesmo colocar:
(travel | trade | trace)
Do que:
tra(vel | de | ce)
O último é mais rápido porque oNFA tentará corresponder a “tra” e não tentará nenhuma das alternativas se não o encontrar.
3.3. Capturando grupos
Cada vez que capturamos grupos, incorremos em uma pequena penalidade.
Se não precisarmos capturar o texto dentro de um grupo, devemos considerar o uso de grupos sem captura. Em vez de usar “(M)“, use “(?:M)“.
4. Conclusão
Neste artigo rápido, revisamos brevemente comoNFA funciona. Em seguida, exploramos como otimizar o desempenho de nossas expressões regulares pré-compilando nossos padrões e reutilizando aMatcher.
Por fim, destacamos algumas considerações a serem lembradas enquanto trabalhamos com alternâncias e grupos.
Como de costume, o código-fonte completo pode ser encontradoover on GitHub.