【问题标题】:StringBuilder exception on changing Capacity!更改容量时的 StringBuilder 异常!
【发布时间】:2011-09-30 22:37:16
【问题描述】:

这是我的代码行:

StringBuilder myCompleteMessage = new StringBuilder();
myCompleteMessage.Capacity = Int32.MaxValue-1;

也试过了:

myCompleteMessage.Capacity = myCompleteMessage.MaxCapacity-1;

我在第 2 行遇到异常。

Exception of type 'System.OutOfMemoryException' was thrown.

堆栈跟踪:

at System.String.GetStringForStringBuilder(String value, Int32 startIndex, Int32 length, Int32 capacity)
at System.Text.StringBuilder.set_Capacity(Int32 value)
at Orca.Server.HandleClientComm(Object newClient) in C:\Users\Dan\Documents\Visual Studio 2010\Projects\msig\Orca\Server.cs:line 100
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart(Object obj)

【问题讨论】:

  • 您是否有理由需要将其指定为容量?如果您让框架来处理容量,通常效果最好(除非您确切知道需要多少)。
  • 如果你需要这么大的字符串,你需要重新设计,但问题是好的。
  • 什么是“预先设计”是什么意思?
  • 在 x64 中也失败了。
  • @Danpe 你是说如果不设置容量就不能让 StringBuilder 增长到 2048 个字符以上?如果是这样,那似乎很奇怪。也许发布另一个问题,询问您为什么在使用 StringBuilder 时遇到这个问题?

标签: c# .net exception memory stringbuilder


【解决方案1】:

假设您使用的是 32 位系统,那么第二行将始终失败。您要求 .NET 为您的 StringBuilder 分配 4 GB 的空间,这比进程必须使用的内存更多(感谢 Joel 指出 char 占用 2 个字节,而不是 1 个)。

编辑
如果你用 ILSpy 查看StringBuilder,你会在Capacity 的集合中看到这段代码:

if (this.Capacity != value)
{
    int num = value - this.m_ChunkOffset;
    char[] array = new char[num];
    Array.Copy(this.m_ChunkChars, array, this.m_ChunkLength);
    this.m_ChunkChars = array;
}

通过将容量设置为 int.MaxValue - 1,您是在告诉 .NET 尝试分配一个 4 GB 字符数组,这就是代码失败的原因。

【讨论】:

  • 嗯,在 x64 上也失败了。有没有办法指定更大的进程空间?如果不支持,为什么默认的 MaxCapacity 会是 Int.MaxValue?
  • 只是好奇 :) 不质疑逻辑
  • 你说得对。 myCompleteMessage.MaxCapacity/2 错了! myCompleteMessage.MaxCapacity/4 对!
  • 不要忘记char 是 16 位 Unicode。 1000 的容量是 2000 字节。 @Adam,int.MaxValue 是唯一明智的答案,因为 Capacityint。任何较小的MaxCapacity 都将是不必要的任意,因为该类无论如何都受到内存的限制。如果你的机器有 6-8GB 或更多内存,我相信你可以将 Capacity 设置为 MaxCapacity 就好了,因为它可能会使用 4GB。
  • @Danpe:任何 CPU 只允许您创建一个在 32 位操作系统上运行 32 位和在 64 位操作系统上运行 64 位的程序集(由JIT 编译器)。正如阿列克谢在他的回答中指出的那样,CLR 对对象的大小施加了自己的限制,所以我之前评论的后半部分无论如何都是不正确的。它需要是连续的内存,这是另一个复杂因素。
【解决方案2】:

CLR 堆限制为 2GB 对象(http://blogs.msdn.com/b/joshwil/archive/2005/08/10/450202.aspx - 对于 2.0,我相信对于 4.0 也是如此),因此没有任何东西可以分配超过该大小的一个连续内存块。对于字符,它会为您提供 Int.MaxValue/2 个条目。

如果您确实需要管理如此大量的文本,请查看允许分块的 MemoryStream 的替代实现或允许分块的数组。如果您想保留在默认类中 - 考虑将数据写入临时文件(使用 DeleteOnClose 创建的临时文件甚至可能不会提交到磁盘 - 因此与每次增加容量时必须复制数据的 StringBuilder 或 MemoryStream 相比,您可以获得更好的性能 - http://msdn.microsoft.com/en-us/library/system.io.fileoptions.aspx)。

【讨论】:

    【解决方案3】:

    StringBuilder 的容量属性是留作字符串缓冲区的字符数。使用 UTF-8,一个字符最多可以有 4 个字节。即使是 2 字节,Int32.MaxValue - 1 的容量也远远超过 32 位系统的容量,仅 2 字节字符 (UTF-8/ASCII) 的容量为 4 GB。此外,如果您查看documentation,StringBuilder 的默认容量已经设置为 Int32.MaxValue。

    如果您正在循环填充 StringBuilder,则填充它的速度可能比 .NET 通过垃圾收集清理分配的内存的速度要快,这就是它可能在 2048 个字符长度处停止的原因。此外,在内部它需要一个连续的内存块,这也可能会遇到问题。

    不过,您的问题是关于容量属性的,您永远无法在 32 位系统上分配这么多容量。我在 MSDN 上找到了一个 interesting discussion 来处理这个特定问题,通过测试发现实际容量要低得多,因为当所有字符都是 2 字节时,Int32.MaxValue 容量转换为需要 4 GB 的存储空间。

    你能用Memory-Mapped file 代替吗? StringBuilder 不是为此而设计的,您需要重新考虑您的设计。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多