Найефективніший спосіб об'єднання рядків?


286

Який найефективніший спосіб об'єднати рядки?


9
Я хочу викласти тут чітке попередження про те, що прийнята відповідь суттєво неповна, оскільки вона не обговорює всіх відповідних випадків.
usr

@usr Дійсно ... більш детальну інформацію про StringBuilderвипадки використання можна знайти тут .
Тамір Веред

Мій новий улюблений на C # 6 - $ "Постійний текст тут {foo} та {bar}" ... це як String.Formatна стероїдах. Котрий, на розумну ефективність, на одному лайнері крихітно повільніше, ніж +і String.Concat, але набагато кращий за ці, хоча і повільніше, ніж StringBuilderпри декількох дзвінках. Практично кажучи, відмінності в продуктивності такі, що, якби мені довелося вибрати лише один спосіб об'єднання, я вибрав би строкові інтерполяції, використовуючи $... Якщо двома способами, то додати StringBuilderдо моєї панелі інструментів. За допомогою цих двох способів ви налаштовані.
u8it

String.JoinВідповідь нижче не робить +справедливість і, практично кажучи, поганий шлях для конкатенації рядків, але це дивно висока продуктивність мудрого. Відповідь, чому цікава. String.Concatі String.Joinможе діяти на масиви, але String.Joinнасправді швидше. Мабуть, String.Joinдосить складний і більш оптимізований, ніж String.Concat, почасти тому, що він працює аналогічно тому, StringBuilderщо спочатку обчислює довжину рядка, а потім будує рядок, використовуючи ці знання, використовуючи UnSafeCharBuffer.
u8it

Добре, так це швидко, але String.Joinтакож вимагає побудова масиву , який здається ресурси неефективного права? ... Виявляється, що +і String.Concatконструкт масивів для їх складових частин в будь-якому випадку. Отже, вручну створювати масив і подавати його String.Joinпорівняно швидше ... однак, StringBuilderвсе-таки випереджає String.Joinпрактично кожен практичний спосіб, хоча $лише трохи повільніше і набагато швидше на довгих рядках ... не кажучи вже про те, що використовувати це незручно і некрасиво, String.Joinякщо у вас є щоб створити масив для нього на місці.
u8it

Відповіді:


155

StringBuilder.Append()Метод набагато краще , ніж з допомогою +оператора. Але я виявив, що при виконанні 1000 конкатенацій або менше, String.Join()це навіть ефективніше, ніж StringBuilder.

StringBuilder sb = new StringBuilder();
sb.Append(someString);

Єдина проблема String.Joinполягає в тому, що вам потрібно з'єднати рядки із загальним роздільником.

Редагувати: як зазначав @ryanversaw , ви можете зробити роздільник string.Empty.

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });

11
StringBuilderмає величезну порівнянну вартість запуску, вона ефективна лише при використанні з дуже великими струнами або дуже великою кількістю конкатенацій. Дізнатися для будь-якої ситуації не тривіально. Якщо продуктивність не викликає труднощів, профілювання - це ваш друг (перевірте ANTS).
Авель

32
Це не вірно для однорядного конкатенації. Скажімо, ви робите myString = "foo" + var1 + "bar" + var2 + "привіт" + var3 + "world", компілятор автоматично перетворює це на виклик string.concat, який є настільки ж ефективним, наскільки він отримує. Ця відповідь невірна, є багато кращих відповідей на вибір
csauve

2
Для тривіального змісту рядків використовуйте те, що найбільше читається. рядок a = b + c + d; майже завжди буде швидше, ніж робити це з StringBuilder, але різниця, як правило, не має значення. Використовуйте StringBuilder (або інший варіант на ваш вибір), коли неодноразово додаєте до одного і того ж рядка (наприклад, створення звіту) або при роботі з великими рядками.
Swanny

5
Чому ти не згадав string.Concat?
Венемо

272

Ріко Маріані , гуру .NET Performance, мав статтю на цю тему. Це не так просто, як можна було б підозрювати. Основна порада така:

Якщо ваш візерунок виглядає так:

x = f1(...) + f2(...) + f3(...) + f4(...)

це один конфат і це zippy, StringBuilder, ймовірно, не допоможе.

Якщо ваш візерунок виглядає так:

if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)

тоді ви, мабуть, хочете StringBuilder.

