【问题标题】:Why can I upload a file, but not download it without getting an Out of Memory Exception?为什么我可以上传文件,但无法下载却没有出现内存不足异常?
【发布时间】:2014-06-20 08:40:12
【问题描述】:

我的软件有一项功能,允许用户上传以字节字符串形式存储在我的数据库中的文件。
代码是这样工作的:

计算文件中的字节数,并创建一个具有该长度的字节数组。然后将文件读入字节数组。从这里,文件被分成几部分并存储在我的数据库中。这是通过使用以下代码完成的:

openFile1 = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
blah, blah, blah
.
.
.
openFileBytesNumber = openFile1.Length;
byteArray = new byte[openFileBytesNumber];
openFile1.Read(byteArray, 0, (int)(openFileBytesNumber));
fullByteString = Convert.ToBase64String(byteArray);

当我下载它时,会发生以下情况:

通过从数据库加载文件数据并连接所有片段来创建一个字节字符串。使用以下代码行将字符串放入字节数组中:

byteArray = Convert.FromBase64String(byteString.ToString());

这是我得到内存不足异常的地方。为什么我在下载时收到 OutOfMemoryException,但在上传时却没有?

【问题讨论】:

  • 什么是byteString 以及如何连接这些片段?
  • .NET 需要分配一个连续的内存块。如果文件很大,可能是因为它没有为第二次分配找到足够的连续内存。将文件存储为 Base64 字符串非常浪费。它应该存储在二进制列中。
  • @Guffa, StringBuilder byteString = new StringBuilder(); for (int i = 0; i
  • 为什么我在下载时收到 OutOfMemoryException,但在上传时却没有?您的意思是您总是收到 OOM 错误,无论如何文件大小? 我们在这里谈论的是什么尺寸
  • @SeanSmyth 你能把 byteString 分解成块并以这种方式解码吗?你有什么理由需要记忆中的全部内容? (换句话说,不要组装,只需一次解码。)

标签: c# .net file exception out-of-memory


【解决方案1】:

Base64 有 30% 的开销,而字符串有 100% 的开销(因为 char 是 16 位)。将数据作为 base64 字符串保存在内存中会产生 160% 的开销 (1.3 * 2.0 = 2.6)。

您将DataSet 中的所有数据与StringBuilder 以及最终字符串以及最终结果一起保存在内存中。总开销为 780% (3 * 2.6 + 1 = 8.8)。

代码需要大约 9 倍于文件大小的内存。

要减少内存使用量,您可以:

  • 使用数据阅读器而不是数据集。这样一来,您在内存中一次只有一条记录。
  • 在每条记录上执行从 base64 到字节的转换,而不是构建整个字符串。如果字符串长度可以被 4 整除,则可以做到这一点。

旁注:您在读取文件时使用了错误的Read 方法。 Read 方法返回读取的字节数,可能小于请求的字节数。您需要从方法中获取返回值并重复调用,直到获得所有数据:

int openFileBytesNumber = (int)openFile1.Length;
int len = 0;
while (len < openFileBytesNumber) {
  int l = openFile1.Read(byteArray, len, openFileBytesNumber - len);
  if (l == 0) {
    // unexpended end of file error - better handle that
  }
  len += l:
}

【讨论】:

    【解决方案2】:

    看起来当您查询数据库时,您正在通过 DataSet 将整个结果集存储在内存中。考虑使用 SqlDataReader(或数据库的 IDataReader)而不是 SqlDataAdapter 和 DataSet。这将允许您一次在内存中拥有 1 行,而不是整个结果集。

    【讨论】:

      【解决方案3】:

      您的FileStream 和任何其他实现IDisposable 的对象需要位于using 块中:

      using (var openFile1 = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
      {
          blah, blah, blah
          .
          .
          .
          openFileBytesNumber = openFile1.Length;
          byteArray = new byte[openFileBytesNumber];
          openFile1.Read(byteArray, 0, (int)(openFileBytesNumber));
          fullByteString = Convert.ToBase64String(byteArray);
      }
      

      【讨论】:

        猜你喜欢
        • 2015-07-11
        • 2012-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多