前言导出功能几乎是所有应用系统必不可少功能,今天我们来谈一谈,如何使用内存映射文件MMF进行内存优化,本文重点介绍使用方法,相关原理可以参考文末的连接 实现我们以单次导出一个excel举例(csv同理),excel包含1~n个sheet,在每个sheet中存储的按行和列的坐标在单元格存储具体数据,如果我们要使用MMF,第一个要考虑的就是如何将整个excel合理的存储到MMF中。这里我们引入MMF两个对象: MemoryMappedFile --表示内存映射文件 MemoryMappedViewAccessor --表示随机访问的内存映射文件视图
数据存储示例: 这面是具体的实现代码: //添加外部引用防止被自动GCpublic 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); //文件大小最大为1Gfor (var row = 0; row < 10; row++) {//每一个ViewAccessor相当于excel的一行var accessor = mmf.CreateViewAccessor(row * 1024 * 1024, 1024 * 1024); //通过具体数据量计算空间,offset位移为每个size的长度,size为1Mvar index = 0;for (var i = 0; i < 16384; i++) {//相当于一行的每一个cellvar 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对象,可以流式读取数据,配合内存映射文件可以实现整个导出过程 |
|