使用内存映射文件MMF实现大数据量导出时的内存优化

前言

     导出功能几乎是全部应用系统必不可少功能,今天咱们来谈一谈,如何使用内存映射文件MMF进行内存优化,本文重点介绍使用方法,相关原理能够参考文末的链接html

实现

     咱们以单次导出一个excel举例(csv同理),excel包含1~n个sheet,在每一个sheet中存储的按行和列的坐标在单元格存储具体数据,若是咱们要使用MMF,第一个要考虑的就是如何将整个excel合理的存储到MMF中。这里咱们引入MMF两个对象:数据库

          MemoryMappedFile --表示内存映射文件api

          MemoryMappedViewAccessor --表示随机访问的内存映射文件视图数组


    使用MemoryMappedFile.CreateNew(string mapName, long capacity)能够获得一个指定名称和指定大小的内存映射文件,如下简称mmf
          * 这里须要注意的是capacity为long类型,以字节为单位,经过计算可知文件大小上限为1G
    使用mmf.CreateViewAccessor(long offset, long size)能够获得一个从指定位置开始的指定大小空间的访问器,如下简称accessor
          * 这里一样须要注意的是size的大小,若是加上offset超过文件大小,会报System.UnauthorizedAccessException: Access to the path is denied.
    考虑到数据体积和管理成本,这里使用mmf对应sheet使用accessor对应一行数据
          * 这里有个须要注意的是mmf不能存储引用类型,包括字符串...,折衷先将string转为char[]而后使用WriteArray方法存储,考虑到取数据的时候一样须要使用char[]数组,而数组必须指定长度,咱们将数组长度和具体数据都存起来,这样取数据时候的索引也能够计算出来了app

数据存储示例:测试

 

 

 这面是具体的实现代码:优化

        //添加外部引用防止被自动GC
        public List<MemoryMappedFile> mmfs = new List<MemoryMappedFile>();

        /// <summary>
        /// 写入
        /// </summary>
        public void WriteMMF()
        {
            for(var f = 1; f <= 3; f ++)
            {
                //每个File至关于一个excel的一个sheet(一个患者一行)
                var mmf = MemoryMappedFile.CreateNew($"mmftest{f}", 1024 * 1024 * 1024); //文件大小最大为1G

                for (var row = 0; row < 10; row++)
                {
                    //每个ViewAccessor至关于excel的一行
                    var accessor = mmf.CreateViewAccessor(row * 1024 * 1024, 1024 * 1024); //经过具体数据量计算空间,offset位移为每一个size的长度,size为1M
                    var index = 0;
                    for (var i = 0; i < 16384; i++)
                    {
                        //至关于一行的每个cell
                        var buffer = ASCIIEncoding.UTF8.GetBytes($"测试第{row}行第{i}个单元格~!");
                        var length = buffer.Length;
                        accessor.Write(index, length);
                        accessor.WriteArray(index + 4, buffer, 0, length);
                        index += (length + 4);
                    }
                }
                mmfs.Add(mmf);
            }
        }

        /// <summary>
        /// 读取
        /// </summary>
        public void ReadMMF()
        {
            for (var f = 1; f <= 3; f++)
            {
                using var mmf = MemoryMappedFile.OpenExisting($"mmftest{f}");
                for (var row = 0; row < 10; row++)
                {
                    using var accessor = mmf.CreateViewAccessor(row * 1024 * 1024, 1024 * 1024); //经过写数据同时作的记录控制大小
                    var index = 0;
                    for (var i = 0; i < 16384; i++)
                    {
                        var size = accessor.ReadInt32(index);
                        var buffer = new byte[size];
                        accessor.ReadArray(index + 4, buffer, 0, size);
                        var result = ASCIIEncoding.UTF8.GetString(buffer);
                        Console.WriteLine(result);
                        index += (size + 4);
                    }
                }
            }

            mmfs = null;
        }

运行效果:spa

          * 这里有个须要注意的点是,若是不在外部引用mmf,若是建立多个mmf,只有最新一个能经过OpenExisting方法打开,其余的都报System.IO.FileNotFoundException,猜想是资源被释放掉了excel

          * 还有个须要注意的点是,必定要计算好数据体积,不要超过mmf上限,使用accessor的时候也要注意,数据量实在太大能够考虑将一个sheet拆成多个mmf,或者将一行数据拆成多个accessorcode

    这样就能够实现从数据库获而后处理再存储到载体的流程,整个过程当中内存使用控制在一个比较低的水平,固然,这是使用时间换空间,相应的导出时间会延长

    顺便说一下,本来考虑后续使用epplus进行excel生成,后来发现npoi也和poi同样有SXSSFWorkbook对象,能够流式读取数据,配合内存映射文件能够实现整个导出过程

 

相关链接参考:

https://docs.microsoft.com/en-us/dotnet/api/system.io.memorymappedfiles?view=netframework-4.8 

http://www.javashuo.com/article/p-sujzujhg-cn.html

https://www.bygeek.cn/2018/05/24/understand-memory-mapped-file/

https://stackoverflow.com/questions/10806518/write-string-data-to-memorymappedfile

相关文章
相关标签/搜索