Ще одна стаття на підтвердження цього твердження походить від Еріка Ліпперта, де він детально описує оптимізацію, виконану на +конкатенаціях однієї лінії .


1
Що з String.Format ()?
IronSlug

86

Є 6 типів рядкових конкатенацій:

  1. Використання +символу плюс ( ).
  2. Використання string.Concat().
  3. Використання string.Join().
  4. Використання string.Format().
  5. Використання string.Append().
  6. Використання StringBuilder.

В експерименті було доведено, що string.Concat()це найкращий спосіб підійти, якщо слів менше 1000 (приблизно) і якщо слів більше 1000, то їх StringBuilderслід використовувати.

Для отримання додаткової інформації перегляньте цей сайт .

string.Join () vs string.Concat ()

Метод string.Concat тут еквівалентний виклику string.Join методу з порожнім роздільником. Додавання порожнього рядка відбувається швидко, але не робити це ще швидше, тому метод string.Concat тут був би кращим.


4
Якщо читати було доведено string.Concat () або + - це найкращий спосіб. Так, я можу отримати це зі статті, але це врятує мене одним клацанням. Отже, + і concat компілюються в один і той же код.
brumScouse

Я використовував цю основу, щоб спробувати зробити метод мін більш ефективним, де мені потрібно було лише об'єднати рівно 3 струни. Я виявив, що +це насправді на 3 мілісекунди швидше string.Concat(), хоча я не string.Concat()вивчав кількість рядків, необхідних до перекрутів +.
Gnemlock

59

З Chinh Do - StringBuilder не завжди швидший :

Емпіричні правила

  • При об'єднанні трьох значень динамічного рядка використовуйте традиційне об'єднання рядків.

  • При об'єднанні більше трьох значень динамічного рядка використовуйте StringBuilder.

  • Створюючи великий рядок з декількох @літеральних рядків, використовуйте або літеральний рядок, або оператор inline +.

Більшу частину часу StringBuilder- це ваша найкраща ставка, але є випадки, як показано на цій посаді, що вам слід принаймні продумати кожну ситуацію.


8
afaik @ вимикає лише обробку послідовностей. msdn.microsoft.com/en-us/library/362314fe.aspx домовитись
абатищев

12

Якщо ви працюєте в циклі, StringBuilderймовірно, це шлях; це економить накладні витрати на створення нових рядків регулярно. У коді, який запускається лише один раз, String.Concatмабуть, добре.

Однак Ріко Маріані (гуру з оптимізації .NET) склав вікторину, в якій наприкінці заявив, що в більшості випадків він рекомендує String.Format.


Я роками рекомендую використовувати string.format над string + string людям, з якими працював. Я думаю, що переваги читабельності є додатковою перевагою, ніж вигода від продуктивності.
Скотт Лоуренс

1
Це фактично правильна відповідь. В даний час прийнята відповідь для StringBuilder невірна, оскільки вона не згадує додавання однорядних рядків, для яких string.concat або + швидше. Маловідомий факт полягає в тому, що компілятор фактично переводить + в "string.concat". Крім того , для петель або кілька concats лінії я використовую для користувача вбудований строковий конструктор , який додає тільки тоді , коли .ToString називається - подолання невизначеною завдання буфера , що StringBuilder має
csauve

2
string.Format - це не найшвидший спосіб за будь-яких обставин. Я не знаю, як придумати випадок, коли це випереджає.
usr

@usr - зауважте, що Ріко прямо не каже, що це найшвидше , тільки що це його рекомендація: "Хоча це найгірше виконання, і ми це знали набагато заздалегідь, обидва ваші архітектори CLR Performance погоджуються з тим, що [string.Format] повинен - це вибір за замовчуванням. У випадку, коли це стає проблемою перф, ця проблема легко вирішується лише скромними локальними змінами. Зазвичай ви просто заробляєте гроші на приємному ремонті. "
Адам V

@AdamV питання про найшвидший спосіб. Я не погоджуюсь з тим, що це вибір за замовчуванням, хоча не з перф-причин. Це може бути незграбний синтаксис. Resharper може конвертувати вперед і назад за бажанням.
usr

10

Ось найшвидший метод, який я розвинув протягом десятиліття для свого масштабного додатка NLP. У мене є варіанти для IEnumerable<T>та інших типів вводу, з роздільниками різних типів і без них ( Char, String), але тут я показую простий випадок об'єднання всіх рядків масиву в одну рядок, без роздільника. Остання версія тут розроблена та перевірена на C # 7 та .NET 4.7 .

