目录
业务场景
原因分析
解决思路
优化后效果
实现代码DEMO
业务场景
由于很多业务需要导出数据库里的数据,一般我们导出的数据都是要给业务部门看的,他们也会拿到做一些数据统计,所以一般都是给他们导出Excel格式的数据文件,但是当我导出五十万条数据时遇到了两个问题:
导出时一般使用POI工具包,这时很容易导致内存溢出
导出时间很慢,很容易导致超时
下面是直接导出时内存占用.
原因分析
由于Java对象是封装型对象,所以内存中对象大小是实际数据的好几倍,所以50W条的数据,最少要有50W个对象,再加上我们可能使用map对象或者JsonObject对其进行一些数据操作,所以内存中保守会有100W个对象,可能要占用至少2G的内存大小。
POI工具为了加快速度,采用把文件全部读到内存中的形式操作文件,这导致如果要操作一个500M的Excel文件将至少占用500M的内存。
这导致内存维持在一个很高的水平
解决思路
1.减少内存中存在的封装类型对象个数
第一,这个我们可以使用分页查询的形式进行查询;
第二我们尽量使用String和int类型存储数据
2.替换POI操作文件形式,使用文件流形式写数据,但是Excel是有严格格式的文件,直接采用文本形式写入流根本无法用Excel形式打开,这时候我们想到了一个文件格式,那就是 csv格式的文件,本质上是文本格式,但是可以用Excel形式打开,并保存为Excel格式。于是我们测试了一下
优化后效果
当每次查询1000条,每次查出来这1000条往文件写一次,查询1000次时内存变化如下:
可以看出内存最多占用350M,不会再往上增加,但是分页次数太多,所以整体时间太长
当每次查询50000条,每一条写一次时内存变化如下:
可以看到内存涨到800M时就不会再增加了,我们可以适当的调整每页的大小,直到我们能够接受的内存大小和总体时间就可以了。
另外写入文件和数据库查询是IO操作,耗时的操作,所以我们也可以通过判断,在适合的时机查询和写入,适当的减少写入次数来提升整体速度。平衡时间和内存的使用,一般时间使用短了,内存使用就会变大。
实现代码DEMO
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:cn/xdf/wlyyb/spring-context.xml"})
public class TestExportLog {
@Autowired
private CheckInOutDaoTest checkInOutDaoTest;
@Test
public void export() throws IOException {
long startTime = System.currentTimeMillis();
File file = new File("e:/log.csv");
if (!file.exists()) {
file.createNewFile();
}else {
file.delete();
file.createNewFile();
}
//声明文件流
FileWriterWithEncoding writer = new FileWriterWithEncoding(file, "gbk");
StringBuffer content = new StringBuffer();
for (int i = 1; i < 1000; i ) {
PageUntil page = new PageUntil();
//通过页大小调整查询次数
page.setPageSize(100000);
page.setPageNum(i);
List<CheckInOut> logList = checkInOutDaoTest.getListByPage( page);
for (int j = 0; j < logList.size(); j ) {
CheckInOut logs = logList.get(j);
content.append(logs.getCheckDate() "," logs.getChecktime() "," logs.getCreat_time() "," logs.getSn() ","
logs.getUid() "," logs.getUserCode() "," logs.getMachineid() "," logs.getSensorid() ",很长很长的文本很长很长的文本很长很长的文本很长很长的文本很长很长的文本很长很长的文本很长很长的文本很长很长的文本很长很长的文本" "\r\n");
//文本大小达到一定长度,往硬盘上写一次,清空其内存占用
if (content.length()> 3*1024*1024) {
//追加文本内容
writer.write(content.toString());
content = new StringBuffer();
}
}
// System.out.println("第" i "页" content);
System.out.println("第" i "页");
if (StringUtils.isBlank(content)) {
break;
}
}
追加文本内容
writer.write(content.toString());
//关闭流
writer.close();
System.out.println("用时:" (System.currentTimeMillis()-startTime));
}
}