Preciso substituir muitas substring diferentes em uma string da maneira mais eficiente. Existe outra maneira diferente da forma de força bruta de substituir cada campo usando string.replace?
97
Se a string em que você está operando for muito longa, ou se estiver operando em muitas strings, pode valer a pena usar um java.util.regex.Matcher (isso requer tempo inicial para compilar, então não será eficiente se a sua entrada for muito pequena ou se o padrão de pesquisa mudar com frequência).
Abaixo está um exemplo completo, baseado em uma lista de tokens tirada de um mapa. (Usa StringUtils do 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());
Uma vez que a expressão regular é compilada, verificar a string de entrada é geralmente muito rápido (embora se sua expressão regular for complexa ou envolver retrocesso, você ainda precisará fazer um benchmark para confirmar isso!)
"%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Algoritmo
Uma das maneiras mais eficientes de substituir strings correspondentes (sem expressões regulares) é usar o algoritmo Aho-Corasick com um Trie de desempenho (pronuncia-se "tentativa"), algoritmo de hash rápido e implementação de coleções eficiente .
Código Simples
Uma solução simples aproveita o Apache da
StringUtils.replaceEach
seguinte maneira:Isso fica mais lento em textos grandes.
Código Rápido
A implementação de Bor do algoritmo Aho-Corasick introduz um pouco mais de complexidade que se torna um detalhe de implementação usando uma fachada com a mesma assinatura de método:
Benchmarks
Para os benchmarks, o buffer foi criado usando randomNumeric da seguinte forma:
Onde
MATCHES_DIVISOR
dita o número de variáveis a serem injetadas:O próprio código de referência ( JMH parecia exagero):
1.000.000: 1.000
Um micro-benchmark simples com 1.000.000 de caracteres e 1.000 strings colocadas aleatoriamente para substituir.
Sem resposta.
10.000: 1.000
Usando 10.000 caracteres e 1.000 strings correspondentes para substituir:
A divisão se fecha.
1.000: 10
Usando 1.000 caracteres e 10 strings correspondentes para substituir:
Para cordas curtas, a sobrecarga de configurar Aho-Corasick eclipsa a abordagem de força bruta por
StringUtils.replaceEach
.Uma abordagem híbrida com base no comprimento do texto é possível, para obter o melhor de ambas as implementações.
Implementações
Considere comparar outras implementações para texto com mais de 1 MB, incluindo:
Papéis
Artigos e informações relacionadas ao algoritmo:
fonte
Isso funcionou para mim:
Exemplo:
Resultado: maçã-banana-frui-
fonte
Se você for alterar uma String muitas vezes, geralmente é mais eficiente usar um StringBuilder (mas meça seu desempenho para descobrir) :
Cada vez que você substitui uma String, um novo objeto String é criado, porque as Strings são imutáveis. StringBuilder é mutável, ou seja, pode ser alterado o quanto você quiser.
fonte
StringBuilder
executará a substituição com mais eficiência, já que seu buffer de matriz de caracteres pode ser especificado para um comprimento necessário.StringBuilder
foi projetado para mais do que apenas anexar!Claro que a verdadeira questão é se esta é uma otimização longe demais? A JVM é muito boa em lidar com a criação de vários objetos e a coleta de lixo subsequente e, como todas as questões de otimização, minha primeira pergunta é se você mediu isso e determinou que é um problema.
fonte
Que tal usar o método replaceAll () ?
fonte
str.replaceAll(search1, replace1).replaceAll(search2, replace2).replaceAll(search3, replace3).replaceAll(search4, replace4)
Rythm, um mecanismo de modelo java agora lançado com um novo recurso chamado modo de interpolação de String que permite fazer algo como:
O caso acima mostra que você pode passar argumentos para o modelo por posição. Rythm também permite que você passe argumentos por nome:
Nota Rythm é MUITO RÁPIDO, cerca de 2 a 3 vezes mais rápido que String.format e velocity, porque compila o modelo em código de bytes java, o desempenho do tempo de execução é muito próximo da concatentação com StringBuilder.
Links:
fonte
"%cat% really needs some %beverage%.";
esse%
token separado não é um formato predefinido? Seu primeiro ponto é ainda mais engraçado, o JDK oferece muitos "recursos antigos", alguns deles começam na década de 90, por que as pessoas se incomodam em usá-los? Seus comentários e votos negativos não fazem nenhum sentidoO que segue é baseado na resposta de Todd Owen . Essa solução tem o problema de que, se as substituições contiverem caracteres com significado especial em expressões regulares, você poderá obter resultados inesperados. Eu também queria poder opcionalmente fazer uma pesquisa que não diferencia maiúsculas de minúsculas. Aqui está o que eu descobri:
Aqui estão meus casos de teste de unidade:
fonte
fonte
Verifique isto:
Por exemplo:
fonte
Resumo: Implementação de classe única da resposta de Dave, para escolher automaticamente o mais eficiente dos dois algoritmos.
Esta é uma implementação completa de classe única baseada na excelente resposta acima de Dave Jarvis . A classe escolhe automaticamente entre os dois algoritmos diferentes fornecidos, para máxima eficiência. (Esta resposta é para pessoas que desejam apenas copiar e colar rapidamente.)
Classe ReplaceStrings:
Dependências necessárias do Maven:
(Adicione-os ao seu arquivo pom, se necessário.)
fonte