java.util.regex - ¿importancia de Pattern.compile ()?

118

¿Cuál es la importancia del Pattern.compile()método?
¿Por qué necesito compilar la cadena de expresiones regulares antes de obtener el Matcherobjeto?

Por ejemplo :

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

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);
Sidharth
fuente
2
Bueno, la importancia es casi NINGUNA si la implementación (como en JDK 1.7) es solo un ACCESO DIRECTO al nuevo Patrón (regex, 0); Dicho esto, la importancia REAL no es el método estático en sí, sino la creación y devolución de un nuevo patrón que se puede guardar para su uso posterior. Tal vez haya otras implementaciones en las que el método estático toma una nueva ruta y almacena en caché los objetos de Pattern, ¡y ese sería un caso real de importancia de Pattern.compile ()!
marcolopes
Las respuestas resaltan la importancia de separar patrones y clases coincidentes (que es probablemente lo que pregunta la pregunta), pero nadie responde por qué no podemos usar un constructor en new Pattern(regex)lugar de una función de compilación estática. El comentario de Marcolopes está sobre el terreno.
kon psych

Respuestas:

144

El compile()método siempre se llama en algún momento; es la única forma de crear un objeto Patrón. Entonces la pregunta es realmente, ¿por qué debería llamarlo explícitamente ? Una razón es que necesita una referencia al objeto Matcher para poder usar sus métodos, como group(int)recuperar el contenido de los grupos de captura. La única forma de obtener el objeto Matcher es a través del matcher()método del objeto Pattern , y la única forma de obtener el objeto Pattern es a través del compile()método. Luego está el find()método que, a diferencia matches(), no está duplicado en las clases String o Pattern.

La otra razón es evitar crear el mismo objeto Patrón una y otra vez. Cada vez que utiliza uno de los métodos basados ​​en expresiones regulares en String (o el matches()método estático en Pattern), crea un nuevo Pattern y un nuevo Matcher. Entonces este fragmento de código:

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

... es exactamente equivalente a esto:

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

Obviamente, eso está haciendo mucho trabajo innecesario. De hecho, puede llevar más tiempo compilar la expresión regular y crear una instancia del objeto Pattern que realizar una coincidencia real. Entonces, por lo general, tiene sentido sacar ese paso del círculo. También puede crear el Matcher con anticipación, aunque no son tan caros:

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

Si está familiarizado con las expresiones regulares de .NET, puede que se pregunte si el compile()método de Java está relacionado con el RegexOptions.Compiledmodificador de .NET ; la respuesta es no. El Pattern.compile()método de Java es simplemente equivalente al constructor Regex de .NET. Cuando especifica la Compiledopción:

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

... compila la expresión regular directamente en el código de bytes CIL, lo que le permite funcionar mucho más rápido, pero a un costo significativo en el procesamiento inicial y el uso de memoria; considérelo como esteroides para expresiones regulares. Java no tiene equivalente; no hay diferencia entre un patrón creado detrás de escena String#matches(String)y uno con el que crea explícitamente Pattern#compile(String).

(EDITAR: originalmente dije que todos los objetos .NET Regex se almacenan en caché, lo cual es incorrecto. Desde .NET 2.0, el almacenamiento en caché automático ocurre solo con métodos estáticos como Regex.Matches(), no cuando se llama a un constructor Regex directamente. Ref )

Alan Moore
fuente
1
Sin embargo, esto no explica la importancia de un método tan TRIVIAL en la clase Pattern. Siempre asumí que el método estático Pattern.compile era mucho más que un simple ACCESO DIRECTO a un nuevo Patrón (regex, 0); Esperaba un CACHE de patrones compilados ... estaba equivocado. ¿Quizás crear un caché es más caro que crear nuevos patrones?
marcolopes
9
Tenga en cuenta que la clase Matcher no es segura para subprocesos y no debe compartirse entre subprocesos. Por otro lado, Pattern.compile () es.
gswierczynski
1
TLDR; "... [Pattern.compile (...)] compila la expresión regular directamente en el código de bytes CIL, lo que le permite funcionar mucho más rápido, pero a un costo significativo en el procesamiento inicial y el uso de la memoria"
sean.boyer
3
Si bien es cierto que los Matchers no son tan caros como Pattern.compile, hice algunas métricas en un escenario en el que ocurrían miles de coincidencias de expresiones regulares y hubo un ahorro adicional muy significativo al crear el Matcher con anticipación y reutilizarlo a través del Matcher. .Reiniciar(). Evitar la creación de nuevos objetos en el montón en métodos llamados miles de veces suele ser mucho más liviano para la CPU, la memoria y, por lo tanto, el GC.
Volksman
@Volksman, ese no es un consejo general seguro porque los objetos Matcher no son seguros para subprocesos. Tampoco es relevante para la pregunta. Pero sí, podría resetutilizar un objeto Matcher que solo sea utilizado por un hilo a la vez para reducir las asignaciones.
AndrewF
40

