web-dev-qa-db-fra.com

String.Replace () contre StringBuilder.Replace ()

J'ai une chaîne dans laquelle je dois remplacer les marqueurs par des valeurs d'un dictionnaire. Il doit être aussi efficace que possible. Faire une boucle avec un string.replace va juste consommer de la mémoire (les chaînes sont immuables, rappelez-vous). StringBuilder.Replace () serait-il mieux car il a été conçu pour fonctionner avec des manipulations de chaînes?

J'espérais éviter les frais de RegEx, mais si cela va être plus efficace, qu'il en soit ainsi.

Remarque: je ne me soucie pas de la complexité du code, seulement de sa vitesse d'exécution et de la mémoire qu'il consomme.

Statistiques moyennes: 255-1024 caractères, 15-30 touches dans le dictionnaire.

73
Dustin Davis

Utilisation de RedGate Profiler à l'aide du code suivant

class Program
    {
        static string data = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
        static Dictionary<string, string> values;

        static void Main(string[] args)
        {
            Console.WriteLine("Data length: " + data.Length);
            values = new Dictionary<string, string>()
            {
                { "ab", "aa" },
                { "jk", "jj" },
                { "lm", "ll" },
                { "yz", "zz" },
                { "ef", "ff" },
                { "st", "uu" },
                { "op", "pp" },
                { "x", "y" }
            };

            StringReplace(data);
            StringBuilderReplace1(data);
            StringBuilderReplace2(new StringBuilder(data, data.Length * 2));

            Console.ReadKey();
        }

        private static void StringReplace(string data)
        {
            foreach(string k in values.Keys)
            {
                data = data.Replace(k, values[k]);
            }
        }

        private static void StringBuilderReplace1(string data)
        {
            StringBuilder sb = new StringBuilder(data, data.Length * 2);
            foreach (string k in values.Keys)
            {
                sb.Replace(k, values[k]);
            }
        }

        private static void StringBuilderReplace2(StringBuilder data)
        {
            foreach (string k in values.Keys)
            {
                data.Replace(k, values[k]);
            }
        }
    }
  • String.Replace = 5.843ms
  • StringBuilder.Replace # 1 = 4.059ms
  • Stringbuilder.Replace # 2 = 0.461ms

Longueur de la chaîne = 1456

stringbuilder # 1 crée le stringbuilder dans la méthode tandis que # 2 ne le fait pas, donc la différence de performance finira par être la même chose, car vous déplacez simplement ce travail hors de la méthode. Si vous commencez avec un constructeur de chaînes au lieu d'une chaîne, alors # 2 pourrait être la voie à suivre.

En ce qui concerne la mémoire, en utilisant le profileur RedGateMemory, il n'y a rien à craindre jusqu'à ce que vous entriez dans de NOMBREUSES opérations de remplacement dans lesquelles le constructeur de chaînes va gagner globalement.

67
Dustin Davis

Cela peut être utile:

http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance. aspx

La réponse courte semble être que String.Replace est plus rapide, bien qu'il puisse avoir un impact plus important sur votre surcharge mémoire/récupération de place.

9
RMD

Oui, StringBuilder vous donnera à la fois un gain de vitesse et de mémoire (essentiellement parce qu'il ne créera pas d'instance de chaîne à chaque fois que vous en ferez une manipulation - StringBuilder fonctionne toujours avec le même objet). Voici un lien MSDN avec quelques détails.

6
Andrei

Est-ce que stringbuilder.replace serait mieux [que String.Replace]

Oui, beaucoup mieux. Et si vous pouvez estimer une limite supérieure pour la nouvelle chaîne (on dirait que vous le pouvez), elle sera probablement assez rapide.

Lorsque vous le créez comme:

  var sb = new StringBuilder(inputString, pessimisticEstimate);

alors le StringBuilder n'aura pas à réallouer son tampon.

5
Henk Holterman

La conversion des données d'une chaîne en StringBuilder et inversement prendra un certain temps. Si l'on n'effectue qu'une seule opération de remplacement, cette fois peut ne pas être récupérée par les améliorations d'efficacité inhérentes à StringBuilder. D'un autre côté, si l'on convertit une chaîne en StringBuilder, puis y effectue de nombreuses opérations de remplacement et la reconvertit à la fin, l'approche StringBuilder est susceptible d'être plus rapide.

