前几天在园子里面看到一篇讲StringBuilder性能的文章。文章里面给出了一个测试用例,比较StringBuilder.AppendJoin和String.Join的性能。根据该测试结果,“对于这个操作,这两种方法的速度很接近,但 StringBuilder.AppendJoin 使用的内存明显较少”。据此,该文言之凿凿地指出,应该使用“StringBuilder.AppendJoin 而不是 String.Join”。

事实果真如此吗?

搜索一下就知道,StringBuilder采用的是先预分配缓冲区,然后将要连接的字符串直接复制到缓冲区的做法。这个做法确实高效,避免了中间结果带来的时间消耗和内存占用。

那么,string.Join真的那么不堪吗?

首先看看string.Join的代码:

public static string Join(string separator, params string[] value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
return string.Join(separator, value, 0, value.Length);
} public unsafe static string Join(string separator, string[] value, int startIndex, int count)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
}
if (count < 0)
{
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
}
if (startIndex > value.Length - count)
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
}
if (separator == null)
{
separator = string.Empty;
}
if (count == 0)
{
return string.Empty;
}
int num = 0;
int num2 = startIndex + count - 1;
for (int i = startIndex; i <= num2; i++)
{
if (value[i] != null)
{
num += value[i].Length;
}
}
num += (count - 1) * separator.Length;
if (num < 0 || num + 1 < 0)
{
throw new OutOfMemoryException();
}
if (num == 0)
{
return string.Empty;
}
string text = string.FastAllocateString(num);
fixed (char* ptr = &text.m_firstChar)
{
UnSafeCharBuffer unSafeCharBuffer = new UnSafeCharBuffer(ptr, num);
unSafeCharBuffer.AppendString(value[startIndex]);
for (int j = startIndex + 1; j <= num2; j++)
{
unSafeCharBuffer.AppendString(separator);
unSafeCharBuffer.AppendString(value[j]);
}
}
return text;
}

可以看到,string.Join的做法是先计算最终结果的大小,然后调用string.FastAllocateString分配空间,最后将数据直接复制到分配的缓冲区。很显然,这一过程和StringBuilder如出一辙。

但是测试结果毕竟摆在那里,那么问题在哪里呢?

看看该文的测试用例:

[Benchmark]
public string UsingStringJoin() {
var list = new List < string > {
"A",
"B", "C", "D", "E"
};
var stringBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
stringBuilder.Append(string.Join(' ', list));
}
return stringBuilder.ToString();
}
[Benchmark]
public string UsingAppendJoin() {
var list = new List < string > {
"A",
"B", "C", "D", "E"
};
var stringBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
stringBuilder.AppendJoin(' ', list);
}
return stringBuilder.ToString();
}

问题就在下面的一句:

stringBuilder.Append(string.Join(' ', list));

这句代码实际上是先用string.Join把list拼好,再调用stringBuilder.Append把string.Join的结果拼接起来。这样,string.Join分配一次内存,stringBuilder再分配一次内存,内存占用怎能不大?

当然,没码没真相,得拿编译后的IL说话。所以,根据这个用法写段测试代码:

private void BtnStartClick(object sender, EventArgs e)
{
string[] dummy = new string[]
{
"zfsdfsd",
"sdfsdf"
};
StringBuilder sb = new StringBuilder();
sb.Append(string.Join(",", dummy));
string s = sb.ToString();
Console.WriteLine(s);
}

看看IL:

 1 .method private hidebysig
