Вот самый быстрый метод, который я развил за десятилетие для моего крупномасштабного приложения НЛП. У меня есть варианты для 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);
Я должен отметить, что этот код немного изменен по сравнению с тем, что я использую сам. В оригинале я вызываю инструкцию cpblk IL из C # , чтобы выполнить фактическое копирование. Для простоты и переносимости кода здесь я заменил это на P / Invoke memcpy
, как вы можете видеть. Для максимальной производительности на x64 (, но, возможно, не на x86 ) вы можете использовать метод cpblk .