Concatenação de strings:operador concat() vs operador “+”
-
09-06-2019 - |
Pergunta
Assumindo as strings a e b:
a += b
a = a.concat(b)
Sob o capô, eles são a mesma coisa?
Aqui está o concat descompilado como referência.Eu gostaria de poder descompilar o +
operador também para ver o que isso faz.
public String concat(String s) {
int i = s.length();
if (i == 0) {
return this;
}
else {
char ac[] = new char[count + i];
getChars(0, count, ac, 0);
s.getChars(0, i, ac, count);
return new String(0, count + i, ac);
}
}
Solução
Não, não exatamente.
Em primeiro lugar, há uma ligeira diferença na semântica.Se a
é null
, então a.concat(b)
lança um NullPointerException
mas a+=b
tratará o valor original de a
Como se fosse null
.Além disso, o concat()
método aceita apenas String
valores enquanto o +
operador converterá silenciosamente o argumento em uma String (usando o toString()
método para objetos).Então o concat()
método é mais rigoroso no que aceita.
Para dar uma olhada nos bastidores, escreva uma aula simples com a += b;
public class Concat {
String cat(String a, String b) {
a += b;
return a;
}
}
Agora desmonte com javap -c
(incluído no Sun JDK).Você deverá ver uma listagem incluindo:
java.lang.String cat(java.lang.String, java.lang.String);
Code:
0: new #2; //class java/lang/StringBuilder
3: dup
4: invokespecial #3; //Method java/lang/StringBuilder."<init>":()V
7: aload_1
8: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
11: aload_2
12: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokevirtual #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/ String;
18: astore_1
19: aload_1
20: areturn
Então, a += b
é o equivalente a
a = new StringBuilder()
.append(a)
.append(b)
.toString();
O concat
método deve ser mais rápido.No entanto, com mais cordas o StringBuilder
método vence, pelo menos em termos de desempenho.
O código fonte de String
e StringBuilder
(e sua classe base package-private) está disponível em src.zip do Sun JDK.Você pode ver que está construindo um array de caracteres (redimensionando conforme necessário) e depois jogando-o fora ao criar o array final. String
.Na prática, a alocação de memória é surpreendentemente rápida.
Atualizar: Como observa Pawel Adamski, o desempenho mudou no HotSpot mais recente. javac
ainda produz exatamente o mesmo código, mas o compilador de bytecode trapaceia.O teste simples falha totalmente porque todo o corpo do código é jogado fora.Resumindo System.identityHashCode
(não String.hashCode
) mostra o StringBuffer
o código tem uma ligeira vantagem.Sujeito a alterações quando a próxima atualização for lançada ou se você usar uma JVM diferente.De @lukaseder, uma lista de intrínsecos do HotSpot JVM.
Outras dicas
Niyaz está correto, mas também vale a pena notar que o operador especial + pode ser convertido em algo mais eficiente pelo compilador Java.Java tem uma classe StringBuilder que representa uma String mutável e não segura para thread.Ao realizar várias concatenações de String, o compilador Java converte silenciosamente
String a = b + c + d;
em
String a = new StringBuilder(b).append(c).append(d).toString();
o que para strings grandes é significativamente mais eficiente.Pelo que eu sei, isso não acontece quando você usa o método concat.
No entanto, o método concat é mais eficiente ao concatenar uma String vazia em uma String existente.Neste caso, a JVM não precisa criar um novo objeto String e pode simplesmente retornar o existente.Ver a documentação do concat para confirmar isso.
Portanto, se você está muito preocupado com a eficiência, deve usar o método concat ao concatenar Strings possivelmente vazias e usar + caso contrário.No entanto, a diferença de desempenho deve ser insignificante e você provavelmente não deveria se preocupar com isso.
Fiz um teste semelhante ao @marcio, mas com o seguinte loop:
String c = a;
for (long i = 0; i < 100000L; i++) {
c = c.concat(b); // make sure javac cannot skip the loop
// using c += b for the alternative
}
Só para garantir, eu joguei StringBuilder.append()
também.Cada teste foi executado 10 vezes, com 100 mil repetições para cada corrida.Aqui estão os resultados:
StringBuilder
vence sem dúvida.O resultado do tempo do relógio foi 0 para a maioria das corridas, e a mais longa durou 16ms.a += b
leva cerca de 40.000 ms (40s) para cada execução.concat
requer apenas 10.000 ms (10 s) por execução.
Ainda não descompilei a classe para ver os detalhes internos ou executá-la no profiler, mas suspeito a += b
passa grande parte do tempo criando novos objetos de StringBuilder
e depois convertê-los de volta para String
.
A maioria das respostas aqui são de 2008.Parece que as coisas mudaram ao longo do tempo.Meus últimos benchmarks feitos com JMH mostram que no Java 8 +
é cerca de duas vezes mais rápido que concat
.
Minha referência:
@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State2 {
public String a = "abc";
public String b = "xyz";
}
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State3 {
public String a = "abc";
public String b = "xyz";
public String c = "123";
}
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State4 {
public String a = "abc";
public String b = "xyz";
public String c = "123";
public String d = "!@#";
}
@Benchmark
public void plus_2(State2 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b);
}
@Benchmark
public void plus_3(State3 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b+state.c);
}
@Benchmark
public void plus_4(State4 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b+state.c+state.d);
}
@Benchmark
public void stringbuilder_2(State2 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
}
@Benchmark
public void stringbuilder_3(State3 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
}
@Benchmark
public void stringbuilder_4(State4 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
}
@Benchmark
public void concat_2(State2 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b));
}
@Benchmark
public void concat_3(State3 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b.concat(state.c)));
}
@Benchmark
public void concat_4(State4 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
}
}
Resultados:
Benchmark Mode Cnt Score Error Units
StringConcatenation.concat_2 thrpt 50 24908871.258 ± 1011269.986 ops/s
StringConcatenation.concat_3 thrpt 50 14228193.918 ± 466892.616 ops/s
StringConcatenation.concat_4 thrpt 50 9845069.776 ± 350532.591 ops/s
StringConcatenation.plus_2 thrpt 50 38999662.292 ± 8107397.316 ops/s
StringConcatenation.plus_3 thrpt 50 34985722.222 ± 5442660.250 ops/s
StringConcatenation.plus_4 thrpt 50 31910376.337 ± 2861001.162 ops/s
StringConcatenation.stringbuilder_2 thrpt 50 40472888.230 ± 9011210.632 ops/s
StringConcatenation.stringbuilder_3 thrpt 50 33902151.616 ± 5449026.680 ops/s
StringConcatenation.stringbuilder_4 thrpt 50 29220479.267 ± 3435315.681 ops/s
Tom está correto ao descrever exatamente o que o operador + faz.Isso cria um temporário StringBuilder
, anexa as partes e termina com toString()
.
No entanto, todas as respostas até agora ignoram os efeitos das otimizações de tempo de execução do HotSpot.Especificamente, essas operações temporárias são reconhecidas como um padrão comum e substituídas por código de máquina mais eficiente em tempo de execução.
@marcio:Você criou um micro-referência;com JVMs modernas, esta não é uma forma válida de criar perfil de código.
A razão pela qual a otimização do tempo de execução é importante é que muitas dessas diferenças no código – incluindo até mesmo a criação de objetos – são completamente diferentes quando o HotSpot começa a funcionar.A única maneira de saber com certeza é traçar o perfil do seu código no local.
Finalmente, todos esses métodos são incrivelmente rápidos.Este pode ser um caso de otimização prematura.Se você tem um código que concatena muito strings, a maneira de obter velocidade máxima provavelmente não tem nada a ver com os operadores que você escolhe, mas sim com o algoritmo que você está usando!
Que tal alguns testes simples?Utilizei o código abaixo:
long start = System.currentTimeMillis();
String a = "a";
String b = "b";
for (int i = 0; i < 10000000; i++) { //ten million times
String c = a.concat(b);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
- O
"a + b"
versão executada em 2500ms. - O
a.concat(b)
executado em 1200 ms.
Testado várias vezes.O concat()
a execução da versão levou metade do tempo em média.
Este resultado me surpreendeu porque o concat()
método sempre cria uma nova string (ele retorna um "new String(result)
".É bem sabido que:
String a = new String("a") // more than 20 times slower than String a = "a"
Por que o compilador não foi capaz de otimizar a criação de strings no código "a + b", sabendo que sempre resultava na mesma string?Isso poderia evitar a criação de uma nova string.Se você não acredita na afirmação acima, teste você mesmo.
Basicamente, existem duas diferenças importantes entre + e o concat
método.
Se você estiver usando o concat método, então você só seria capaz de concatenar strings enquanto no caso do + operador, você também pode concatenar a string com qualquer tipo de dados.
Por exemplo:
String s = 10 + "Hello";
Neste caso, a saída deve ser 10Olá.
String s = "I"; String s1 = s.concat("am").concat("good").concat("boy"); System.out.println(s1);
No caso acima você deve fornecer duas strings obrigatórias.
A segunda e principal diferença entre + e concat é aquele:
Caso 1:Suponha que eu concatene as mesmas strings com concat operador desta forma
String s="I"; String s1=s.concat("am").concat("good").concat("boy"); System.out.println(s1);
Neste caso, o número total de objetos criados no pool é 7 assim:
I am good boy Iam Iamgood Iamgoodboy
Caso 2:
Agora vou concatinar as mesmas strings via + operador
String s="I"+"am"+"good"+"boy"; System.out.println(s);
No caso acima, o número total de objetos criados é de apenas 5.
Na verdade, quando concatinamos as strings via + operador então ele mantém uma classe StringBuffer para executar a mesma tarefa da seguinte forma: -
StringBuffer sb = new StringBuffer("I"); sb.append("am"); sb.append("good"); sb.append("boy"); System.out.println(sb);
Desta forma criará apenas cinco objetos.
Então pessoal, essas são as diferenças básicas entre + e a concat método.Aproveitar :)
Para ser mais completo, gostaria de acrescentar que a definição do operador '+' pode ser encontrada no JLS SE8 15.18.1:
Se apenas uma expressão de operando for da string do tipo, a conversão da string (§5.1.11) for realizada no outro operando para produzir uma string no tempo de execução.
O resultado da concatenação da string é uma referência a um objeto String que é a concatenação das duas seqüências de operando.Os caracteres do operando esquerdo precedem os caracteres do operando direito na string recém-criada.
O objeto String é criado recentemente (§12.5), a menos que a expressão seja uma expressão constante (§15.28).
Sobre a implementação o JLS diz o seguinte:
Uma implementação pode optar por executar conversão e concatenação em uma etapa para evitar a criação e descarte um objeto de string intermediário.Para aumentar o desempenho da concatenação repetida de string, um compilador Java pode usar a classe StringBuffer ou uma técnica semelhante para reduzir o número de objetos intermediários de string que são criados pela avaliação de uma expressão.
Para tipos primitivos, uma implementação também pode otimizar a criação de um objeto de wrapper convertendo diretamente de um tipo primitivo em uma string.
Portanto, a julgar por 'um compilador Java pode usar a classe StringBuffer ou uma técnica semelhante para reduzir', diferentes compiladores podem produzir códigos de bytes diferentes.
O + operador pode funcionar entre uma string e um valor de tipo de dados string, char, inteiro, duplo ou flutuante.Ele apenas converte o valor em sua representação de string antes da concatenação.
O operador concat só pode ser feito em e com strings.Ele verifica a compatibilidade do tipo de dados e gera um erro, se não corresponderem.
Exceto isso, o código que você forneceu faz a mesma coisa.
Eu não acho.
a.concat(b)
é implementado em String e acho que a implementação não mudou muito desde as primeiras máquinas Java.O +
a implementação da operação depende da versão Java e do compilador.Atualmente +
é implementado usando StringBuffer
para tornar a operação o mais rápida possível.Talvez no futuro isso mude.Em versões anteriores do java +
a operação em Strings era muito mais lenta, pois produzia resultados intermediários.
eu acho que +=
é implementado usando +
e igualmente otimizado.
Ao usar +, a velocidade diminui conforme o comprimento da string aumenta, mas ao usar concat a velocidade fica mais estável, e a melhor opção é usar a classe StringBuilder que tem velocidade estável para fazer isso.
Eu acho que você pode entender o porquê.Mas a melhor maneira de criar strings longas é usar StringBuilder() e append(), qualquer uma das velocidades será inaceitável.