É melhor usar o String.format sobre a concatenação de string em Java?

273

Existe uma diferença perceptível entre o uso String.formate a concatenação de String em Java?

Costumo usar, String.formatmas ocasionalmente escorregar e usar uma concatenação. Fiquei me perguntando se um era melhor que o outro.

A meu ver, String.formatlhe dá mais poder em "formatar" a string; e concatenação significa que você não precisa se preocupar em adicionar acidentalmente% s ou perder um.

String.format também é mais curto.

Qual é mais legível depende de como sua cabeça funciona.

Omar Kooheji
fonte
Acho que podemos usar o MessageFormat.format. Consulte a resposta stackoverflow.com/a/56377112/1491414 para obter mais informações.
Ganesa Vijayakumar

Respostas:

242

Eu sugeriria que é uma prática melhor usar String.format(). O principal motivo é que String.format()pode ser mais facilmente localizado com o texto carregado de arquivos de recursos, enquanto a concatenação não pode ser localizada sem produzir um novo executável com código diferente para cada idioma.

Se você planeja que seu aplicativo seja localizável, também deve adquirir o hábito de especificar posições de argumento para seus tokens de formato:

"Hello %1$s the time is %2$t"

Em seguida, isso pode ser localizado e os tokens de nome e hora são trocados sem a necessidade de uma recompilação do executável para levar em conta as diferentes ordens. Com posições de argumento, você também pode reutilizar o mesmo argumento sem passá-lo para a função duas vezes:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)
workmad3
fonte
1
Você pode me apontar para alguma documentação que fala sobre como trabalhar com posições / ordem de argumentos em Java (isto é, como referenciar argumentos por sua posição)? Obrigado.
MarkVgti
13
Antes
Aksel
174

Sobre o desempenho:

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }
  long end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;

  start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = String.format("Hi %s; Hi to you %s",i, + i*2);
  }
  end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
}

Os resultados do tempo são os seguintes:

  • Concatenação = 265 milissegundos
  • Formato = 4141 milissegundos

Portanto, a concatenação é muito mais rápida que String.format.

Icaro
fonte
15
São todas más práticas. Use StringBuilder.
Amir Raminfar
8
O StringBuilder está fora do escopo aqui (a questão OP era sobre a comparação do String.format sobre a concatenação de strings), mas você executou dados sobre o String Builder?
Icaro
108
@AmirRaminar: o compilador converte "+" em chamadas para StringBuilder automaticamente.
Martin Schröder
40
@ MartinSchröder: Se você executar, javap -c StringTest.classverá que o compilador converte "+" para StringBuilder automaticamente somente se você não estiver em um loop. Se a concatenação for feita em uma única linha, é o mesmo que usar '+', mas se você usar myString += "morechars";ou myString += anotherString;em várias linhas, perceberá que mais de um StringBuilder pode ser criado, portanto, usar "+" nem sempre é tão eficiente como StringBuilder.
Ccpizza
7
@Joffrey: o que eu quis dizer foi que para loops +não é convertido em, StringBuilder.append()mas new StringBuilder()ocorre um em cada iteração.
ccpizza
39

Como há discussões sobre desempenho, achei que acrescentaria uma comparação que incluía StringBuilder. Na verdade, é mais rápido que o concat e, naturalmente, a opção String.format.

Para tornar isso uma espécie de comparação de maçãs com maçãs, instanciamos um novo StringBuilder no loop, e não no exterior (isso é realmente mais rápido do que fazer apenas uma instancia, provavelmente devido à sobrecarga de reatribuir espaço para o loop acrescentado no final de um construtor).

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    log.info("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    log.info("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("; Hi to you ").append(i * 2);
    }

    end = System.currentTimeMillis();

    log.info("String Builder = " + ((end - start)) + " millisecond");
  • 2012-01-11 16: 30: 46,058 INFO [TestMain] - Formato = 1416 milissegundos
  • 2012-01-11 16: 30: 46,190 INFO [TestMain] - Concatenação = 134 milissegundos
  • 2012-01-11 16: 30: 46,313 INFO [TestMain] - Construtor de cadeias = 117 milissegundos
TechTrip
fonte
21
O teste StringBuilder não chama toString (), portanto, não é uma comparação justa. Eu suspeito que você achará que está dentro do erro de medição do desempenho da concatenação se você corrigir esse bug.
precisa
15
Nos testes de concatenação e formato, você solicitou a String. O teste StringBuilder, para ser justo, precisa de uma etapa final que transforma o conteúdo do StringBuilder em uma String. Você faz isso ligando bldString.toString(). Espero que isso explique?
Jamey afiada
4
Jamey Sharp está exatamente certo. A chamada de bldString.toString () é praticamente a mesma, se não mais lenta que a concatenação de strings.
Akos Cz
3
String s = bldString.toString(); Os horários foram com concatenação e stringbuilder quase a par com o outro: Format = 1520 millisecond, Concatenation = 167 millisecond, String Builder = 173 millisecond eu corri-los em um loop e em média cada um para obter uma boa reputação: (otimização pré-jvm, vai tentar um loop 10000 + quando eu chegar em tempo)
TechTrip
3
Como vocês sabem se o código é executado? As variáveis ​​nunca são lidas ou usadas; você não pode ter certeza de que o JIT não remove esse código em primeiro lugar.
Alobodzk
37

