背景
- 前一段时间遇到一种情况,服务器经常宕机,而且没有规律性,查看GC日志发生了out of memory,是堆溢出导致的,分析了一下堆的dump文件,发现在发生OOM时创建了大量的String对象。最后对照时间点,发现宕机的时候业务人员在上传一个excel文件,但是这个excel文件才28MB大小,感觉应该不会引起内存溢出。后来在本地启动了服务,然后尝试上传这个excel文件,同时使用Java VisualVM监控GC情况,发现在上传的时候,创建了大量的String对象,后来老年代没有可分配空间导致了OOM。最终分析结果是,excel文件中存在几十万的空行数据,表面上看,这些空行数据跟不存在数据的行是一样的,但是POI会把这种空行数据读入到内存中,感觉这也是一个坑。
- 在网上搜了很长时间,发现国内网站上的解决方案真是没法看,基本上答案都差不多,没有什么有见解性的解决方法,后来在stackoverflow上找到了解决方法。算是给自己做一下备注,也想帮助一些还在坑里的人,就分享一下,只是自己的见解,有不得当的地方也请见谅。
- 原先获取方法
/**
* 获取execle 文件
*
* @param fileName
* @return
*/
public static Workbook getWookBook(String fileName) {
Workbook workbook = null;
FileInputStream fileInputStream = null;
try {
File file = new File(fileName);
if (!file.exists()) {
System.out.println("模板文件:" + fileName + "不存在!");
}
fileInputStream = new FileInputStream(fileName);
workbook = WorkbookFactory.create(fileInputStream);
} catch (IOException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
} finally {
CloseableUtils.close(fileInputStream);
}
return workbook;
}
-
使用Excel Streaming Reader,这个第三方工具会把一部分的行(可以设置)缓存到内存中,在迭代时不断加载行到内存中,而不是一次性的加载所有记录到内存,这样就可以不断的读取excel内容并且不影响内存的使用。
-
但是这个工具也有一定的限制:只能用于读取excel的内容,写入操作不可用;可以使用getSheetAt()方法获取到对应的Sheet,因为当前只是加载了有限的row在内存中,因此不能随机访问row,即不能使用getRow(int rowNum)方法;由于行数据已经加载到了内存,因此可以随机的访问Cell数据,即可以使用getCell(int cellnum)方法。使用这个工具,建议使用迭代器来进行迭代。具体内容可以参见:https://github.com/monitorjbl/excel-streaming-reader。
在pom.xml文件中引入需要的jar包:
<dependency>
<groupId>com.monitorjbl</groupId>
<artifactId>xlsx-streamer</artifactId>
<version>1.2.0</version>
</dependency>
@Test
public void testLoad() throws Exception{
FileInputStream in = new FileInputStream("e:/2.xlsx");
Workbook wk = StreamingReader.builder()
.rowCacheSize(100) //缓存到内存中的行数,默认是10
.bufferSize(4096) //读取资源时,缓存到内存的字节大小,默认是1024
.open(in); //打开资源,必须,可以是InputStream或者是File,注意:只能打开XLSX格式的文件
Sheet sheet = wk.getSheetAt(0);
//遍历所有的行
for (Row row : sheet) {
System.out.println("开始遍历第" + row.getRowNum() + "行数据:");
//遍历所有的列
for (Cell cell : row) {
System.out.print(cell.getStringCellValue() + " ");
}
System.out.println(" ");
}
}
- 最终写法
/**
* 获取execle 文件
*
* @param fileName
* @return
*/
public static Workbook getWookBook(String fileName) {
Workbook workbook = null;
if (VerifyUtil.isEmpty(fileName)) return workbook;
boolean b = fileName.endsWith(".xlsx");
if (b) {
return getXlsx(fileName);
}
FileInputStream fileInputStream = null;
try {
File file = new File(fileName);
if (!file.exists()) {
System.out.println("模板文件:" + fileName + "不存在!");
}
fileInputStream = new FileInputStream(fileName);
workbook = WorkbookFactory.create(fileInputStream);
} catch (IOException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
} finally {
CloseableUtils.close(fileInputStream);
}
return workbook;
}
/**
* @return
* @throws
* @Description 解析xlsx 大文件类型
* @author liuding
* @date 2019/4/10 0010 08:57
*/
public static Workbook getXlsx(String fileName) {
FileInputStream in = null;
try {
System.out.println("文件:" + fileName);
in = new FileInputStream(fileName);
System.out.println("文件大小:" + String.valueOf(in.available()) + "--" + in.available() / 1024 / 1024);
Workbook workbook = StreamingReader.builder()
.rowCacheSize(100) //缓存到内存中的行数,默认是10
.bufferSize(4096) //读取资源时,缓存到内存的字节大小,默认是1024
.open(in); //打开资源,必须,可以是InputStream或者是File,注意:只能打开XLSX格式的文件
return workbook;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}