1. 场景
需要后台生成excel,数据量可能有30万行以上,使用之前的poi 代码可能会报 OOM。
2. 解决办法
减少内存占用,将生成的excel同步写入本地文件中。
具体实现方式可以使用 easy poi 或者 poi 中的 SXSSF。
easy poi 不够灵活,此处使用的是 SXSSF。
poi 分类
POI提供了HSSF、XSSF以及SXSSF三种方式操作Excel。
HSSF:Excel97-2003版本,扩展名为.xls。一个sheet最大行数65536,最大列数256。
XSSF:Excel2007版本开始,扩展名为.xlsx。一个sheet最大行数1048576,最大列数16384。
SXSSF:是在XSSF基础上,POI3.8版本开始提供的支持低内存占用的操作方式,扩展名为.xlsx。
Excel版本兼容性是向下兼容。
3 . 生成大文件excel
有第一点可知,如果excel数据量很大的话,需要使用 SXSSFWorksheet生成excel,
具体代码如下:
import junit.framework.Assert;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
public static void main(String[] args) throws Throwable {
// 配置SXSSFWorksheet内存中的数据,其余的实时写入本地文件;
// 100标识内存中存放数据的条数;默认为100;
// 为-1时表示全部在内存处理;
SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
Sheet sh = wb.createSheet();
for(int rownum = 0; rownum < 1000; rownum++){
Row row = sh.createRow(rownum);
for(int cellnum = 0; cellnum < 10; cellnum++){
Cell cell = row.createCell(cellnum);
String address = new CellReference(cell).formatAsString();
cell.setCellValue(address);
}
}
// 小于900 的应该都被放入了本地文件;
// 所以现在获取不到小于900的行,下面的断言会触发;
// Rows with rownum < 900 are flushed and not accessible
for(int rownum = 0; rownum < 900; rownum++){
Assert.assertNull(sh.getRow(rownum));
}
// 大于900 的没有被放入了本地文件;
// 所以现在可以获取到大于900的行,下面的断言会触发;
// ther last 100 rows are still in memory
for(int rownum = 900; rownum < 1000; rownum++){
Assert.assertNotNull(sh.getRow(rownum));
}
FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
wb.write(out);
out.close();
// 销毁本地临时文件
// dispose of temporary files backing this workbook on disk
wb.dispose();
}
参考:官方文档
4. 实例
此处需要将生成的大文件上传导 S3,某些简单方法略。
@SneakyThrows
public void executeNoLogoExcelDownload(DownloadCenterEntity downloadCenterEntity) {
String fileName = downloadCenterEntity.getFileName();
File file = new File(fileName);
if (!file.exists()) {
file.createNewFile();
}
try {
generateNoLogoExcelBytes(fileName);
} catch (Exception e) {
e.printStackTrace();
asyncDownloadEnd(downloadCenterEntity,file,RESULT_FAILED);
disposeFile(file);
return;
}
asyncDownloadEnd(downloadCenterEntity,file,RESULT_SUCCESS);
disposeFile(file);
}
public void generateNoLogoExcelBytes(String excelUri) {
logoNumber = 0;
init();
workbook = new SXSSFWorkbook(SHEET_WINDOW_SIZE);
sheet = workbook.createSheet(excelName);
excelUtil.createSheet(sheet, workbook, excelName, header, data, paramColumnIndexMap, adhocExcelDTO);
try (FileOutputStream out = new FileOutputStream(excelUri)) {
workbook.write(out);
} catch (IOException e) {
e.printStackTrace();
}
}
private void disposeFile(File file) {
workbook.dispose();
if (file.exists()) {
file.delete();
}
}
@SneakyThrows
@Override
public void asyncDownloadEnd(DownloadCenterEntity entity,File file,int result) {
String netUrlPath = S3Utils.upload(file, "/DOWNLOAD/" +file.getName());
entity.setStatus(result);
entity.setUrl(netUrlPath);
downloadCenterService.updateById(entity);
}
public static String upload(File file, String s3Path) throws IOException {
Bucket bucket = conn.createBucket(s3ConfigProperties.getBucketName());
FileInputStream fis = new FileInputStream(file);
s3Path = s3Path.replace("\\", "/");
if (s3Path.indexOf(":") > 0) {
s3Path = s3Path.substring(s3Path.indexOf(":") + 1);
}
if (s3Path.startsWith("/")) {
s3Path = s3Path.substring(1);
}
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentEncoding("UTF-8");
objectMetadata.setContentLength((long)fis.available());
objectMetadata.setHeader("x-amz-acl", "public-read");
conn.putObject(bucket.getName(), s3Path, fis, objectMetadata);
return s3Path;
}