Um problema .formaté que você perde a segurança do tipo estático. Você pode ter muito poucos argumentos para o seu formato e tipos incorretos para os especificadores de formato - ambos levando a um IllegalFormatException tempo de execução , portanto, você pode acabar com o código de log que interrompe a produção.

Por outro lado, os argumentos para +podem ser testados pelo compilador.

O histórico de segurança de(na qual a formatfunção é modelada) é longa e assustadora.

Martin Schröder
fonte
16
apenas para o registro, IDEs modernas (por exemplo, IntelliJ) auxiliar na argumentos contar e tipo de correspondência
Ron Klein
2
Bom argumento sobre a compilação, eu recomendo que você faça essas verificações via FindBugs (que podem ser executados no IDE ou Maven durante a compilação), observe que isso também verificará a formatação em todos os seus logs! Isso funciona independentemente do usuário IDE
Christophe Roussy
20

Qual é mais legível depende de como sua cabeça funciona.

Você tem sua resposta aqui.

É uma questão de gosto pessoal.

A concatenação de strings é marginalmente mais rápida, suponho, mas isso deve ser insignificante.

Thilo
fonte
3
Concordo. Pensar nas diferenças de desempenho aqui é principalmente uma otimização prematura - no improvável evento em que a criação de perfil mostre que há um problema aqui, então se preocupe.
297 Jonik
3
É realmente apenas uma questão de gosto pessoal, se o projeto for pequeno e nunca pretender ser internacionalizado em nenhum sentido significativo. Caso contrário, o String.format vence a concatenação de todas as formas.
28490 workmad3 #
4
Discordo. Não importa o tamanho do projeto, você dificilmente localizará todas as strings que já foram construídas nele. Em outras palavras, depende da situação (para que são usadas as strings).
297 Jonik
Não consigo imaginar como alguém consideraria 'String.format ("% s% s", a, b)' mais legível que 'a + b', e dada a diferença de ordem de magnitude na velocidade que a resposta me parece clara (em situações que não exigirão localização, como depuração ou a maioria das instruções de log).
BobDoolittle
16

Aqui está um teste com vários tamanhos de amostra em milissegundos.

public class Time {

public static String sysFile = "/sys/class/camera/rear/rear_flash";
public static String cmdString = "echo %s > " + sysFile;

public static void main(String[] args) {

  int i = 1;
  for(int run=1; run <= 12; run++){
      for(int test =1; test <= 2 ; test++){
        System.out.println(
                String.format("\nTEST: %s, RUN: %s, Iterations: %s",run,test,i));
        test(run, i);
      }
      System.out.println("\n____________________________");
      i = i*3;
  }
}

public static void test(int run, int iterations){

      long start = System.nanoTime();
      for( int i=0;i<iterations; i++){
          String s = "echo " + i + " > "+ sysFile;
      }
      long t = System.nanoTime() - start;   
      String r = String.format("  %-13s =%10d %s", "Concatenation",t,"nanosecond");
      System.out.println(r) ;


     start = System.nanoTime();       
     for( int i=0;i<iterations; i++){
         String s =  String.format(cmdString, i);
     }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "Format",t,"nanosecond");
     System.out.println(r);

      start = System.nanoTime();          
      for( int i=0;i<iterations; i++){
          StringBuilder b = new StringBuilder("echo ");
          b.append(i).append(" > ").append(sysFile);
          String s = b.toString();
      }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "StringBuilder",t,"nanosecond");
     System.out.println(r);
}

}

TEST: 1, RUN: 1, Iterations: 1
  Concatenation =     14911 nanosecond
  Format        =     45026 nanosecond
  StringBuilder =      3509 nanosecond

TEST: 1, RUN: 2, Iterations: 1
  Concatenation =      3509 nanosecond
  Format        =     38594 nanosecond
  StringBuilder =      3509 nanosecond

____________________________

TEST: 2, RUN: 1, Iterations: 3
  Concatenation =      8479 nanosecond
  Format        =     94438 nanosecond
  StringBuilder =      5263 nanosecond

TEST: 2, RUN: 2, Iterations: 3
  Concatenation =      4970 nanosecond
  Format        =     92976 nanosecond
  StringBuilder =      5848 nanosecond

____________________________