Є два клавіші для підвищення продуктивності; Перший - попередньо обчислити потрібний загальний розмір. Цей крок є тривіальним, коли вхід - це масив, як показано тут. Для обробки IEnumerable<T>замість цього варто спочатку зібрати рядки у тимчасовий масив для обчислення загальної суми (масив потрібно уникати виклику ToString()більше одного разу за елемент, оскільки технічно, враховуючи можливість побічних ефектів, це може змінити очікувану семантику операції 'string join').

Далі, враховуючи загальний розмір розподілу підсумкового рядка, найбільший приріст продуктивності досягається за рахунок побудови результативного рядка на місці . Для цього потрібна (можливо, суперечлива) техніка тимчасового припинення незмінності нового, Stringякий спочатку виділяється повним нулями. Але будь-яка така суперечка осторонь ...

... Зауважте , що це єдиний в масі конкатенації рішення на цій сторінці повністю уникає додатковий раунд виділення і копіювання з боку Stringконструктора.

Повний код:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

Слід зазначити, що цей код має незначну модифікацію в порівнянні з тим, що я використовую сам. У оригіналі я закликаю інструкцію IL cpblk від C #, щоб зробити фактичне копіювання. Для простоти та портативності коду тут я замінив це на P / Invoke memcpy, як ви бачите. Для отримання максимальної продуктивності на x64 ( але, можливо, не на x86 ) ви можете скористатися методом cpblk .


string.Joinробить усе це вже для вас. Не потрібно писати це самостійно. Він обчислює розмір остаточного рядка, конструює рядок такого розміру, а потім виписує в основний масив символів. Він навіть має премію за використання читаних імен змінних у процесі.
Сервіс

1
@ Сервіс Дякую за коментар; дійсно String.Joinможе бути ефективним. Як я натякнув у вступі, код тут є лише найпростішою ілюстрацією сімейства функцій, які я використовую для сценаріїв, які String.Joinабо не обробляють (наприклад, оптимізацію для Charроздільника), або не обробляють у попередніх версіях .NET. Я вважаю, що я не мав би обрати це для найпростішого прикладу, оскільки це справа, яка String.Joinвже справляється добре, хоча і з "неефективністю", ймовірно, незмірною, при обробці вакуумного сепаратора, а саме. String.Empty.
Гленн Слейден

Впевнені, що у вас немає роздільника, то вам слід зателефонувати Concat, що також робить це правильно. У будь-якому випадку вам не потрібно писати код самостійно.
Сервіс

7
@Servy Я порівняв продуктивність String.Joinпорівняно зі своїм кодом, використовуючи цей тестовий джгут . Для 10 мільйонів операцій з випадковим конкатенацією до 100 рядків розміром у слові кожен код, показаний вище, стабільно на 34% швидше, ніж String.Joinу версії x64, що складається з .NET 4.7 . Оскільки ОП прямо вимагає «найефективнішого» методу, результат підказує, що моя відповідь застосовується. Якщо це вирішує ваші проблеми, я пропоную вам переглянути свій голос.
Гленн Слейден

1
Нещодавно я встановив це на x64 full CLR 4.7.1 і виявив, що це приблизно вдвічі швидше, ніж string.Join з виділеним приблизно на 25% менше пам’яті ( i.imgur.com/SxIpEmL.png ) при використанні cpblk або github.com/ JonHanna / Mnemosyne
квентин-зірин

6

З цієї статті MSDN :

Існує деяка накладні витрати, пов'язані зі створенням об'єкта StringBuilder, як у часі, так і в пам'яті. На машині з швидкою пам'яттю StringBuilder стає корисним, якщо ви робите близько п'яти операцій. Як правило, я б сказав, що 10 і більше рядкових операцій є виправданням накладних витрат на будь-якій машині, навіть більш повільній.

Тож якщо ви довіряєте MSDN, перейдіть до StringBuilder, якщо вам доведеться виконати більше 10 операцій / конкатенацій рядків - інакше простий рядковий конфакт з '+' добре.


5

Важливо також зазначити, що вам слід використовувати +оператор, якщо ви об'єднуєте рядкові літерали .

Коли ви об'єднуєте літеральні рядки або рядкові константи за допомогою оператора +, компілятор створює одну рядок. Зв'язок часу виконання не відбувається.

