使用内存映射文件MMF实现大数据量导出时的内存优化
前言
导出功能几乎是所有应用系统必不可少功能,今天我们来谈一谈,如何使用内存映射文件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); } } } mmfs = null; }
运行效果:
* 这里有个需要注意的点是,如果不在外部引用mmf,如果创建多个mmf,只有最新一个能通过openexisting方法打开,其他的都报system.io.filenotfoundexception,猜测是资源被释放掉了
* 还有个需要注意的点是,一定要计算好数据体积,不要超过mmf上限,使用accessor的时候也要注意,数据量实在太大可以考虑将一个sheet拆成多个mmf,或者将一行数据拆成多个accessor
这样就可以实现从数据库获然后处理再存储到载体的流程,整个过程中内存使用控制在一个比较低的水平,当然,这是使用时间换空间,相应的导出时间会延长
顺便说一下,原本考虑后续使用epplus进行excel生成,后来发现npoi也和poi一样有sxssfworkbook对象,可以流式读取数据,配合内存映射文件可以实现整个导出过程
相关连接参考:
下一篇: 关于Java调用接入微信、支付宝支付提现