TEST: 3, RUN: 1, Iterations: 9
  Concatenation =     11403 nanosecond
  Format        =    287115 nanosecond
  StringBuilder =     14326 nanosecond

TEST: 3, RUN: 2, Iterations: 9
  Concatenation =     12280 nanosecond
  Format        =    209051 nanosecond
  StringBuilder =     11818 nanosecond

____________________________

TEST: 5, RUN: 1, Iterations: 81
  Concatenation =     54383 nanosecond
  Format        =   1503113 nanosecond
  StringBuilder =     40056 nanosecond

TEST: 5, RUN: 2, Iterations: 81
  Concatenation =     44149 nanosecond
  Format        =   1264241 nanosecond
  StringBuilder =     34208 nanosecond

____________________________

TEST: 6, RUN: 1, Iterations: 243
  Concatenation =     76018 nanosecond
  Format        =   3210891 nanosecond
  StringBuilder =     76603 nanosecond

TEST: 6, RUN: 2, Iterations: 243
  Concatenation =     91222 nanosecond
  Format        =   2716773 nanosecond
  StringBuilder =     73972 nanosecond

____________________________

TEST: 8, RUN: 1, Iterations: 2187
  Concatenation =    527450 nanosecond
  Format        =  10291108 nanosecond
  StringBuilder =    885027 nanosecond

TEST: 8, RUN: 2, Iterations: 2187
  Concatenation =    526865 nanosecond
  Format        =   6294307 nanosecond
  StringBuilder =    591773 nanosecond

____________________________

TEST: 10, RUN: 1, Iterations: 19683
  Concatenation =   4592961 nanosecond
  Format        =  60114307 nanosecond
  StringBuilder =   2129387 nanosecond

TEST: 10, RUN: 2, Iterations: 19683
  Concatenation =   1850166 nanosecond
  Format        =  35940524 nanosecond
  StringBuilder =   1885544 nanosecond

  ____________________________

TEST: 12, RUN: 1, Iterations: 177147
  Concatenation =  26847286 nanosecond
  Format        = 126332877 nanosecond
  StringBuilder =  17578914 nanosecond

TEST: 12, RUN: 2, Iterations: 177147
  Concatenation =  24405056 nanosecond
  Format        = 129707207 nanosecond
  StringBuilder =  12253840 nanosecond
Derek Ziemba
fonte
1
StringBuilder é absolutamente o método mais rápido quando você anexa caracteres em um loop, por exemplo, quando deseja criar uma string com mil 1s, adicionando-as uma a uma. Aqui está mais informações: pellegrino.link/2015/08/22/…
Carlos Hoyos
Eu gosto de como você sempre usa String.format para saída: D, então há uma vantagem. e, para ser sincero, se não estamos falando de milhões de iterações, prefiro string.format para facilitar a leitura, pois seu código mostra a vantagem óbvia!
mohamnag 21/09
9

Aqui está o mesmo teste acima, com a modificação da chamada do método toString () no StringBuilder . Os resultados abaixo mostram que a abordagem StringBuilder é um pouco mais lenta que a concatenação de String usando o operador + .

arquivo: StringTest.java

class StringTest {

  public static void main(String[] args) {

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("Hi to you ").append(i * 2).toString();
    }

    end = System.currentTimeMillis();

    System.out.println("String Builder = " + ((end - start)) + " millisecond");

  }
}

Comandos do shell: (compile e execute o StringTest 5 vezes)

> javac StringTest.java
> sh -c "for i in \$(seq 1 5); do echo \"Run \${i}\"; java StringTest; done"

Resultados :

Run 1
Format = 1290 millisecond
Concatenation = 115 millisecond
String Builder = 130 millisecond

Run 2
Format = 1265 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

Run 3
Format = 1303 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 4
Format = 1297 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 5
Format = 1270 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond
Akos Cz
fonte
6

String.format()é mais do que concatenar seqüências de caracteres. Por exemplo, você pode exibir números em uma localidade específica usando String.format().

No entanto, se você não se importa com a localização, não há diferença funcional. Talvez um seja mais rápido que o outro, mas na maioria dos casos será insignificante.

Fortega
fonte
4

Geralmente, a concatenação de strings deve ser preferida String.format. Este último tem duas principais desvantagens:

  1. Ele não codifica a sequência a ser construída de maneira local.
  2. O processo de construção é codificado em uma sequência.

No ponto 1, quero dizer que não é possível entender o que uma String.format()chamada está fazendo em uma única passagem seqüencial. Um é forçado a ir e voltar entre a string de formato e os argumentos, enquanto conta a posição dos argumentos. Para concatenações curtas, isso não é um problema. Nesses casos, no entanto, a concatenação de strings é menos detalhada.

Por ponto 2, quero dizer que a parte importante do processo de construção está codificada na cadeia de formato (usando uma DSL). O uso de strings para representar código tem muitas desvantagens. Não é inerentemente seguro para o tipo e complica o destaque de sintaxe, a análise de código, a otimização etc.