Як: об'єднати кілька рядків (Посібник з програмування C #)


5

Додаючи до інших відповідей, майте на увазі, що StringBuilder можна повідомити про початковий об'єм пам'яті .

Параметр місткості визначає максимальну кількість символів, які можуть зберігатися в пам'яті, виділеної поточним екземпляром. Його значення присвоюється властивості Capacity . Якщо кількість символів, що зберігаються в поточному екземплярі, перевищує це значення ємності , об'єкт StringBuilder виділяє додаткову пам'ять для їх зберігання.

Якщо ємність дорівнює нулю, використовується специфічна для реалізації потужність за замовчуванням.

Неодноразове додавання до StringBuilder, який не був попередньо виділений, може призвести до безлічі зайвих виділень, як і багаторазове об'єднання регулярних рядків.

Якщо ви знаєте, як довго триватиме остаточний рядок, можете потрійно його обчислити, або ви можете навчитись здогадуватися про загальний випадок (виділити занадто багато не обов'язково погано), вам слід надати цю інформацію конструктору або Властивість ємності . Особливо під час запуску тестів на ефективність для порівняння StringBuilder з іншими методами, такими як String.Concat, які роблять те ж саме всередині. Будь-який тест, який ви бачите в Інтернеті, який не включає попереднє виділення StringBuilder у своїх порівняннях, є неправильним.

Якщо ви не можете вгадати будь-який розмір про розмір, ви, ймовірно, пишете функцію утиліти, яка повинна мати власний необов'язковий аргумент для контролю попереднього розподілу.


4

Далі може бути ще одне альтернативне рішення для об'єднання декількох рядків.

String str1 = "sometext";
string str2 = "some other text";

string afterConcate = $"{str1}{str2}";

інтерполяція рядків


1
Це насправді дивно приємно як загальний метод конкатенації. Це в основному, String.Formatале легше читати і простіше працювати. Розмітка його, це трохи повільніше , ніж +та String.Concatна одній лінії зчеплень , але набагато краще , ніж обидва з тих , при повторних викликах робить StringBuilderменш необхідним.
u8it

2

Найбільш ефективним є використання StringBuilder, наприклад:

StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();

@jonezy: String.Concat добре, якщо у вас є кілька дрібниць. Але якщо ви об'єднаєте мегабайти даних, ваша програма, швидше за все, запрацює.


ей, що рішення для мегабайт даних?
Ніл

2

Спробуйте це 2 частини коду, і ви знайдете рішення.

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

Vs

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

Ви побачите, що 1-й код закінчиться дуже швидко, і пам'ять буде в хорошій кількості.

Другий код, можливо, пам'ять буде в порядку, але це займе більше часу ... набагато довше. Тож якщо у вас є додаток для багатьох користувачів і вам потрібна швидкість, використовуйте 1-ю. Якщо у вас є на короткий термін одне користувацьке додаток, можливо, ви можете використовувати і те, і друге, буде більш "природним" для розробників.

Ура.


1

Лише для двох рядків ви точно не хочете використовувати StringBuilder. Існує деякий поріг, над яким накладні витрати StringBuilder менше, ніж накладні витрати для виділення декількох рядків.

Отже, для більш ніж 2-3 рядків використовуйте код DannySmurf . В іншому випадку просто скористайтеся оператором +.


1

System.String є незмінним. Коли ми змінюємо значення струнної змінної, новій пам'яті виділяється нове значення, а попередній розподіл пам'яті звільняється. System.StringBuilder був розроблений таким чином, щоб мати концепцію змінної рядка, де можна виконати різні операції без виділення окремого місця пам'яті для модифікованого рядка.


1

Ще одне рішення:

всередині циклу використовуйте Список замість рядка.

List<string> lst= new List<string>();

for(int i=0; i<100000; i++){
    ...........
    lst.Add(...);
}
return String.Join("", lst.ToArray());;

це дуже дуже швидко.



0

Це залежало б від коду. StringBuilder, як правило, більш ефективний, але якщо ви об'єднаєте лише кілька рядків і зробите це в одному рядку, оптимізація коду, швидше за все, піклується про нього за вас. Важливо подумати про те, як виглядає код: для більших наборів StringBuilder полегшить читання, для маленьких StringBuilder просто додасть непотрібного безладу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.