web-dev-qa-db-fra.com

StringBuilder vs String envisageant de remplacer

Lors de la concaténation de nombreuses chaînes, il m'a été recommandé d'utiliser une variable StringBuilder en tant que telle:

StringBuilder someString = new StringBuilder("abc");
someString.append("def");
someString.append("123");
someString.append("moreStuff");

par opposition à 

String someString = "abc";
someString = someString + "def";
someString = someString + "123";
someString = someString + "moreStuff";

ce qui aboutirait à la création de plusieurs chaînes, par opposition à une.

Maintenant, je dois faire la même chose, mais au lieu d'utiliser la concaténation, j'utilise la méthode replace de String en tant que telle:

String someString = SOME_LARGE_STRING_CONSTANT;
someString = someString.replace("$VARIABLE1", "abc");
someString = someString.replace("$VARIABLE2", "def");
someString = someString.replace("$VARIABLE3", "123");
someString = someString.replace("$VARIABLE4", "moreStuff");

Pour accomplir la même chose en utilisant StringBuilder, je dois le faire, juste pour un remplacement:

someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");

Ma question est donc la suivante: "Est-il préférable d'utiliser String.replace et de créer de nombreuses chaînes supplémentaires, ou d'utiliser StringBuilder quand même, et de disposer de nombreuses lignes longues comme celle ci-dessus?"

30
DanielGibbs

Il est vrai que StringBuilder a tendance à être meilleur que la concaténation ou la modification manuelle de chaînes, puisque StringBuilder est modifiable, alors que String est immuable et que vous devez créer une nouvelle chaîne pour chaque modification.

Notons cependant que le compilateur Java convertira automatiquement un exemple comme celui-ci:

String result = someString + someOtherString + anotherString;

dans quelque chose comme:

String result = new StringBuilder().append(someString).append(someOtherString).append(anotherString).toString();

Cela dit, à moins que vous ne remplaciez un grand nombre de chaînes, choisissez ce qui est le plus lisible et le plus facile à maintenir. Donc, si vous pouvez le garder plus propre en ayant une séquence d'appels 'remplacer', continuez avec la méthode StringBuilder. La différence sera négligeable comparée au stress que vous éviterez de faire face à la triste tragédie des micro-optimisations .

PS

Pour votre exemple de code (qui, comme OscarRyz l'a souligné, ne fonctionnera pas si vous avez plus d'un "$VARIABLE1" dans someString, auquel cas vous devrez utiliser une boucle), vous pouvez mettre en cache le résultat de l'appel indexOf dans:

someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");

Avec

int index = someString.indexOf("$VARIABLE1");    
someString.replace(index, index+10, "abc");

Pas besoin de chercher deux fois dans la chaîne :-)

43
Zach L

Devine quoi? Si vous utilisez Java 1.5+, la concaténation fonctionne de la même manière avec les littéraux de chaîne.

  String h = "hello" + "world";

et

  String i = new StringBuilder().append("hello").append("world").toString();

Sont identiques. 

Donc, le compilateur a déjà fait le travail pour vous. 

Bien sûr, mieux serait: 

 String j = "hellworld"; // ;) 

Quant à la seconde, oui, c'est préférable, mais ne devrait pas être si difficile, avec le pouvoir de "rechercher et remplacer" et un peu de regex 

Par exemple, vous pouvez définir une méthode comme celle de cet exemple:

  public static void replace( String target, String replacement, 
                              StringBuilder builder ) { 
    int indexOfTarget = -1;
    while( ( indexOfTarget = builder.indexOf( target ) ) >= 0 ) { 
      builder.replace( indexOfTarget, indexOfTarget + target.length() , replacement );
    }
  }

Et votre code ressemble actuellement à ceci:

someString = someString.replace("VARIABLE1", "abc");
someString = someString.replace("VARIABLE2", "xyz");

Tout ce que vous avez à faire est de saisir un éditeur de texte avec un déclencheur comme ceci: vi recherchez et remplacez:

%s/^.*("\(.*\)".\s"\(.*\)");/replace("\1","\2",builder);

Cette lecture: "prend n'importe quoi entre parenthèses et qui ressemble à un littéral de chaîne, et le met dans cette autre chaîne" .

Et votre code ressemblera à ceci:

someString = someString.replace("VARIABLE1", "abc");
someString = someString.replace("VARIABLE2", "xyz");

pour ça:

replace( "VARIABLE1", "abc", builder );
replace( "VARIABLE2", "xyz", builder );

En un rien de temps.

Voici une démo de travail:

class DoReplace { 
  public static void main( String ... args ) {
    StringBuilder builder = new StringBuilder(
       "LONG CONSTANT WITH VARIABLE1 and  VARIABLE2 and VARIABLE1 and VARIABLE2");
    replace( "VARIABLE1", "abc", builder );
    replace( "VARIABLE2", "xyz", builder );
    System.out.println( builder.toString() );
  }
  public static void replace( String target, String replacement, 
                              StringBuilder builder ) { 
    int indexOfTarget = -1;
    while( ( indexOfTarget = builder.indexOf( target ) ) > 0 ) { 
      builder.replace( indexOfTarget, indexOfTarget + target.length() , 
                       replacement );
    }
  }
}
7
OscarRyz

Je dirais qu’il faut utiliser StringBuilder mais écrire simplement un wrapper qui facilite la lisibilité du code, donc sa maintenance, tout en maintenant son efficacité. = D

import Java.lang.StringBuilder;
public class MyStringBuilder
{
    StringBuilder sb;

    public MyStringBuilder() 
    {
       sb = new StringBuilder();
    }

    public void replace(String oldStr, String newStr)
    {
            int start = -1;
            while ((start = sb.indexOf(oldStr)) > -1)
            {
                    int end = start + oldStr.length(); 
                    sb.replace(start, end, newStr);
            }
    }

    public void append(String str)
    {
       sb.append(str);
    }

    public String toString()
    {
          return sb.toString();
    }

    //.... other exposed methods

    public static void main(String[] args)
    {
          MyStringBuilder sb = new MyStringBuilder();
          sb.append("old old olD dudely dowrite == pwn");
          sb.replace("old", "new");
          System.out.println(sb);
    }
}

SORTIE:

new new olD dudely dowrite == pwn

Maintenant, vous pouvez simplement utiliser la nouvelle version qui est une doublure facile 

MyStringBuilder mySB = new MyStringBuilder();
mySB.append("old dudley dowrite == pwn");
mySB.replace("old", "new"):
3
Feisty Mango

Au lieu d'avoir de longues lignes comme celle-là, vous pouvez simplement écrire une méthode pour remplacer des parties de chaînes de StringBuilder, comme suit:

public StringBuilder replace(StringBuilder someString, String replaceWhat, String replaceWith) {
   return someString.replace(someString.indexOf(replaceWhat), someString.indexOf(replaceWhat)+replaceWhat.length(), replaceWith);
}
1
Valera

Si votre chaîne est vraiment volumineuse et que les performances vous préoccupent, nous vous recommandons d'écrire une classe qui prend le texte de votre modèle et une liste de variables, puis lit la chaîne source caractère par caractère et génère le résultat à l'aide de StringBuilder. Cela devrait être le plus efficace en termes d'utilisation du processeur et de la mémoire. De plus, si vous lisez ce modèle de texte depuis un fichier, je ne le chargerai pas tout en mémoire à l’avance. Traitez-le en morceaux au fur et à mesure que vous le lisez dans le fichier.

Si vous cherchez simplement un moyen agréable de créer une chaîne qui n’est pas aussi efficace que StringBuilder mais plus efficace que l’ajout répétitif de chaînes, vous pouvez utiliser String.format () . Cela fonctionne comme sprintf () en C. MessageFormat.format () est une option aussi, mais il utilise StringBuffer.

Il y a une autre question connexe ici: Insertion d'une chaîne Java dans une autre chaîne sans concaténation?

0
Sarel Botha

Tous les codes des gars ont un bogue .try yourReplace("x","xy"). Il va boucle infiniment

0
Jam Hong

Peut être la classe String utilise en interne 

indice de

méthode pour trouver l’index de l’ancienne chaîne et la remplacer par une nouvelle chaîne.

De plus, StringBuilder n’est pas threadsafe et s’exécute donc beaucoup plus rapidement.

0
Ankit

s'il est vrai que les microoptimisations peuvent être problématiques, cela dépend parfois du contexte. Par exemple, si votre remplacement s'exécute dans une boucle comportant 10000 itérations, vous verrez une différence de performances significative par rapport aux optimisations "inutiles".

dans la plupart des cas cependant, il est préférable de privilégier la lisibilité

0
Austin_Anderson

Jam Hong a raison - les solutions ci-dessus contiennent toutes le potentiel d’une boucle infinie. Je pense que la leçon à retenir est que les microoptimisations peuvent souvent causer toutes sortes de problèmes horribles et ne vous épargnent pas beaucoup. Quoi qu’il en soit, voici une solution qui ne sera pas bouclée à l’infini.

private static void replaceAll(StringBuilder builder, String replaceWhat, String replaceWith){
    int occuranceIndex = builder.indexOf(replaceWhat);
    int lastReplace = -1;
    while(occuranceIndex >= 0){
        if(occuranceIndex >= lastReplace){
            builder.replace(occuranceIndex, occuranceIndex+replaceWhat.length(), replaceWith);
            lastReplace = occuranceIndex + replaceWith.length();
            occuranceIndex = builder.indexOf(replaceWhat);
        }else{
            break;
        }
    }
}
0
ChrisR