Obviamente, ao usar ferramentas ou estruturas externas à linguagem Java, novos fatores podem entrar em jogo.

Stephane Bersier
fonte
2

Não fiz benchmarks específicos, mas acho que a concatenação pode ser mais rápida. String.format () cria um novo Formatador que, por sua vez, cria um novo StringBuilder (com um tamanho de apenas 16 caracteres). Essa é uma quantidade considerável de sobrecarga, especialmente se você estiver formatando uma string mais longa e o StringBuilder continuar tendo que redimensionar.

No entanto, a concatenação é menos útil e mais difícil de ler. Como sempre, vale a pena fazer uma referência no seu código para ver qual é o melhor. As diferenças podem ser desprezíveis no aplicativo do servidor depois que seus pacotes de recursos, localidades etc. são carregados na memória e o código é JITted.

Talvez como prática recomendada, seria uma boa idéia criar seu próprio Formatador com um StringBuilder (Appendable) e um Localidade de tamanho adequado e usá-lo se você tiver muita formatação para fazer.

AngerClown
fonte
2

Pode haver uma diferença perceptível.

String.format é bastante complexo e usa uma expressão regular por baixo; portanto, não use o hábito de usá-lo em qualquer lugar, mas apenas onde você precisar.

StringBuilder seria uma ordem de magnitude mais rápida (como alguém aqui já apontou).

Pawel Zieminski
fonte
1

Acho que podemos seguir em frente MessageFormat.format, pois deve ser bom tanto na legibilidade quanto no desempenho.

Usei o mesmo programa usado por Icaro na resposta acima e aprimorei-o com o código anexado MessageFormatpara explicar os números de desempenho.

  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = "Hi " + i + "; Hi to you " + i * 2;
    }
    long end = System.currentTimeMillis();
    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = String.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = MessageFormat.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("MessageFormat = " + ((end - start)) + " millisecond");
  }

Concatenação = 69 milissegundos

Formato = 1435 milissegundos

MessageFormat = 200 milissegundos

ATUALIZAÇÕES:

De acordo com o Relatório SonarLint, as seqüências de caracteres no formato Printf devem ser usadas corretamente (squid: S3457)

Como as printf-styleseqüências de formato são interpretadas em tempo de execução, em vez de validadas pelo compilador, elas podem conter erros que resultam na criação de seqüências de caracteres erradas. Esta regra estaticamente valida a correlação de printf-styleseqüências de formato para os seus argumentos ao chamar os métodos formato (...) de java.util.Formatter, java.lang.String, java.io.PrintStream, MessageFormat, e java.io.PrintWriteras classes e os printf(...)métodos de java.io.PrintStreamou java.io.PrintWriterclasses.

Substituo o estilo printf pelos colchetes e obtive resultados interessantes como abaixo.

Concatenação = 69 milissegundos
Formato = 1107 milissegundos
Formato: colchetes = 416 milissegundos
MessageFormat = 215 milissegundos
MessageFormat: colchetes = 2517 milissegundos

Conclusão:
Como destaquei acima, usar String.format com colchetes deve ser uma boa opção para obter benefícios de boa legibilidade e também desempenho.

Ganesa Vijayakumar
fonte
0

Você não pode comparar Concatenação de String e String.Format pelo programa acima.

Você pode tentar isso também trocando a posição de usar sua String.Format e Concatenation no seu bloco de código como abaixo

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = String.format( "Hi %s; Hi to you %s",i, + i*2);
  }

  long end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
  start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }

  end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;
}

Você ficará surpreso ao ver que o formato funciona mais rápido aqui. Isso ocorre porque os objetos iniciais criados podem não ser liberados e pode haver um problema com a alocação de memória e, portanto, o desempenho.

DotNetUser
fonte
3
você já tentou seu código? Concatenação é sempre tempo dez mais rápidos
Icaro
e quanto aos milis necessários para executar este "System.currentTimeMillis ()": P.
rehan
0

Demora um pouco para se acostumar com o String.Format, mas na maioria dos casos vale a pena. No mundo da NRA (nunca repita nada), é extremamente útil manter suas mensagens tokenizadas (log ou usuário) em uma biblioteca Constant (prefiro o que equivale a uma classe estática) e chamá-las conforme necessário com String.Format, independentemente de você estão localizando ou não. Tentar usar essa biblioteca com um método de concatenação é mais difícil de ler, solucionar problemas, revisar e gerenciar com qualquer abordagem que exija concatenação. A substituição é uma opção, mas duvido que tenha desempenho. Depois de anos de uso, meu maior problema com o String.Format é que a duração da chamada é inconvenientemente longa quando eu a passo para outra função (como Msg), mas é fácil usar uma função personalizada para servir como um alias .

user665056
fonte