Pergunta

Dado o 2 toString() implementações abaixo, qual é a preferida:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

ou

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

?

Mais importante ainda, dado que temos apenas 3 propriedades, isso pode não fazer diferença, mas em que ponto você mudaria de + concat para StringBuilder?

Foi útil?

Solução

A versão 1 é preferível porque é mais curta e O compilador de fato o transformará na versão 2 - Não há diferença de desempenho.

Mais importante, considerando que temos apenas três propriedades, pode não fazer a diferença, mas em que ponto você muda de concat para o construtor?

No ponto em que você está concatenando em um loop - é geralmente quando o compilador não pode substituir StringBuilder por si próprio.

Outras dicas

A chave é se você está escrevendo uma única concatenação em um só lugar ou acumulando -a ao longo do tempo.

Para o exemplo que você deu, não faz sentido usar explicitamente o StringBuilder. (Veja o código compilado para o seu primeiro caso.)

Mas se você estiver construindo uma corda, por exemplo, dentro de um loop, use StringBuilder.

Para esclarecer, assumindo que a Hundearray contém milhares de strings, código como este:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

é muito tempo e de memória em comparação com:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

Eu prefiro:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );

... Porque é curto e legível.

Eu poderia não Otimize isso para velocidade, a menos que você o use dentro de um loop com uma contagem de repetição muito alta e mediram a diferença de desempenho.

Concordo que, se você precisar gerar muitos parâmetros, esse formulário poderá ficar confuso (como diz um dos comentários). Nesse caso, eu mudaria para uma forma mais legível (talvez usando TostringBuilder de Apache -Commons - retirado da resposta de Matt b) e ignore o desempenho novamente.

Na maioria dos casos, você não verá uma diferença real entre as duas abordagens, mas é fácil construir o pior cenário como este:

public class Main
{
    public static void main(String[] args)
    {
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    }

    private static void fast()
    {
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    }
}

A saída é:

slow elapsed 11741 ms
fast elapsed 7 ms

O problema é que, para += anexar a uma string, reconstrói uma nova string, por isso custa algo linear ao comprimento de suas strings (soma de ambos).

Então - para sua pergunta:

A segunda abordagem seria mais rápida, mas é menos legível e mais difícil de manter. Como eu disse, no seu caso específico, você provavelmente não veria a diferença.

Eu também tive um confronto com meu chefe com o fato de usar o Append ou +. Como eles estão usando o Append (ainda não consigo descobrir, pois eles dizem que toda vez que um novo objeto é criado). Por isso, pensei em fazer um pouco de R & D., embora eu ame Michael Borgwardt explicar, mas só queria mostrar uma explicação se alguém realmente precisará saber no futuro.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}

e a desmontagem da classe acima sai como

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------

A partir dos dois códigos acima, você pode ver que Michael está certo. Em cada caso, apenas um objeto SB é criado.

Desde Java 1.5, concatenação simples de uma linha com "+" e stringbuilder.append () gera exatamente o mesmo bytecode.

Portanto, para a legibilidade do código, use "+".

2 exceções:

  • ambiente multithread: stringbuffer
  • Concatenação em Loops: StringBuilder/StringBuffer

Usando a versão mais recente do Java (1.8) a desmontagem (javap -c) mostra a otimização introduzida pelo compilador. + também sb.append() irá gerar código muito semelhante. No entanto, valerá a pena inspecionar o comportamento se estivermos usando + em um loop.

Adicionando strings usando + em um loop

Java:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}

Bytecode :(for trecho de loop)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12

Adicionando strings usando stringbuilder.append

Java:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}

Bytecdoe :(for trecho de loop)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2

Há um pouco de diferença gritante no entanto. No primeiro caso, onde + foi usado, novo StringBuilder é criado para cada uma para iteração de loop e o resultado gerado é armazenado ao fazer um toString() Ligue (29 a 41). Então você está gerando seqüências intermediárias que você realmente não precisa enquanto usa + operador in for ciclo.

Em Java 9, a versão 1 deve ser mais rápida porque é convertida para invokedynamic ligar. Mais detalhes podem ser encontrados em JEP-280:

A idéia é substituir toda a dança do StringBuilder Appender por uma simples chamada invocedynâmica para java.lang.invoke.stringConcatFactory, que aceitará os valores na necessidade de concatenação.

Apache Commons-Lang tem um TostringBuilder classe que é super fácil de usar. Ele faz um bom trabalho ao lidar com a Append-Logic e a formatação de como você deseja que sua tostragem pareça.

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

Retornará a saída que se parece com.blah.YourClass@abc1321f[a=whatever, b=foo].

