StringBuilder类的OutOfMemoryException

26

我编写了以下函数

public void TestSB()
{
  string str = "The quick brown fox jumps over the lazy dog.";
  StringBuilder sb = new StringBuilder();
  int j = 0;
  int len = 0;

  try
  {
     for (int i = 0; i < (10000000 * 2); i++)
     {
        j = i;
        len = sb.Length;
        sb.Append(str);
     }

    Console.WriteLine("Success ::" + sb.Length.ToString());
  }
  catch (Exception ex)
  {
      Console.WriteLine(
          ex.Message + " :: " + j.ToString() + " :: " + len.ToString());
  }
}

我认为,StringBuilder具有超过20亿个字符的容量(精确地说是2,147,483,647)。

但是,当我运行上述函数时,它在达到大约8亿的容量时就会给出System.OutOfMemoryException。 此外,我发现在具有相同内存和类似负载量的不同PC上,结果差异很大。

请问是否有人能够提供或解释这种情况的原因?


1
我会看一下https://dev59.com/hnRC5IYBdhLWcg3wP-j4和https://dev59.com/lErSa4cB1Zd3GeqPVU8B。 - Baz1nga
你可以通过 StringBuilder sb = new StringBuilder(10000000 * 1); 更好地处理大量数据。对于大型集合,使用(初始)容量总是一个好主意。 - H H
1个回答

40
每个字符需要2个字节(在.NET中作为char是一个UTF-16代码单元)。因此当您达到8亿个字符时,就需要1.6GB的连续内存1。现在,当StringBuilder需要重新调整大小时,它必须创建一个新大小的另一个数组(我认为它会尝试将容量加倍),这意味着尝试分配一个3.2GB的数组。
我认为CLR(即使在64位系统上)也无法分配超过2GB大小的单个对象。(这确实曾经是这种情况。) 我猜测您的StringBuilder正在尝试增加大小,并且超出了这个限制。您可以通过使用特定容量来构建StringBuilder来获得更高的容量——容量约为10亿可能是可行的。
在正常情况下,这不是问题,当然,甚至需要几百兆的字符串也很少见。

1 我相信StringBuilder的实现在.NET 4中实际上已经更改,在某些情况下使用片段 - 但我不知道细节。因此,在仍处于builder形式时,它可能不需要连续的内存...但如果您调用了ToString,它就需要。


3
我认为之所以StringBuilder需要连续的内存来分配其内容,是因为根据机器之前的操作方式,内存可能会以不同的方式被碎片化。即使仍然有很多物理RAM可用,但由于没有足够的连续内存,仍可能会出现OutOfMemory异常。 - Neil Fenwick
1
@jon 我同意,但是让我感到奇怪的是,那个人说在表面上配置相同的机器上得到了非常不同的结果。但你实际上证实了我的疑虑,即机器的平等性。 - Tigran
我在我的机器上注意到了一件事情。当我使用VS2008(.Net 3.5)运行它时,循环变量(i)的较小值会引发异常,而在使用VS2010(.Net 4.0)时则不会。 - Atur
1
@atur:没错 - 这与我的脚注相对应 - StringBuilder 的实现在 .NET 4 中已经改变。 - Jon Skeet
我尝试的原始示例是在 .Net 4.0 中,它能够处理高达8亿的值。而 .Net 3.5 在处理1/3的值时就会崩溃。 - Atur
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接