Necesito reemplazar muchas subcadenas diferentes en una cadena de la manera más eficiente. ¿Hay otra forma que no sea la forma de fuerza bruta de reemplazar cada campo usando string.replace?
97
Si la cadena en la que está operando es muy larga, o si está operando con muchas cadenas, entonces podría valer la pena usar un java.util.regex.Matcher (esto requiere tiempo por adelantado para compilar, por lo que no será eficiente si su entrada es muy pequeña o su patrón de búsqueda cambia con frecuencia).
A continuación se muestra un ejemplo completo, basado en una lista de tokens extraídos de un mapa. (Utiliza StringUtils de Apache Commons Lang).
Map<String,String> tokens = new HashMap<String,String>();
tokens.put("cat", "Garfield");
tokens.put("beverage", "coffee");
String template = "%cat% really needs some %beverage%.";
// Create pattern of the format "%(cat|beverage)%"
String patternString = "%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(template);
StringBuffer sb = new StringBuffer();
while(matcher.find()) {
matcher.appendReplacement(sb, tokens.get(matcher.group(1)));
}
matcher.appendTail(sb);
System.out.println(sb.toString());
Una vez que se compila la expresión regular, escanear la cadena de entrada generalmente es muy rápido (aunque si su expresión regular es compleja o implica retroceder, ¡aún necesitaría comparar para confirmar esto!)
"%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Algoritmo
Una de las formas más eficientes de reemplazar cadenas coincidentes (sin expresiones regulares) es usar el algoritmo Aho-Corasick con un Trie eficaz (pronunciado "try"), algoritmo hash rápido e implementación eficiente de colecciones .
Código simple
Una solución simple aprovecha Apache de la
StringUtils.replaceEach
siguiente manera:Esto se ralentiza en textos grandes.
Código rápido
La implementación de Bor del algoritmo Aho-Corasick introduce un poco más de complejidad que se convierte en un detalle de implementación al usar una fachada con la misma firma de método:
Benchmarks
Para los puntos de referencia, el búfer se creó utilizando randomNumeric de la siguiente manera:
Donde
MATCHES_DIVISOR
dicta el número de variables a inyectar:El código de referencia en sí ( JMH parecía excesivo):
1.000.000: 1.000
Un micro-benchmark simple con 1,000,000 de caracteres y 1,000 cadenas colocadas al azar para reemplazar.
No contestar.
10,000: 1,000
Usando 10,000 caracteres y 1,000 cadenas coincidentes para reemplazar:
La división se cierra.
1.000: 10
Usando 1,000 caracteres y 10 cadenas coincidentes para reemplazar:
Para cadenas cortas, la sobrecarga de configurar Aho-Corasick eclipsa el enfoque de fuerza bruta
StringUtils.replaceEach
.Es posible un enfoque híbrido basado en la longitud del texto para obtener lo mejor de ambas implementaciones.
Implementaciones
Considere comparar otras implementaciones para texto de más de 1 MB, que incluyen:
Documentos
Artículos e información relacionada con el algoritmo:
fuente
Esto funcionó para mí:
Ejemplo:
Salida: manzana-plátano-frui-
fuente
Si va a cambiar un String muchas veces, entonces generalmente es más eficiente usar un StringBuilder (pero mida su rendimiento para averiguarlo) :
Cada vez que realiza un reemplazo en un String, se crea un nuevo objeto String, porque los Strings son inmutables. StringBuilder es mutable, es decir, se puede cambiar tanto como desee.
fuente
StringBuilder
realizará el reemplazo de manera más eficiente, ya que su búfer de matriz de caracteres se puede especificar con la longitud requerida.StringBuilder
está diseñado para más que agregar!Por supuesto, la verdadera pregunta es si esto es una optimización demasiado lejos. La JVM es muy buena para manejar la creación de múltiples objetos y la posterior recolección de basura, y como todas las preguntas de optimización, mi primera pregunta es si ha medido esto y ha determinado que es un problema.
fuente
¿Qué tal usar el método replaceAll () ?
fuente
str.replaceAll(search1, replace1).replaceAll(search2, replace2).replaceAll(search3, replace3).replaceAll(search4, replace4)
Rythm, un motor de plantillas de Java ahora lanzado con una nueva característica llamada Modo de interpolación de cadenas que le permite hacer algo como:
El caso anterior muestra que puede pasar un argumento a una plantilla por posición. Rythm también le permite pasar argumentos por nombre:
Tenga en cuenta que Rythm es MUY RÁPIDO, alrededor de 2 a 3 veces más rápido que String.format y velocity, ya que compila la plantilla en código de bytes de Java, el rendimiento en tiempo de ejecución está muy cerca de la concatenación con StringBuilder.
Enlaces:
fuente
"%cat% really needs some %beverage%.";
¿no es ese%
token separado un formato predefinido? Su primer punto es aún más divertido, JDK proporciona muchas "capacidades antiguas", algunas de ellas comienzan en los 90, ¿por qué la gente se molesta en usarlas? Sus comentarios y votaciones negativas no tienen ningún sentido realLo siguiente se basa en la respuesta de Todd Owen . Esa solución tiene el problema de que si los reemplazos contienen caracteres que tienen un significado especial en expresiones regulares, puede obtener resultados inesperados. También quería poder hacer opcionalmente una búsqueda que no distinga entre mayúsculas y minúsculas. Esto es lo que se me ocurrió:
Aquí están mis casos de prueba unitarios:
fuente
fuente
Mira esto:
Por ejemplo:
fuente
Resumen: implementación de clase única de la respuesta de Dave, para elegir automáticamente el más eficiente de los dos algoritmos.
Esta es una implementación completa de una sola clase basada en la excelente respuesta anterior de Dave Jarvis . La clase elige automáticamente entre los dos algoritmos suministrados diferentes, para una máxima eficiencia. (Esta respuesta es para personas a las que les gustaría copiar y pegar rápidamente).
Clase ReplaceStrings:
Dependencias de Maven necesarias:
(Agregue estos a su archivo pom si es necesario).
fuente