我的问题是: C#中的字符串连接是否安全?如果字符串连接导致意外错误,并且使用StringBuilder替换该字符串连接会导致这些错误消失,那可能表示什么?
后台:我正在开发一个小命令行C#应用程序。它接受命令行参数,执行稍微复杂的SQL查询,并将大约1300行数据输出到格式化的XML文件中。
我的初始程序总是在调试模式下正常运行。但是,在发布模式下,它将获得大约第750个SQL结果,然后因错误而死亡。错误是无法读取某一列数据,即使通过SqlDataReader对象的Read()方法刚刚返回true也是如此。
通过对代码中的所有操作使用StringBuilder来解决此问题,之前已经存在“string1 + string2”。我不是在谈论SQL查询循环中的字符串连接,其中StringBuilder已经在使用中。我在谈论代码中早期的两个或三个短字符串变量之间的简单连接。
我的印象是C#足够聪明,可以通过添加几个字符串来处理内存管理。我错了吗?或者这是否表明其他一些代码问题?
答案 0 :(得分:15)
回答您的问题: C#中的字符串连接(和一般的.NET) “安全”,但是如您所描述的那样在紧密循环中执行它可能会导致严重的内存压力并对垃圾收集器造成压力。
我猜想你所说的错误与某种资源耗尽有关,但如果你能提供更多细节会有所帮助 - 例如,你收到了例外吗?申请是否异常终止?
<强>背景强> .NET字符串是不可变的,所以当你进行这样的连接时:
var stringList = new List<string> {"aaa", "bbb", "ccc", "ddd", //... };
string result = String.Empty;
foreach (var s in stringList)
{
result = result + s;
}
这大致等同于以下内容:
string result = "";
result = "aaa"
string temp1 = result + "bbb";
result = temp1;
string temp2 = temp1 + "ccc";
result = temp2;
string temp3 = temp2 + "ddd";
result = temp3;
// ...
result = tempN + x;
这个例子的目的是强调每次循环都会导致分配一个新的临时字符串。
由于字符串是不可变的,因此运行时没有其他选项,但每次在结果末尾添加另一个字符串时都会分配一个新字符串。
虽然result
字符串不断更新以指向最新且最好的中间结果,但是您生成了大量这些未命名的临时字符串,几乎可以立即进行垃圾收集。
在此连接结束时,您将在内存中存储以下字符串(为简单起见,假设垃圾收集器尚未运行)。
string a = "aaa";
string b = "bbb";
string c = "ccc";
// ...
string temp1 = "aaabbb";
string temp2 = "aaabbbccc";
string temp3 = "aaabbbcccddd";
string temp4 = "aaabbbcccdddeee";
string temp5 = "aaabbbcccdddeeefff";
string temp6 = "aaabbbcccdddeeefffggg";
// ...
虽然所有这些隐式临时变量几乎都可以立即进行垃圾回收,但仍然必须进行分配。在紧密循环中执行连接时,这会给垃圾收集器带来很大的压力,如果不出意外,会使代码运行得非常慢。我已经看到了第一手的性能影响,当你的连接字符串变大时,它变得非常引人注目。
如果您要进行多个字符串连接,建议的方法是始终使用StringBuilder
。 StringBuilder
使用可变缓冲区来减少分配的数量在构建你的字符串时是必要的。
答案 1 :(得分:11)
如果在循环中连接大量字符串,则串联连接比使用StringBuilder更加内存密集。在极端情况下,你可能会耗尽内存。
这几乎肯定是代码中的错误。
也许你正在连接很多字符串。或者也许是完全不同的东西。
我会在没有任何对根本原因的先入之见的情况下重新进行调试 - 如果您仍然遇到问题,请尝试将其降低到重现问题和发布代码所需的最低限度。
答案 2 :(得分:7)
除了你正在做的事情可能最好用XML API代替字符串或StringBuilder我怀疑你看到的错误是由字符串连接引起的。也许切换到StringBuilder只是掩盖了错误或优雅地过了它,但我怀疑使用字符串确实是原因。
答案 3 :(得分:3)
串联版本与字符串构建器版本需要多长时间?您与DB的连接可能已关闭。如果你正在进行大量的连接,我会使用StringBuilder,因为它更有效率。
答案 4 :(得分:1)
一个原因可能是字符串在.Net中是不可变的,因此当您对连接等操作进行操作时,实际上是在创建一个新字符串。
另一个可能的原因是字符串长度为int,因此最大可能长度为Int32.MaxValue或2,147,483,647。
在任何一种情况下,对于这种类型的操作,StringBuilder优于“string1 + string2”。虽然,使用内置的XML功能会更好。
答案 5 :(得分:0)
这是我在黑暗中的镜头......
.NET中的字符串(不是stringbuilders)进入String Intern Pool。这基本上是由CLR管理的区域,用于共享字符串以提高性能。这里必须有一些限制,虽然我不知道这个限制是什么。我想你正在做的所有连接都是撞到字符串实习池的天花板。所以SQL说是的我有一个值,但它不能把它放在任何地方,所以你得到一个例外。
快速简便的测试将是nGen你的程序集,看看你是否仍然得到错误。在nGen'ing之后,您的应用程序将不再使用该池。
如果失败了,我会联系微软尝试获取一些详细信息。我认为我的想法听起来似乎有道理,但我不知道为什么它在调试模式下工作。也许在调试模式下,字符串不会被实现。我也不是专家。
答案 6 :(得分:0)
string.Concat(string[])
是连接字符串的最快方法。在循环中使用时,它会在性能上轻易杀死StringBuilder
,尤其是在每次迭代中创建StringBuilder
时。
如果您使用Google“c#string format vs stringbuilder”或类似内容,则会有很多引用。
http://www.codeproject.com/KB/cs/StringBuilder_vs_String.aspx为您提供了一个关于时代的理念。这里string.Join赢得串联测试,但我相信这是因为使用了string.Concat(string, string)
而不是采用数组的重载版本。
如果您查看由不同方法生成的MSIL代码,您将看到引擎盖下发生了什么。
答案 7 :(得分:-3)
将字符串复合在一起时,我总是使用StringBuilder。它是专为它而设计的,只需使用“string1 + string2”就可以更高效。