将文件加载到位图中但保留原始文件的完整性。

7
如何在C#中实现这个功能?
如果使用Bitmap.FromFile(),则原始文件会被锁定。
如果使用Bitmap.FromStream(),原始文件不会被锁定,但文档中指出:“您必须在Image的生命周期内保持流处于打开状态。”这可能意味着文件仍然与图像对象链接(例如,如果文件更改,则对象也会更改,反之亦然)。
我想要做的就是读取位图并将其保存到一个对象中,之后文件和图像对象之间就没有任何链接了。
4个回答

26

关于这种行为的一些背景信息:Bitmap使用内存映射文件来访问位图中的像素。这是Windows API中非常基本的功能,它允许将内存与文件数据进行高效映射。只有在程序读取内存时才从文件中读取数据,虚拟内存页面不占用Windows分页文件中的任何空间。

完全相同的机制用于加载.NET程序集。正是内存映射导致文件被锁定。这基本上就是为什么在.NET程序中使用程序集时会被锁定的原因。Image.Dispose()方法释放锁定。对抗锁定通常表明您忘记了处理位图。非常重要的是,忘记调用Dispose()通常不会对.NET类造成问题,除了Bitmap之外,因为它可能需要大量(非托管)内存。

是的,FromStream()防止该类进行此优化。代价很大,当位图加载时,您需要双倍的内存。当位图很大时,这将是一个问题,当程序运行一段时间后(碎片化地址空间)并且它没有运行在64位操作系统上时,您正在绕过OOM。如果位图的Width x Height x 4 >= 45 MB左右,请务必避免这样做。

这里有一些代码,您不必跳过CopyStream的步骤:

    public static Image LoadImageNoLock(string path) {
        var ms = new MemoryStream(File.ReadAllBytes(path)); // Don't use using!!
        return Image.FromStream(ms);
    }

请注意,您不希望释放MemoryStream,否则在使用位图时会出现难以诊断的“通用错误”。这是由于Image类延迟读取流所导致的。

你还需要这个来获取位图。Bitmap bitmap = new Bitmap(LoadImageNoLock(path)); - Elijah
2
MemoryStream是可释放的,谁会在这个解决方案中负责处理它的释放? - kwesolowski

4

通过将文件从FileStream复制到MemoryStream中,将文件读入内存。(在Stack Overflow上搜索CopyStream以找到许多安全地执行此操作的示例。基本上,在读取时循环,将每个块写入内存流,直到没有更多数据可读为止。) 然后倒回MemoryStream (设置Position = 0),然后将其传递给Bitmap.FromStream


4
为了创建一张图像而不锁定文件,您需要创建图像的FileStream的副本。请参考此页面 Best way to copy between two Stream instances - C# 了解如何复制流。
之后,只需从复制的流中创建您的图像即可开始使用。

你为我们做了Jon建议的搜索,真是太棒了... 你找到了一篇非常好的文章。 - awe

0
我经常使用将数据复制到MemoryStream,然后将MemoryStream提供给Bitmap.FromStream的技术。但是,这种技术也有一个需要注意的地方。
如果您计划稍后使用Bitmap.Save方法保存已加载的图像之一,则必须保持流处于活动状态(即,在加载图像后不要释放它),否则您将会遇到可怕的“发生了通用 GDI+ 错误”的异常!

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