109 votes

Est-il préférable de réutiliser un StringBuilder dans une boucle?

J'ai une question sur les performances concernant l'utilisation de StringBuilder. Dans une très longue boucle, je manipule un StringBuilder et le passe à une autre méthode comme celle-ci:

 for (loop condition) {
    StringBuilder sb = new StringBuilder();
    sb.append("some string");
    . . .
    sb.append(anotherString);
    . . .
    passToMethod(sb.toString());
}
 

L'instanciation de StringBuilder à chaque cycle de boucle est-elle une bonne solution? Et appeler une suppression est-il préférable, comme ci-dessous?

 StringBuilder sb = new StringBuilder();
for (loop condition) {
    sb.delete(0, sb.length);
    sb.append("some string");
    . . .
    sb.append(anotherString);
    . . .
    passToMethod(sb.toString());
}
 

71voto

Epaga Points 12717

Le second est environ 25% plus rapide dans ma mini-référence.

 public class ScratchPad {

    static String a;

    public static void main( String[] args ) throws Exception {
        long time = System.currentTimeMillis();
        for( int i = 0; i < 10000000; i++ ) {
            StringBuilder sb = new StringBuilder();
            sb.append( "someString" );
            sb.append( "someString2"+i );
            sb.append( "someStrin4g"+i );
            sb.append( "someStr5ing"+i );
            sb.append( "someSt7ring"+i );
            a = sb.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for( int i = 0; i < 10000000; i++ ) {
            sb.delete( 0, sb.length() );
            sb.append( "someString" );
            sb.append( "someString2"+i );
            sb.append( "someStrin4g"+i );
            sb.append( "someStr5ing"+i );
            sb.append( "someSt7ring"+i );
            a = sb.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
    }
}
 

Résultats:

 25265
17969
 

Notez qu'il s'agit de JRE 1.6.0_07.


Sur la base des idées de Jon Skeet dans l'édition, voici la version 2. Même résultat. Alors arrêtez de voter pour que les gens disent que # 1 est plus rapide. :)

 public class ScratchPad {

    static String a;

    public static void main( String[] args ) throws Exception {
        long time = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for( int i = 0; i < 10000000; i++ ) {
            sb.delete( 0, sb.length() );
            sb.append( "someString" );
            sb.append( "someString2" );
            sb.append( "someStrin4g" );
            sb.append( "someStr5ing" );
            sb.append( "someSt7ring" );
            a = sb.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        for( int i = 0; i < 10000000; i++ ) {
            StringBuilder sb2 = new StringBuilder();
            sb2.append( "someString" );
            sb2.append( "someString2" );
            sb2.append( "someStrin4g" );
            sb2.append( "someStr5ing" );
            sb2.append( "someSt7ring" );
            a = sb2.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
    }
}
 

Résultats:

 5016
7516
 

26voto

Dave Jarvis Points 12598

Encore plus rapide:

public class ScratchPad {

    private static String a;

    public static void main( String[] args ) throws Exception {
        long time = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder( 128 );

        for( int i = 0; i < 10000000; i++ ) {
            // Resetting the string is faster than creating a new object.
            // Since this is a critical loop, every instruction counts.
            //
            sb.setLength( 0 );
            sb.append( "someString" );
            sb.append( "someString2" );
            sb.append( "someStrin4g" );
            sb.append( "someStr5ing" );
            sb.append( "someSt7ring" );
            setA( sb.toString() );
        }

        System.out.println( System.currentTimeMillis()-time );
    }

    private static void setA( String aString ) {
        a = aString;
    }
}

Dans la philosophie de l'écriture solide code, les rouages de la méthode qui devrait être caché les objets qui utilisent la méthode. Ainsi, il ne fait pas de différence à partir de la perspective du système si vous redeclare le StringBuilder dans la boucle ou à l'extérieur de la boucle. Depuis déclarant en dehors de la boucle est plus rapide, et il ne permet pas de rendre le code plus compliqué à lire, puis de réutiliser l'objet plutôt que de le retrouver.

Même si le code a été plus compliqué, et on savait que l'instanciation d'objets a été le goulot d'étranglement, en commentaire.

Notez que "someString"+i est le véritable goulot d'étranglement comme le + de l'opérateur est cher; utiliser append(...) à la place.

Trois pistes avec cette réponse:

$ java ScratchPad
1567
$ java ScratchPad
1569
$ java ScratchPad
1570

Trois pistes avec les autres de répondre:

$ java ScratchPad2
1663
2231
$ java ScratchPad2
1656
2233
$ java ScratchPad2
1658
2242

Bien que non significatif, le réglage de l' StringBuilders'initiale de la taille de la mémoire tampon donnera un petit gain.

25voto

Peter Points 4666

Dans la philosophie de l'écriture de code solide, il est toujours préférable de placer votre StringBuilder dans votre boucle. De cette façon, il ne sort pas du code auquel il est destiné.

Deuxièmement, la plus grande amélioration de StringBuilder vient de lui donner une taille initiale pour l’éviter de devenir plus gros pendant que la boucle s’exécute

 for (loop condition) {
  StringBuilder sb = new StringBuilder(4096);
}
 

12voto

Jon Skeet Points 692016

Ok, je comprends maintenant ce qui se passe, et c'est logique.

J'étais sous l'impression qu' toString juste après le sous-jacent char[] dans une Chaîne constructeur qui n'a pas en prendre une copie. Une copie sera ensuite mis sur le côté "écriture" de l'opération (par exemple, delete). Je crois que cela a été le cas avec StringBuffer dans certaines version précédente. (Ce n'est pas maintenant.) Mais le no - toString passe uniquement le tableau (et l'index et la longueur) à l'expert - String constructeur qui prend une copie.

Ainsi, dans la "réutilisation de l' StringBuilder" cas nous véritablement créer une copie des données par chaîne, à l'aide du même char tableau dans la mémoire tampon tout le temps. Évidemment, la création d'un nouveau StringBuilder chaque fois crée un nouveau sous-jacente de la mémoire tampon, puis la mémoire tampon est copié (un peu inutilement, dans notre cas particulier, mais pour des raisons de sécurité) lors de la création d'une nouvelle chaîne.

Tout cela conduit à la deuxième version certainement être plus efficace - mais en même temps, j'aurais quand même dire que c'est plus laide du code.

(EDIT: j'ai supprimé mon autre réponse, comme inutile et inutile.)

9voto

Jack Leow Points 11081

Depuis, je ne pense pas qu'il souligné, cependant, parce que les optimisations construit dans le Soleil compilateur Java, ce qui crée automatiquement StringBuilders (StringBuffers pré-J2SE 5.0) quand il voit des concaténations de Chaîne, le premier exemple de la question est équivalente à:

for (loop condition) {
  String s = "some string";
  . . .
  s += anotherString;
  . . .
  passToMethod(s);
}

Qui est plus lisible, de l'OMI, la meilleure approche. Vos tentatives pour optimiser peut entraîner des gains dans certains plate-forme, mais potentiellement des pertes autres.

Mais si vous êtes vraiment à courir dans des problèmes de performance, c'est sûr que, d'optimiser loin. Je commencerais avec spécifiant explicitement la taille de la mémoire tampon de la StringBuilder si, par Jon Skeet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X