Compile analiza la expresión regular y crea una representación en memoria . La sobrecarga para compilar es significativa en comparación con una coincidencia. Si utiliza un patrón repetidamente , obtendrá algo de rendimiento para almacenar en caché el patrón compilado.

Thomas Jung
fuente
7
Además, puede especificar banderas como case_insensitive, dot_all, etc.durante la compilación, pasando un parámetro de banderas adicional
Sam Barnum
17

Cuando compila, PatternJava realiza algunos cálculos para que la búsqueda de coincidencias en Strings sea más rápida. (Crea una representación en memoria de la expresión regular)

Si va a reutilizar Patternvarias veces, verá un gran aumento de rendimiento en comparación con la creación de una nueva Patterncada vez.

En el caso de usar el Patrón solo una vez, el paso de compilación parece una línea extra de código, pero, de hecho, puede ser muy útil en el caso general.

jjnguy
fuente
5
Por supuesto, puede escribirlo todo en una línea Matcher matched = Pattern.compile(regex).matcher(text);. Esto tiene ventajas sobre la introducción de un solo método: los argumentos se nombran de manera efectiva y es obvio cómo factorizarlos Patternpara un mejor rendimiento (o dividirlos entre métodos).
Tom Hawtin - tackline el
1
Siempre parece que sabes mucho sobre Java. Deberían contratarte para trabajar para ellos ...
jjnguy
5

Es cuestión de rendimiento y uso de memoria, compile y mantenga el patrón cumplido si necesita usarlo mucho. Un uso típico de expresiones regulares es validar la entrada del usuario (formato) y también formatear los datos de salida para los usuarios , en estas clases, guardar el patrón cumplido parece bastante lógico, ya que generalmente se llama mucho.

A continuación se muestra un validador de muestra, que realmente se llama mucho :)

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;
    }    
}

Como lo mencionó @Alan Moore, si tiene expresiones regulares reutilizables en su código (antes de un bucle, por ejemplo), debe compilar y guardar el patrón para su reutilización.

Alireza Fattahi
fuente
2

Pattern.compile()permitir reutilizar una expresión regular varias veces (es seguro para subprocesos). El beneficio de rendimiento puede ser bastante significativo.

Hice un punto de referencia rápido:

    @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 fue entre 3 y 4 veces más rápido . Supongo que depende en gran medida de la expresión regular en sí, pero para una expresión regular que se usa a menudo, opto por unastatic Pattern pattern = Pattern.compile(...)

apflieger
fuente
0

La compilación previa de la expresión regular aumenta la velocidad. Reutilizar el Matcher te da otra ligera aceleración. Si el método se llama con frecuencia, digamos que se llama dentro de un ciclo, el rendimiento general ciertamente aumentará.

DragonBorn
fuente
0

Similar a 'Pattern.compile', hay 'RECompiler.compile' [de com.sun.org.apache.regexp.internal] donde:
1. el código compilado para el patrón [az] tiene 'az'
2. código compilado para el patrón [0-9] tiene '09' en él
3. El código compilado para el patrón [abc] tiene 'aabbcc' en él.

Por lo tanto, el código compilado es una excelente manera de generalizar varios casos. Por lo tanto, en lugar de tener diferentes situaciones de manejo de código 1, 2 y 3. El problema se reduce a comparar con el ascii del elemento presente y siguiente en el código compilado, de ahí los pares. Así
a. cualquier cosa con ascii entre a y z está entre a y z
b. cualquier cosa con ascii entre 'ay a es definitivamente' a '

Devashish Priyadarshi
fuente
0

La clase de patrón es el punto de entrada del motor de expresiones regulares. Puede usarlo a través de Pattern.matches () y Pattern.comiple (). # Diferencia entre estos dos. Match () : para comprobar rápidamente si un texto (String) coincide con una expresión regular determinada. comiple () : crea la referencia de Pattern. Por lo tanto, puede usar varias veces para hacer coincidir la expresión regular con varios textos.

Para referencia:

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
fuente