7

多次使用 MemoryStream 时遇到问题。

例子:

For Each XImage As XImage In pdfDocument.Pages(pageCount).Resources.Images
   Dim imageStream As New MemoryStream()
   XImage.Save(imageStream, System.Drawing.Imaging.ImageFormat.Jpeg)

   ' some further processing

   imageStream.Close()
   imageStream.Dispose()    
Next

这段代码循环浏览 PDF 文件页面上的图像。该文件最多可能有 500 页,例如每页 5 张图像。它会导致数千次迭代。问题是 MemoryStream 没有被释放,它会导致 Out of Memory 异常。XImage 通常有 250 kB 左右。

我在这里使用 Aspose.PDF 库来处理 PDF(XImage 是这个库中的一个类),但这没关系。我试着做一个简单的例子,我只是创建一个新的 MemoryStream 并将一个虚拟位图保存到它。它会导致同样的问题。

我也尝试使用 FileStream 而不是 MemoryStream 但它的行为相同。

任何帮助表示赞赏。

谢谢

吉里

4

1 回答 1

21

流中的内存被释放。我答应你。真的,是的。

没有释放的是应用程序中以前被该内存占用的地址空间。您的计算机有大量可用的 ram,但您的特定应用程序崩溃了,因为它无法在地址表中找到要分配的位置。

您达到限制的原因是 MemoryStream 会随着其增长而回收其缓冲区。它在内部使用 byte[] 来保存其数据,并且默认情况下将数组初始化为一定的大小。当您写入流时,如果超出数组的大小,流将使用加倍算法来分配新数组。然后将信息从旧数组复制到新数组。在此之后,旧数组可以并且将被收集,但不会被压缩(想想:碎片整理)。结果是程序的虚拟地址表中的漏洞不再足以容纳您的 MemoryStream 缓冲区。一个 MemoryStream 可能会使用多个数组,从而导致多个内存空洞的总地址空间可能比源数据大得多。

AFAIK,目前无法强制垃圾收集器压缩您的内存地址空间。因此,解决方案是分配一个可以处理最大图像的大块,然后一遍又一遍地重用同一个块,这样就不会出现无法访问的内存地址。

对于此代码,这意味着在循环之外创建 MemoryStream,并将整数传递给构造函数,以便将其初始化为合理的字节数。您会发现这也给您带来了不错的性能提升,因为您的应用程序突然不再花时间频繁地将数据从一个字节数组复制到另一个字节数组,这意味着即使您可以压缩地址表,这也是更好的选择:

Using imageStream As New MemoryStream(307200) 'start at 300K... gives you some breathing room for larger images
    For Each XImage As XImage In pdfDocument.Pages(pageCount).Resources.Images

       'reset the stream, but keep using the same memory
       imageStream.Seek(0, SeekOrigin.Begin)
       imageStream.SetLength(0)

       XImage.Save(imageStream, System.Drawing.Imaging.ImageFormat.Jpeg)

       ' some further processing
   
    Next
End Using
于 2013-06-25T20:16:35.787 回答