Ou de uma forma mais condensada usando o encadeamento:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}

Ou se você deseja usar a reflexão para incluir todos os campos da classe:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

Você também pode personalizar o estilo do toque, se quiser.

Por razões de desempenho, o uso de += (String concatenação) é desencorajado. A razão é: java String é imutável, toda vez que uma nova concatenação é feita um novo String é criado (o novo tem uma impressão digital diferente do mais antigo no pool de cordas ). Criar novas cordas pressiona o GC e diminui o programa: a criação de objetos é cara.

Abaixo, o código deve torná -lo mais prático e claro ao mesmo tempo.

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

Os resultados para uma execução são relatados abaixo.

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

Não considerando os resultados de 1 concatenação (o JIT ainda não estava fazendo seu trabalho), mesmo por 10 concatenações, a penalidade de desempenho é relevante; Para milhares de concatenações, a diferença é enorme.

Lições aprendidas com este experimento muito rápido (facilmente reproduzível com o código acima): nunca use o += Para concatenar as cordas juntas, mesmo em casos muito básicos em que algumas concatenações são necessárias (como dito, criar novas cordas é caro de qualquer maneira e pressiona o GC).

Torne o método da tostragem o mais legível possível!

A única exceção para isso no meu livro é se você pode provar para mim que consome recursos significativos :) (sim, isso significa perfil)

Observe também que o compilador Java 5 gera código mais rápido que a abordagem "StringBuffer" manuscrita usada nas versões anteriores do Java. Se você usa "+", isso e os aprimoramentos futuros surgirem gratuitamente.

Parece haver algum debate se o uso do StringBuilder ainda é necessário com os compiladores atuais. Então eu pensei que vou dar meus 2 centavos de experiência.

eu tenho um JDBC Conjunto de resultados de 10k Records (sim, eu preciso de todos eles em um lote.) O uso do operador + leva cerca de 5 minutos na minha máquina com Java 1.8. Usando stringBuilder.append("") leva menos de um segundo para a mesma consulta.

Portanto, a diferença é enorme. Dentro de um loop StringBuilder é muito mais rápido.

Veja o exemplo abaixo:

//java8
static void main(String[] args) {
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID()+"---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}

static void case2() {
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str+=UUID.randomUUID()+"---";
        saveTime(savedTimes, startTime);
    }        
    System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}

static void case3() {
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID()+"---");
        saveTime(savedTimes, startTime);
    }        
    System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");

}

static void saveTime(List<Long> executionTimes, long startTime) {
        executionTimes.add(System.currentTimeMillis()-startTime);
        if(executionTimes.size()%CALC_AVG_EVERY == 0) {
            out.println("average time for "+executionTimes.size()+" concatenations: "+
                    NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(()->0))+
                    " ms avg");
            executionTimes.clear();
        }
}

Resultado:

Tempo médio para 10000 concatenações: 0,096 ms AVG
Tempo médio para 10000 concatenações: 0,185 ms AVG
Tempo médio para 10000 concatenações: 0,327 ms AVG
Tempo médio para 10000 concatenações: 0,501 ms AVG
Tempo médio para 10000 concatenações: 0,656 ms AVG
Creada String of Comprimento: 1950000 em 17745 MS
Tempo médio para 10000 concatenações: 0,21 ms AVG
Tempo médio para 10000 concatenações: 0,652 ms AVG
Tempo médio para 10000 concatenações: 1,129 MS AVG
Tempo médio para 10000 concatenações: 1,727 MS AVG
Tempo médio para 10000 concatenações: 2,302 MS AVG
Creada String of Comprimento: 1950000 em 60279 MS
Tempo médio para 10000 concatenações: 0,002 ms AVG
Tempo médio para 10000 concatenações: 0,002 ms AVG
Tempo médio para 10000 concatenações: 0,002 ms AVG
Tempo médio para 10000 concatenações: 0,002 ms AVG
Tempo médio para 10000 concatenações: 0,002 ms AVG
Creada String of Comprimento: 1950000 em 100 ms

À medida que o comprimento da string aumenta, o mesmo ocorre com o tempo de concatenação.
É aí que o StringBuilder é definitivamente necessário.
Como você vê, a concatenação: UUID.randomUUID()+"---", realmente não afeta o tempo.

PS: Eu não acho Quando usar o StringBuilder em Java é realmente uma duplicata disso.
Esta pergunta fala sobre toString() O que na maioria das vezes não realiza concatenações de cordas enormes.

