zoukankan      html  css  js  c++  java
  • 使用内存映射文件MMF实现大数据量导出时的内存优化(Windows篇)

    前言

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

    实现

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

              MemoryMappedFile --表示内存映射文件

              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[]数组,而数组必须指定长度,我们将数组长度和具体数据都存起来,这样取数据时候的索引也可以计算出来了

    数据存储示例:

     这面是具体的实现代码:

            //添加外部引用防止被自动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);
                        }
                    }
                }
            }

    运行效果:

              * 这里有个需要注意的点是,如果不在外部引用mmf,如果创建多个mmf,只有最新一个能通过OpenExisting方法打开,其他的都报System.IO.FileNotFoundException,猜测是资源被释放掉了

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

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

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

    相关连接参考:

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

    https://www.cnblogs.com/flyant/p/4443187.html

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

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

  • 相关阅读:
    IBM Thread and Monitor Dump Analyzer for Java解决生产环境中的性能问题
    ORACLE中的字符串替换 replce、regexp_replace 和 translate
    ORA-01654 索引 无法通过 表空间扩展
    HTML篇之CSS样式:<button></button>按钮变成超链接<a></a>的样式
    HTML里用如何包含引用另一个html文件 .
    java程序中实现打开 某个指定浏览器
    Oracle查询数据库中所有表的记录数
    getOutputStream() has already been called for this response解释以及解决方法
    oracle索引,索引的建立、修改、删除
    各种组件的js 获取值 / js动态赋值
  • 原文地址:https://www.cnblogs.com/y-yp/p/12191258.html
Copyright © 2011-2022 走看看