连接字符串的最有效方法是什么?
17 回答
.NET 性能专家Rico Mariani有一篇关于这个主题的文章。这并不像人们想象的那么简单。基本建议是这样的:
如果您的模式如下所示:
x = f1(...) + f2(...) + f3(...) + f4(...)
那是一个 concat,它很活泼, StringBuilder 可能无济于事。
如果您的模式如下所示:
if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)
那么你可能想要 StringBuilder。
支持这一主张的另一篇文章来自 Eric Lippert,他在其中+
详细描述了在单行连接上执行的优化。
该StringBuilder.Append()
方法比使用运算符要好得多+
。但是我发现,当执行 1000 个或更少的连接时,String.Join()
它甚至比StringBuilder
.
StringBuilder sb = new StringBuilder();
sb.Append(someString);
唯一的问题String.Join
是您必须使用公共分隔符连接字符串。
编辑:正如@ryanversaw指出的那样,您可以制作 delimiter string.Empty
。
string key = String.Join("_", new String[]
{ "Customers_Contacts", customerID, database, SessionID });
有 6 种类型的字符串连接:
- 使用加号 (
+
) 符号。 - 使用
string.Concat()
. - 使用
string.Join()
. - 使用
string.Format()
. - 使用
string.Append()
. - 使用
StringBuilder
.
在一个实验中,已经证明string.Concat()
如果单词少于 1000(大约)是最好的方法,如果单词超过 1000 则StringBuilder
应该使用。
有关更多信息,请查看此站点。
string.Join() 与 string.Concat()
这里的 string.Concat 方法等价于使用空分隔符调用 string.Join 方法。附加一个空字符串很快,但不这样做更快,所以string.Concat方法在这里会更好。
来自Chinh Do - StringBuilder 并不总是更快:
经验法则
当连接三个或更少的动态字符串值时,使用传统的字符串连接。
当连接三个以上的动态字符串值时,使用
StringBuilder
.从多个字符串文字构建大字符串时,请使用
@
字符串文字或内联 + 运算符。
大多数时候StringBuilder
是你最好的选择,但正如那篇文章所示,你至少应该考虑每种情况。
如果您在循环中运行,StringBuilder
则可能是要走的路;它为您节省了定期创建新字符串的开销。不过,在只运行一次的代码中,String.Concat
可能没问题。
然而,Rico Mariani(.NET 优化大师)做了一个小测验,最后他说,在大多数情况下,他推荐String.Format
.
这是我十多年来为我的大型 NLP 应用程序开发的最快方法。我有IEnumerable<T>
其他输入类型的变体,有和没有不同类型的分隔符(Char
, String
),但这里我展示了 将数组中的所有字符串连接成一个字符串的简单情况,没有分隔符。这里的最新版本是在C# 7和.NET 4.7上开发和单元测试的。
提高性能有两个关键;首先是预先计算所需的确切总大小。当输入是此处所示的数组时,此步骤很简单。相反,为了处理IEnumerable<T>
,值得首先将字符串收集到一个临时数组中以计算该总数(该数组需要避免ToString()
每个元素多次调用,因为从技术上讲,考虑到副作用的可能性,这样做可能会改变预期的语义'字符串连接'操作)。
接下来,给定最终字符串的总分配大小,通过就地构建结果字符串可以获得最大的性能提升。这样做需要(可能有争议的)技术,即暂时中止String
最初分配为全零的 new 的不变性。但是,除了任何此类争议...
...请注意,这是此页面上唯一的批量连接解决方案,它完全避免了构造函数的额外一轮分配和复制
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);
我应该提到,这段代码与我自己使用的代码略有修改。在原文中,我从C#调用cpblk IL 指令来进行实际的复制。如您所见,为了此处代码的简单性和可移植性,我将其替换为 P/Invoke 。为了在 x64(但可能不是 x86)上获得最高性能,您可能需要改用cpblk方法。memcpy
从这篇MSDN 文章:
创建 StringBuilder 对象会产生一些开销,包括时间和内存。在具有快速内存的机器上,如果您正在执行大约五个操作,则 StringBuilder 变得有价值。根据经验,我会说 10 个或更多的字符串操作是任何机器上开销的理由,即使是较慢的机器也是如此。
因此,如果您信任 MSDN,如果您必须执行 10 次以上的字符串操作/连接,请使用 StringBuilder - 否则使用“+”的简单字符串连接就可以了。
除了其他答案,请记住StringBuilder 可以被告知要分配的初始内存量。
容量参数定义了当前实例分配的内存中可以存储的最大字符数。它的值分配给容量属性。如果当前实例中要存储的字符数超过此容量值,则 StringBuilder 对象会分配额外的内存来存储它们。
如果容量为零,则使用特定于实现的默认容量。
重复附加到尚未预分配的 StringBuilder 会导致大量不必要的分配,就像重复连接常规字符串一样。
如果您知道最终字符串的长度,可以简单地计算它,或者可以对常见情况做出有根据的猜测(分配太多不一定是坏事),您应该将此信息提供给构造函数或容量属性。特别是在运行性能测试以将 StringBuilder 与其他方法(如 String.Concat)进行比较时,它们在内部做同样的事情。您在网上看到的任何在其比较中不包括 StringBuilder 预分配的测试都是错误的。
如果您无法对大小做出任何猜测,那么您可能正在编写一个实用函数,它应该有自己的可选参数来控制预分配。
以下可能是连接多个字符串的另一种替代解决方案。
String str1 = "sometext";
string str2 = "some other text";
string afterConcate = $"{str1}{str2}";
试试这 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();
}
您会发现第一个代码将很快结束,并且内存量很大。
第二个代码可能内存没问题,但需要更长的时间……更长的时间。因此,如果您有一个面向大量用户的应用程序并且您需要速度,请使用第一个。如果您有一个短期的单用户应用程序,也许您可以同时使用两者,否则对于开发人员来说,第二个应用程序会更“自然”。
干杯。
最有效的是使用 StringBuilder,如下所示:
StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();
@jonezy:如果你有一些小东西,String.Concat 很好。但是,如果您要连接数兆字节的数据,您的程序可能会失败。
System.String 是不可变的。当我们修改字符串变量的值时,会为新值分配新内存,并释放先前的内存分配。System.StringBuilder 旨在具有可变字符串的概念,其中可以执行各种操作而无需为修改后的字符串分配单独的内存位置。
另一种解决方案:
在循环内,使用 List 而不是字符串。
List<string> lst= new List<string>();
for(int i=0; i<100000; i++){
...........
lst.Add(...);
}
return String.Join("", lst.ToArray());;
它非常非常快。
对于只有两个字符串,您绝对不想使用 StringBuilder。有一些阈值,超过该阈值 StringBuilder 开销小于分配多个字符串的开销。
因此,对于超过 2-3 个字符串,请使用DannySmurf 的代码。否则,只需使用 + 运算符。
这实际上取决于您的使用模式。可以在此处找到 string.Join、string、Concat 和 string.Format 之间的详细基准:String.Format 不适合密集型日志记录
(这实际上与我对这个问题的回答相同)
这将取决于代码。StringBuilder 通常更高效,但如果您只是连接几个字符串并在一行中完成所有操作,代码优化可能会为您处理它。考虑代码的外观也很重要:对于较大的集合 StringBuilder 将使其更易于阅读,对于较小的集合 StringBuilder 只会增加不必要的混乱。