C# 图片加载引发的内存溢出异常

在c#中,使用下面代码将图片读取到内存,发现内存暴涨。

public static System.Windows.Media.Imaging.BitmapImage ByteArrayToBitmapImage(this byte[] array)
{
    using (var ms = new System.IO.MemoryStream(array))
    {
        try
        {
            var image = new System.Windows.Media.Imaging.BitmapImage();
            image.BeginInit();
            image.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad; // here
            image.StreamSource = ms;
            image.EndInit();
            image.Freeze();
            return image;
        }
        catch (Exception ex)
        {
            LogHelper.Error(ex);
        }
        return null;
    }
}

使用托管堆内存查看工具,发现托管堆增长的大小和图片物理空间基本一致;那么暴涨出来的那部分大小在哪儿呢?大概率是开辟了非托管内存!

图片加载到内存会占用多少内存?

答案:不一定,因为需要看你的处理方式。如上面的代码,并未进行额外处理,系统会进行下面的处理:

  • 将300kb的数据读入内存
  • 以原始分辨率进行解码,解码后会变成未压缩的位图,公式为:宽度×高度×每像素字节数 。举个例子:假如你的图片分辨率为 1920*1080 ;那么解码后占用内存可能为 1920*1080*4 ≈ 7.9MB在bitmapiamge内部开辟的是非托管内存。故用托管内存分析工具还看不到这段内存。
  • 如果这个图片用于WPF的Image控件展示,还会有额外的内存开支。

如何正确处理图片以应对内存占用?

设想在这些场景中:展示头像,展示在列表行中...,图片真的要用非压缩的形式展示给用户么?
答案是:不需要,而是要设置解码宽度,拿WPF的Image控件来说,解码宽度设置为你的Image的真实宽度--ActualWidth。

// 在你的方法中添加信息输出
public static BitmapImage ByteArrayToBitmapImage(this byte[] array, int iPicWid = 0 )
{
    using (var ms = new MemoryStream(array))
    {
        try
        {
            var image = new BitmapImage();
            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.StreamSource = ms;
            
            // 设置解码宽度 避免以无损方式解码
            if (iPicWid > 0) image.DecodePixelWidth = iPicWid ;

            image.EndInit();
            image.Freeze();
            
            // 调试信息
            #if DEBUG
            PrintImageInfo(image);
            #endif
            
            return image;
        }
        catch (Exception ex)
        {
            LogHelper.Error(ex);
            return null;
        }
    }
}

// 在调试时输出详细信息
public static void PrintImageInfo(BitmapImage image)
{
    Console.WriteLine("=== 图像信息 ===");
    Console.WriteLine($"宽度: {image.PixelWidth} 像素");
    Console.WriteLine($"高度: {image.PixelHeight} 像素");
    Console.WriteLine($"格式: {image.Format}");
    Console.WriteLine($"每像素位数: {image.Format.BitsPerPixel}");
    
    int bytesPerPixel = (image.Format.BitsPerPixel + 7) / 8;
    Console.WriteLine($"每像素字节数: {bytesPerPixel}");
    
    long totalBytes = image.PixelWidth * image.PixelHeight * bytesPerPixel;
    Console.WriteLine($"总内存占用: {totalBytes} 字节 ({totalBytes / 1024.0 / 1024.0:F2} MB)");
    
    // 如果是文件加载的,还可以显示原始文件大小
    if (image.StreamSource != null)
    {
        Console.WriteLine($"压缩格式: 已压缩");
    }
}

我拿了一个300kb大小的图片,不设置解码宽度的情况下,打印了理论内存占用,代码如下:
ByteArrayToBitmapImage(buffer);
输出信息如下:

=== 图像信息 ===
宽度: 2560 像素
高度: 1440 像素
格式: Bgr32
每像素位数: 32
每像素字节数: 4
总内存占用: 14745600 字节 (14.06 MB)
压缩格式: 已压缩

参考

WPF 解决Image控件读取高分辨率图片并缩放占用内存过大
How to: Use a BitmapImage
WPF的BitmapImage的文件无法释放及内存泄露的问题

posted @ 2025-11-27 20:07  BigBosscyb  阅读(8)  评论(0)    收藏  举报