2 instance void BtnStartClick (
3 object sender,
4 class [mscorlib]System.EventArgs e
5 ) cil managed
6 {
7 // Header Size: 12 bytes
8 // Code Size: 65 (0x41) bytes
9 // LocalVarSig Token: 0x11000004 RID: 4
10 .maxstack 3
11 .locals init (
12 [0] string[] dummy,
13 [1] class [mscorlib]System.Text.StringBuilder sb,
14 [2] string s,
15 [3] string[] CS$0$0000
16 )
17
18 /* (34,3)-(34,4) d:\Work_Private\IoT\ClientSimulator\MainForm.cs */
19 /* 0x00000340 00 */ IL_0000: nop
20 /* (35,4)-(35,52) d:\Work_Private\IoT\ClientSimulator\MainForm.cs */
21 /* 0x00000341 18 */ IL_0001: ldc.i4.2
22 /* 0x00000342 8D1D000001 */ IL_0002: newarr [mscorlib]System.String
23 /* 0x00000347 0D */ IL_0007: stloc.3
24 /* 0x00000348 09 */ IL_0008: ldloc.3
25 /* 0x00000349 16 */ IL_0009: ldc.i4.0
26 /* 0x0000034A 7201000070 */ IL_000A: ldstr "zfsdfsd"
27 /* 0x0000034F A2 */ IL_000F: stelem.ref
28 /* 0x00000350 09 */ IL_0010: ldloc.3
29 /* 0x00000351 17 */ IL_0011: ldc.i4.1
30 /* 0x00000352 7211000070 */ IL_0012: ldstr "sdfsdf"
31 /* 0x00000357 A2 */ IL_0017: stelem.ref
32 /* 0x00000358 09 */ IL_0018: ldloc.3
33 /* 0x00000359 0A */ IL_0019: stloc.0
34 /* (37,4)-(37,41) d:\Work_Private\IoT\ClientSimulator\MainForm.cs */
35 /* 0x0000035A 731600000A */ IL_001A: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
36 /* 0x0000035F 0B */ IL_001F: stloc.1
37 /* (38,4)-(38,38) d:\Work_Private\IoT\ClientSimulator\MainForm.cs */
38 /* 0x00000360 07 */ IL_0020: ldloc.1
39 /* 0x00000361 721F000070 */ IL_0021: ldstr ","
40 /* 0x00000366 06 */ IL_0026: ldloc.0
41 /* 0x00000367 281700000A */ IL_0027: call string [mscorlib]System.String::Join(string, string[])
42 /* 0x0000036C 6F1800000A */ IL_002C: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
43 /* 0x00000371 26 */ IL_0031: pop
44 /* (40,4)-(40,27) d:\Work_Private\IoT\ClientSimulator\MainForm.cs */
45 /* 0x00000372 07 */ IL_0032: ldloc.1
46 /* 0x00000373 6F1900000A */ IL_0033: callvirt instance string [mscorlib]System.Object::ToString()
47 /* 0x00000378 0C */ IL_0038: stloc.2
48 /* (42,4)-(42,25) d:\Work_Private\IoT\ClientSimulator\MainForm.cs */
49 /* 0x00000379 08 */ IL_0039: ldloc.2
50 /* 0x0000037A 281A00000A */ IL_003A: call void [mscorlib]System.Console::WriteLine(string)
51 /* 0x0000037F 00 */ IL_003F: nop
52 /* (45,3)-(45,4) d:\Work_Private\IoT\ClientSimulator\MainForm.cs */
53 /* 0x00000380 2A */ IL_0040: ret
54 } // end of method MainForm::BtnStartClick

从第41和42行可以清楚看到,代码先调用了String.Join,然后是StringBuilder.Append.

所以事情很清楚了。错误的代码得出了不符合本意的测试结果,根据这个结果得到的结论自然也是错误的。

实际上,根据MS的文档,“修改 StringBuilder 时,除非达到容量,否则对象不会为自己重新分配空间。 当达到容量时,将自动分配新的空间且容量翻倍。”,可以看出,在边界情况下,使用StringBuilder耗费的空间反而比Join要大。当然,考虑到内存对齐的因素,Join也会有部分内存浪费,但这实在是微不足道的。

那么,应该使用StringBuilder还是Join呢?

很简单,按照具体情况决定。如果要拼接的是现成的字符串数组,自然应该用Join。否则的话,还是用StringBuilder省事点。