1
supercat

Cela dépendra beaucoup du nombre de marqueurs présents dans une chaîne donnée en moyenne.

Les performances de recherche d'une clé sont probablement similaires entre StringBuilder et String, mais StringBuilder gagnera si vous devez remplacer plusieurs marqueurs dans une seule chaîne.

Si vous vous attendez à un ou deux marqueurs par chaîne en moyenne et que votre dictionnaire est petit, je choisirais String.Replace.

S'il existe de nombreux marqueurs, vous souhaiterez peut-être définir une syntaxe personnalisée pour identifier les marqueurs, par exemple enfermant entre accolades avec une règle d'échappement appropriée pour une accolade littérale. Vous pouvez ensuite implémenter un algorithme d'analyse qui parcourt une fois les caractères de la chaîne, en reconnaissant et en remplaçant chaque marqueur qu'il trouve. Ou utilisez une expression régulière.

1
Joe

Plutôt que d'exécuter 15-30 remplacer des opérations sur la chaîne entière, il pourrait être plus efficace d'utiliser quelque chose comme une structure de données trie pour contenir votre dictionnaire. Ensuite, vous pouvez parcourir votre chaîne d'entrée une fois pour effectuer toutes vos recherches/remplacements.

1
Matt Bridges

Le problème avec la réponse de @DustinDavis est qu'elle fonctionne récursivement sur la même chaîne. Sauf si vous prévoyez de faire un type de manipulation de va-et-vient, vous devriez vraiment avoir des objets séparés pour chaque cas de manipulation dans ce type de test.

J'ai décidé de créer mon propre test car j'ai trouvé des réponses contradictoires sur tout le Web et je voulais en être absolument sûr. Le programme sur lequel je travaille traite beaucoup de texte (des fichiers avec des dizaines de milliers de lignes dans certains cas).

Voici donc une méthode rapide que vous pouvez copier et coller et voir par vous-même, qui est plus rapide. Vous devrez peut-être créer votre propre fichier texte à tester, mais vous pouvez facilement copier et coller du texte de n'importe où et créer un fichier suffisamment grand pour vous-même:

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;

void StringReplace_vs_StringBuilderReplace( string file, string Word1, string Word2 )
{
    using( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) )
    using( StreamReader streamReader = new StreamReader( fileStream, Encoding.UTF8 ) )
    {
        string text = streamReader.ReadToEnd(),
               @string = text;
        StringBuilder @StringBuilder = new StringBuilder( text );
        int iterations = 10000;

        Stopwatch watch1 = new Stopwatch.StartNew();
        for( int i = 0; i < iterations; i++ )
            if( i % 2 == 0 ) @string = @string.Replace( Word1, Word2 );
            else @string = @string.Replace( Word2, Word1 );
        watch1.Stop();
        double stringMilliseconds = watch1.ElapsedMilliseconds;

        Stopwatch watch2 = new Stopwatch.StartNew();
        for( int i = 0; i < iterations; i++ )
            if( i % 2 == 0 ) @StringBuilder = @StringBuilder .Replace( Word1, Word2 );
            else @StringBuilder = @StringBuilder .Replace( Word2, Word1 );
        watch2.Stop();
        double StringBuilderMilliseconds = watch1.ElapsedMilliseconds;

        MessageBox.Show( string.Format( "string.Replace: {0}\nStringBuilder.Replace: {1}",
                                        stringMilliseconds, StringBuilderMilliseconds ) );
    }
}

J'ai obtenu cette chaîne.Replace () était plus rapide d'environ 20% à chaque fois que je remplaçais des mots de 8 à 10 lettres. Essayez-le par vous-même si vous voulez vos propres preuves empiriques.

1
Meloviz

Mes deux cents ici, je viens d'écrire quelques lignes de code pour tester le fonctionnement de chaque méthode et, comme prévu, le résultat est "cela dépend".

Pour les chaînes plus longues, Regex semble mieux fonctionner, pour les chaînes plus courtes, String.Replace c'est. Je peux voir que l'utilisation de StringBuilder.Replace n'est pas très utile, et s'il est mal utilisé, il pourrait être mortel dans la perspective GC (j'ai essayé de partager une instance de StringBuilder).

Vérifiez mon StringReplaceTests GitHub repo .

1
Zdeněk