Existe uma diferença considerável na execução de concatenação/construção de Strings por diferentes métodos:
- Uso de StringBuilder e seus "append"s
- Uso de String.concat, como em x.concat(':').concat(y)
- Uso de leftShift, como em x << ":" << y
- Uso de List com lista.join('')
- Uso de GString, como em "$x:$y"
- Uso de soma de String's, como em x + ':' + y
Segue abaixo um "teste benchmark" que pode ser executado no GroovyConsole para visualizar a diferença de tempo.
Em minha máquina as principais saídas produzidas (para a média de 3 execuções de 1 milhão de repetições cada) foram:
| Teste 2 Strings | Média | Execuções Individuais
| StringBuilder | 298 ms | 334 ms / 283 ms / 279 ms
| String.concat | 271 ms | 281 ms / 266 ms / 268 ms
| leftShift | 327 ms | 340 ms / 323 ms / 320 ms
| List+join | 412 ms | 418 ms / 411 ms / 408 ms
| GString | 338 ms | 355 ms / 330 ms / 329 ms
| Soma String | 531 ms | 549 ms / 521 ms / 525 ms
| Teste 4 Strings | Média | Execuções Individuais
| StringBuilder | 409 ms | 511 ms / 362 ms / 354 ms
| String.concat | 422 ms | 447 ms / 416 ms / 404 ms
| leftShift | 566 ms | 603 ms / 549 ms / 547 ms
| List+join | 540 ms | 564 ms / 527 ms / 531 ms
| GString | 655 ms | 676 ms / 644 ms / 647 ms
| Soma String | 1158 ms | 1175 ms / 1151 ms / 1150 ms
| Teste 10 Strings | Média | Execuções Individuais
| StringBuilder | 578 ms | 580 ms / 576 ms / 578 ms
| String.concat | 961 ms | 943 ms / 942 ms / 998 ms
| leftShift | 1457 ms | 1473 ms / 1468 ms / 1431 ms
| List+join | 1561 ms | 1535 ms / 1603 ms / 1546 ms
| GString | 1902 ms | 1896 ms / 1898 ms / 1913 ms
| Soma String | 3495 ms | 3483 ms / 3501 ms / 3501 ms
Estes resultados comprovam a eficiência do StringBuilder e a estabilidade de seu tempo desde a concatenação de apenas 2 strings até 10 strings, imagine em casos onde se concatenam milhares de strings em uma lógica complexa? O tempo perdido pode se tornar expressivo. Principalmente ao compararmos com a Soma String que em geral costuma demorar o dobro do tempo quando comparado à qualquer outra alternativa, mas ainda é o método mais utilizado pelos programadores.
int qtdMaxStrings = 10
List<String> lista10Strings = (1..qtdMaxStrings).collect { it.toString().padLeft(7, '0') }
List<List<String>> testesStrings = []
(2..qtdMaxStrings).each { int qtd ->
testesStrings.add(lista10Strings.subList(0, qtd))
}
testesStrings.each { List<String> testeStrings ->
println "| **Teste ${testeStrings.size()} Strings** | **Média** | **Execuções Individuais**"
executaTeste('StringBuilder', testeStrings) {
StringBuilder stringBuilder = new StringBuilder(testeStrings.head())
for (String umaString : testeStrings.tail()) {
stringBuilder.append(':')
stringBuilder.append(umaString)
}
String textoUnido = stringBuilder.toString()
return textoUnido
}
executaTeste('String.concat', testeStrings) {
String textoUnido = testeStrings.head()
for (String umaString : testeStrings.tail()) {
textoUnido = textoUnido.concat(':').concat(umaString)
}
return textoUnido
}
executaTeste('leftShift', testeStrings) {
String textoUnido = testeStrings.head()
for (String umaString : testeStrings.tail()) {
textoUnido = textoUnido << ":" << umaString
}
return textoUnido
}
executaTeste('List<String>+join', testeStrings) {
String textoUnido = testeStrings.join(':')
return textoUnido
}
executaTeste('GString', testeStrings) {
String textoUnido = testeStrings.head()
for (String umaString : testeStrings.tail()) {
textoUnido = "${textoUnido}:${umaString}"
}
return textoUnido
}
executaTeste('Soma String', testeStrings) {
String textoUnido = testeStrings.head()
for (String umaString : testeStrings.tail()) {
textoUnido = textoUnido + ':' + umaString
}
return textoUnido
}
println ''
}
void executaTeste(String identificadorTeste, List<String> testeStrings, Closure execucao) {
String textoUnidoEsperado = testeStrings.join(':')
List<Long> duracoesMs = []
int qtdAnalises = 3
qtdAnalises.times {
assert execucao.call() == textoUnidoEsperado
long timeInicioNano = System.nanoTime()
int loopAnaliseDeDesempenho = 1_000_000
loopAnaliseDeDesempenho.times {
execucao.call()
}
long timeFimNano = System.nanoTime()
long duracaoMs = (long) (timeFimNano - timeInicioNano) / 1e6
duracoesMs.add(duracaoMs)
}
long media = duracoesMs.sum() / qtdAnalises
println "| ${identificadorTeste.padRight(19, ' ')} | ${media.toString().padLeft(6, ' ')} ms | ${duracoesMs.collect { "${it.toString().padLeft(5, ' ')} ms" }.join(' / ')}"
}
println 'FIM'