也谈string.Join和StringBuilder的性能比较的更多相关文章

  1. C# String.Join 与 StringBuilder 对比,谁更快

    String.Join 文档      StringBuilder 文档 这两天刷 Leedcode 做到一道 String 的题时突然想到这俩对比的问题,于是查了一下资料并简单对比了一下. 首先对于 ...

  2. 如何提高C# StringBuilder的性能

    本文探讨使用C# StringBuilder 的最佳实践,用于减少内存分配,提高字符串操作的性能. 在 .NET 中,字符串是不可变的类型.每当你在 .NET 中修改一个字符串对象时,就会在内存中创建 ...

  3. string.Join和string.Concat的区别

    源自Difference between String.Join() vs String.Concat() With .NET 4.0, String.Join() uses StringBuilde ...

  4. 教你50招提升ASP.NET性能(二十三):StringBuilder不适用于所有字符串连接的场景;String.Join可能是

    (41)StringBuilder is NOT the answer for all string concatenation scenarios; String.Join could be 招数4 ...

  5. 浅谈String、StringBuffer与StringBuilder

    浅谈String.StringBuffer与StringBuilder   先详细介绍一下String.StringBuffer与StringBuilder String: 官方对String的说明: ...

  6. 2019.03.19 读书笔记 string与stringbuilder的性能

    1 string与stringbuilder 并不是stringbuilder任何时候都在性能上占优势,在少量(大约个位数)的字符串时,并不比普通string操作快. string慢的原因不是stri ...

  7. C# 字符串拼接性能探索 c#中+、string.Concat、string.Format、StringBuilder.Append四种方式进行字符串拼接时的性能

    本文通过ANTS Memory Profiler工具探索c#中+.string.Concat.string.Format.StringBuilder.Append四种方式进行字符串拼接时的性能. 本文 ...

  8. java String、String.concat和StringBuilder性能对比

    看到网上有人已经做过对比,并且贴出了代码,然后我运行了之后发现跟我分析的结论差距很大.发现他的代码有个问题,UUID.randomUUID() 首次调用耗时会很高,这个耗时被计算给了String,这对 ...

  9. BCL中String.Join的实现

    在开发中,有时候会遇到需要把一个List对象中的某个字段用一个分隔符拼成一个字符串的情况.比如在SQL语句的in条件中,我们通常需要把List<int>这样的对象转换为“1,2,3”这样的 ...

随机推荐

  1. 2021.8.11考试总结[NOIP模拟36]

    T1 Dove玩扑克 考场并查集加树状数组加桶期望$65pts$实际$80pts$,考后多开个数组记哪些数出现过,只扫出现过的数就切了.用$set$维护可以把被删没的数去掉,更快. $code:$ 1 ...

  2. 【BZOJ 1419】Red is good [概率DP]

    我 是 Z Z 概率好玄啊(好吧是我太弱.jpg Description 桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付出1美元.可以随时停止翻 ...

  3. arm开发板上找不到/dev/i2c-*设备

    最近在调试arm与外设iic通讯是,想来个投机取巧,先不写单独的驱动,直接通过iic bus设备是否可以连接到外设,然后发现在板子上找不到"/dev/i2c-n"的设备,标准的系统 ...

  4. binary-tree-maximum-path-sum leetcode C++

    Given a binary tree, find the maximum path sum. The path may start and end at any node in the tree. ...

  5. 恶意代码の奇客pdf分析

    目录 恶意代码の奇客pdf分析 奇客PDF安装包分析 静态分析基础技术 上传杀毒网 查壳 编译时间 导入表分析 资源查看 动态分析基础技术 Process Monitor监控 Process Expl ...

  6. Java项目中集成钉钉机器人推送消息提醒

    前言: 项目中有一个需求,当有新订单产生的时候,希望能够及时通知到业务相关人员进行处理,整体考虑了一下,选用了钉钉机器人提醒功能(公司内部主要也是使用钉钉进行通讯). 操作: 主要分为两部分进行处理: ...

  7. mysql数据库导入导出文件sql文件

    window下 1.导出整个数据库 mysqldump -u 用户名 -p 数据库名 > 导出的文件名 mysqldump -u dbuser -p dbname > dbname.sql ...

  8. OpenXml SDK学习笔记(1):Word的基本结构

    能写多少篇我就不确定了,可能就这一篇就太监了,也有可能会写不少. OpenXml SDK 相信很多人都不陌生,这个就是管Office一家的文档格式,Word, Excel, PowerPoint等都用 ...

  9. Java学习(十一)

    今天学习了this和static关键字,这两个都是c++中学过的,但讲师还是讲了2个小时... 学得东西大部分都知道吧. this是当前对象的地址,类中带有static的方法不能使用this. 类中带 ...

  10. [第三章]c++学习笔记1(this指针)

    this指针作用,其作用就是指向成员函数所作用的对象 使用例 为了返回c1,使用this指针,来指向作用的对象 使用空指针调用hello,调用hello欲使其作用在p指向的对象上,然而p没指向任何对象 ...