A concatenação de String em termos de desempenho usando '+' é mais cara porque é necessário fazer uma cópia totalmente nova de String, já que Strings são imutáveis ​​​​em Java.Isto desempenha um papel particular se a concatenação for muito frequente, por exemplo:dentro de um loop.A seguir está o que minha IDEIA sugere quando tento fazer tal coisa:

enter image description here

Regras gerais:

  • Dentro de uma atribuição de string única, usar a concatenação de strings é adequado.
  • Se você estiver fazendo um loop para criar um grande bloco de dados de caracteres, escolha StringBuffer.
  • Usar += em uma String sempre será menos eficiente do que usar um StringBuffer, por isso deve soar sinais de alerta - mas em certos casos a otimização obtida será insignificante em comparação com os problemas de legibilidade, então use o bom senso.

Aqui está um bom blog do Jon Skeet em torno deste tópico.

Posso ressaltar que, se você vai iterar sobre uma coleção e usar o StringBuilder, convém conferir Apache Commons Lang e Stringutils.join () (em sabores diferentes)?

Independentemente do desempenho, ele economizará que você terá que criar stringbuilders e loops para o que parece ser o milionésimo Tempo.

Comparei quatro abordagens diferentes para comparar o desempenho. Eu exatamente não sei o que acontece com o GC, mas o importante para mim é o tempo. O compilador é um fator importante aqui.

concatWithPlusOperator = 8
concatWithBuilder = 130
concatWithConcat = 127
concatStringFormat = 3737
concatWithBuilder2 = 46


public class StringConcatenationBenchmark {

private static final int MAX_LOOP_COUNT = 1000000;

public static void main(String[] args) {

    int loopCount = 0;
    long t1 = System.currentTimeMillis();
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithPlusOperator();
        loopCount++;
    }
    long t2 = System.currentTimeMillis();
    System.out.println("concatWithPlusOperator = " + (t2 - t1));

    long t3 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder();
        loopCount++;
    }
    long t4 = System.currentTimeMillis();
    System.out.println("concatWithBuilder = " + (t4 - t3));

    long t5 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithConcat();
        loopCount++;
    }
    long t6 = System.currentTimeMillis();
    System.out.println("concatWithConcat = " + (t6 - t5));

    long t7 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatStringFormat();
        loopCount++;
    }
    long t8 = System.currentTimeMillis();
    System.out.println("concatStringFormat = " + (t8 - t7));

    long t9 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder2();
        loopCount++;
    }
    long t10 = System.currentTimeMillis();
    System.out.println("concatWithBuilder2 = " + (t10 - t9));
}

private static void concatStringFormat() {
    String s = String.format("%s %s %s %s ", "String", "String", "String", "String");
}

private static void concatWithConcat() {
    String s = "String".concat("String").concat("String").concat("String");
}

private static void concatWithBuilder() {
    StringBuilder builder=new StringBuilder("String");
    builder.append("String").append("String").append("String");
    String s = builder.toString();
}

private static void concatWithBuilder2() {
    String s = new StringBuilder("String").append("String").append("String").append("String").toString();
}

private static void concatWithPlusOperator() {
    String s = "String" + "String" + "String" + "String";
}
}

Aqui está o que eu verifiquei em java8

  • Usando concatenação da string
  • Usando StringBuilder

    long time1 = System.currentTimeMillis();
    usingStringConcatenation(100000);
    System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms");
    
    time1 = System.currentTimeMillis();
    usingStringBuilder(100000);
    System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms");
    
    
    private static void usingStringBuilder(int n)
    {
        StringBuilder str = new StringBuilder();
        for(int i=0;i<n;i++)
            str.append("myBigString");    
    }
    
    private static void usingStringConcatenation(int n)
    {
        String str = "";
        for(int i=0;i<n;i++)
            str+="myBigString";
    }
    

É realmente um pesadelo se você estiver usando concatenação de string para um grande número de strings.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

Eu acho que devemos ir com a abordagem de apêndice de stringbuilder. A razão é

  1. O String Concatenate criará um novo objeto String cada vez (como a string é um objeto imutável), para criar 3 objetos.

  2. Com o String Builder, apenas um objeto será criado [StringBuilder é mutável] e a string adicional é anexada a ele.

Para cordas simples como essa, prefiro usar

"string".concat("string").concat("string");

Para que eu diria que o método preferido de construção de uma string está usando o StringBuilder, String#concat (), depois o operador sobrecarregado +. O StringBuilder é um aumento significativo de desempenho ao trabalhar grandes strings, assim como o uso do operador + é uma grande diminuição no desempenho (diminuição exponencialmente grande à medida que o tamanho da corda aumenta). O único problema com o uso de .Concat () é que ele pode lançar o NullPointerExceptions.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top