java.util.regex - importância de Pattern.compile ()?

118

Qual é a importância do Pattern.compile()método?
Por que preciso compilar a string regex antes de obter o Matcherobjeto?

Por exemplo :

String regex = "((\\S+)\\s*some\\s*";

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);
Sidharth
fonte
2
Bem, a importância é quase NADA se a implementação (como no JDK 1.7) for apenas um mero ATALHO para o novo padrão (regex, 0); Dito isso, a REAL importância não é o método estático em si, mas a criação e retorno de um novo padrão que pode ser salvo para uso posterior. Talvez existam outras implementações onde o método estático pega uma nova rota e armazena em cache os objetos Pattern, e esse seria um caso real de importância de Pattern.compile ()!
marcolopes
As respostas destacam a importância de separar o padrão e as classes correspondentes (o que provavelmente é o que a pergunta pergunta), mas ninguém responde por que não podemos simplesmente usar um construtor em new Pattern(regex)vez de uma função de compilação estática. O comentário de marcolopes está no local.
kon psych

Respostas:

144

O compile()método é sempre chamado em algum ponto; é a única maneira de criar um objeto Padrão. Portanto, a questão é: por que você deveria chamá-lo explicitamente ? Um motivo é que você precisa de uma referência ao objeto Matcher para poder usar seus métodos, como group(int)recuperar o conteúdo de grupos de captura. A única maneira de obter o objeto Matcher é por meio do matcher()método do objeto Pattern , e a única maneira de obter o objeto Pattern é por meio do compile()método. Depois, há o find()método que, ao contrário matches(), não é duplicado nas classes String ou Pattern.

A outra razão é evitar criar o mesmo objeto Padrão repetidamente. Cada vez que você usa um dos métodos baseados em regex em String (ou o matches()método estático em Pattern), ele cria um novo Pattern e um novo Matcher. Portanto, este snippet de código:

for (String s : myStringList) {
    if ( s.matches("\\d+") ) {
        doSomething();
    }
}

... é exatamente equivalente a isto:

for (String s : myStringList) {
    if ( Pattern.compile("\\d+").matcher(s).matches() ) {
        doSomething();
    }
}

Obviamente, isso está fazendo um trabalho desnecessário. Na verdade, pode facilmente levar mais tempo para compilar a regex e instanciar o objeto Pattern do que para realizar uma correspondência real. Portanto, geralmente faz sentido retirar essa etapa do loop. Você também pode criar o Matcher com antecedência, embora eles não sejam tão caros:

Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("");
for (String s : myStringList) {
    if ( m.reset(s).matches() ) {
        doSomething();
    }
}

Se você está familiarizado com regexes do .NET, pode estar se perguntando se o compile()método do Java está relacionado ao RegexOptions.Compiledmodificador do .NET ; a resposta é não. O Pattern.compile()método Java é meramente equivalente ao construtor Regex do .NET. Quando você especifica a Compiledopção:

Regex r = new Regex(@"\d+", RegexOptions.Compiled); 

... ele compila o regex diretamente para o código de byte CIL, permitindo um desempenho muito mais rápido, mas a um custo significativo no processamento inicial e no uso de memória - pense nisso como esteróides para regexes. Java não tem equivalente; não há diferença entre um padrão criado nos bastidores String#matches(String)e outro com o qual você cria explicitamente Pattern#compile(String).

(EDITAR: Eu disse originalmente que todos os objetos .NET Regex são armazenados em cache, o que é incorreto. Desde .NET 2.0, o armazenamento em cache automático ocorre apenas com métodos estáticos como Regex.Matches(), não quando você chama um construtor Regex diretamente. Ref )

Alan Moore
fonte
1
No entanto, isso não explica a importância de tal método TRIVIAL na classe Pattern! Sempre presumi que o método estático Pattern.compile era muito mais do que um simples ATALHO para um novo padrão (regex, 0); Eu esperava um CACHE de padrões compilados ... eu estava errado. Talvez criar um cache seja mais caro do que criar novos padrões ??!
marcolopes
9
Observe que a classe Matcher não é thread-safe e não deve ser compartilhada entre threads. Por outro lado, Pattern.compile () é.
gswierczynski
1
TLDR; "... [Pattern.compile (...)] compila o regex diretamente para o código de byte CIL, permitindo um desempenho muito mais rápido, mas a um custo significativo no processamento inicial e uso de memória"
sean.boyer
3
Embora seja verdade que Matchers não são tão caros quanto Pattern.compile, fiz algumas métricas em um cenário onde milhares de correspondências de regex estavam acontecendo e houve uma economia adicional muito significativa ao criar o Matcher com antecedência e reutilizá-lo por meio do matcher .Redefinir(). Evitar a criação de novos objetos no heap em métodos chamados milhares de vezes costuma ser muito mais leve na CPU, memória e, portanto, no GC.
Volksman
@Volksman que não é um conselho geral seguro porque os objetos Matcher não são thread-safe. Também não é relevante para a questão. Mas sim, você poderia resetum objeto Matcher que só é usado por um encadeamento por vez para reduzir as alocações.
AndrewF
40

Compile analisa a expressão regular e constrói uma representação na memória . A sobrecarga para compilar é significativa em comparação com uma correspondência. Se você estiver usando um padrão repetidamente, ele obterá algum desempenho para armazenar em cache o padrão compilado.

Thomas Jung
fonte
7
Além disso, você pode especificar sinalizadores como case_insensitive, dot_all, etc. durante a compilação, passando um parâmetro sinalizador extra
Sam Barnum
17

Quando você compila, o PatternJava faz alguns cálculos para tornar Stringmais rápida a localização de correspondências em s. (Constrói uma representação na memória do regex)

Se você for reutilizar Patternvárias vezes, verá um grande aumento de desempenho em relação à criação de um novo a Patterncada vez.

No caso de usar o Padrão apenas uma vez, a etapa de compilação parece apenas uma linha extra de código, mas, na verdade, pode ser muito útil no caso geral.

jjnguy
fonte
5
Claro que você pode escrever tudo em uma linha Matcher matched = Pattern.compile(regex).matcher(text);. Há vantagens nisso em relação à introdução de um único método: os argumentos são efetivamente nomeados e é óbvio como fatorar o Patternpara melhor desempenho (ou dividir os métodos).
Tom Hawtin - tackline
1
Sempre parece que você sabe muito sobre Java. Eles deveriam contratar você para trabalhar para eles ...
jjnguy
5

É questão de desempenho e uso de memória, compilar e manter o padrão cumprido se precisar usá-lo muito. Um uso típico de regex é validar a entrada do usuário (formatar) e também formatar os dados de saída para os usuários , nessas classes, salvando o padrão cumprido, parece bastante lógico, já que costumam chamar muito.

Abaixo está um validador de exemplo, que realmente é muito chamado :)

public class AmountValidator {
    //Accept 123 - 123,456 - 123,345.34
    private static final String AMOUNT_REGEX="\\d{1,3}(,\\d{3})*(\\.\\d{1,4})?|\\.\\d{1,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;
    }    
}

Conforme mencionado por @Alan Moore, se você tiver regex reutilizável em seu código (antes de um loop, por exemplo), você deve compilar e salvar o padrão para reutilização.

Alireza Fattahi
fonte
2

Pattern.compile()permite reutilizar uma regex várias vezes (é threadsafe). O benefício de desempenho pode ser bastante significativo.

Fiz uma avaliação rápida:

    @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 foi entre 3x e 4x mais rápido . Acho que depende muito da própria regex, mas para uma regex que é usada com frequência, procuro umstatic Pattern pattern = Pattern.compile(...)

Apflieger
fonte
0

A pré-compilação do regex aumenta a velocidade. Reutilizar o Matcher oferece outra ligeira aceleração. Se o método for chamado frequentemente, digamos que seja chamado dentro de um loop, o desempenho geral certamente aumentará.

DragonBorn
fonte
0

Semelhante a 'Pattern.compile', há 'RECompiler.compile' [de com.sun.org.apache.regexp.internal] onde:
1. código compilado para o padrão [az] tem 'az' nele
2. código compilado para padrão [0-9] tem '09' nele
3. código compilado para o padrão [abc] tem 'aabbcc' nele.

Assim, o código compilado é uma ótima maneira de generalizar vários casos. Assim, em vez de ter diferentes situações de manipulação de código 1,2 e 3. O problema se reduz à comparação com o ascii do elemento presente e do próximo no código compilado, daí os pares. Portanto,
a. qualquer coisa com ascii entre a e z está entre a e z
b. qualquer coisa com ascii entre 'a e a é definitivamente' a '

Devashish Priyadarshi
fonte
0

A classe de padrão é o ponto de entrada do mecanismo regex. Você pode usá-la por meio de Pattern.matches () e Pattern.comiple (). #Diferença entre esses dois. Match () - para verificar rapidamente se um texto (String) corresponde a uma dada expressão regular comiple () - cria a referência de Pattern. Portanto, pode usar várias vezes para comparar a expressão regular com vários textos.

Para referência:

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()+ " ");
     }
}
vkstream
fonte