本文由黑壳博客发布
本文来源 Java 大数据量高性能 Excel 导出 - 黑壳网
壳叔搞笑时间
单位开会,主任说了快一个小时了,我都睡着了,突然被主任点名叫了起来。
睡得迷迷糊糊的我,嘟囔了一句:就你有嘴,一天叭叭的!
正文
前段日子,公司的电商后台导出会员和订单数据突然把 tomcat 崩掉了。
查看异常日志
然后看了一下服务器的 tomcat 的日志,tomcat 报异常为:
java.lang.OutOfMemoryError: Java heap space
意思为内存泄露,看了下代码又问了下之前写这个的同事。
最终确定问题
导出 Excel 没做缓存处理,将导出的数据全部堆在内存里,导致内存泄露了。
解决方案
由于之前代码运行了很久,并且不能轻易修改,所以打算重写这一份 Excel 导出代码。
同样是采用 Apache-poi 插件
代码部分
用于导入 jar 文件
pom.xml 文件
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.10-FINAL</version>
</dependency>
工具类 重点代码示例
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Created by kzyuan on 16/12/5.
*/
public class ExportExcel {
private static final Logger LOGGER = LoggerFactory.getLogger(ExportExcel.class);
public static void generateExcel(HttpServletResponse response, Map<String, Object[]> data, String fileName, String sheetName, int[] columnWidths) throws IOException {
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet(sheetName);
for (int i = 0; i < columnWidths.length; ++i) {
sheet.setColumnWidth(i, columnWidths[i] * 256);
}
//Iterate over data and write to sheet
Set<String> keyset = data.keySet();
int rownum = 0;
for (String key : keyset) {
Row row = sheet.createRow(rownum++);
Object[] objArr = data.get(key);
int cellnum = 0;
for (Object obj : objArr) {
Cell cell = row.createCell(cellnum++);
setCellValue(obj, cell);
}
}
fileName = new String(fileName.getBytes("GBK"), "ISO-8859-1");
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment;filename="
+ fileName);
OutputStream ouputStream = response.getOutputStream();
workbook.write(ouputStream);
ouputStream.flush();
ouputStream.close();
}
private static void setCellValue(Object obj, Cell cell) {
if (obj instanceof String) {
cell.setCellValue((String) obj);
} else if (obj instanceof Integer) {
cell.setCellValue((Integer) obj);
} else if (obj instanceof BigDecimal) {
cell.setCellValue(obj.toString());
}
}
/**
* @param srcfile 文件名数组
* @param zipfile 压缩后文件
*/
public static void zipFiles(File[] srcfile, File zipfile) {
byte[] buf = new byte[1024];
try {
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
zipfile));
for (int i = 0; i < srcfile.length; i++) {
FileInputStream in = new FileInputStream(srcfile[i]);
out.putNextEntry(new ZipEntry(srcfile[i].getName()));
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
in.close();
}
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 文件删除
*
* @param fileNames
* @param zipPath
*/
public static void deleteFile(List<String> fileNames, String zipPath) {
String sPath = null;
File file = null;
boolean flag = false;
try {
// 判断目录或文件是否存在
for (int i = 0; i < fileNames.size(); i++) {
sPath = fileNames.get(i);
file = new File(sPath);
if (file.exists()) {
file.delete();
}
}
file = new File(zipPath);
if (file.exists()) {
file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 导出zip方法(需要有excel文件夹)
* @param fileNames
* @param path
* @param zip
* @param out
* @param fileName
* @throws IOException
*/
public static void exportZip(List<String> fileNames, String path, File zip, OutputStream out, String fileName) throws IOException {
File[] srcfile = new File[fileNames.size()];
for (int i = 0, n = fileNames.size(); i < n; i++) {
srcfile[i] = new File(fileNames.get(i));
}
ExportExcle.zipFiles(srcfile, zip);
FileInputStream inStream = new FileInputStream(zip);
try {
byte[] buf = new byte[4096];
int readLength;
while ((readLength = inStream.read(buf)) != -1) {
out.write(buf, 0, readLength);
}
inStream.close();
ExportExcle.deleteFile(fileNames, path + "excel/" + fileName + ".zip");
} catch (IOException e) {
e.printStackTrace();
} finally {
inStream.close();
}
}
}
调用导出 excle 方法 进行参考
/**
* excle 会员导出方式 kzyuan
*
* @param memberFrom
* @param startDate
* @param endDate
* @param response
* @param request
*/
@RequestMapping(value = "exportMemberExcle", method = RequestMethod.GET)
public void exportMemberExcle(String memberFrom,String startDate,String endDate,HttpServletResponse response, HttpServletRequest request) {
String fileName = "导出会员.xlsx";
String sheetName = 导出会员分页;
/**
* 获取导出数据
*/
List<TheMemberListingBean> list = webmemberService.exportMember(memberFrom,startDate,endDate);
/**
* 第一列的列宽和列里的内容
*/
int[] columnWidths = new int[]{20, 30, 20};
Map<String, Object[]> data = new TreeMap<String, Object[]>();
data.put("1", new Object[]{
"会员编号或邀请码",
"手机号",
"会员级别"
});
/**
* 开始输出内容
*/
int dataIdx = 2;
for (TheMemberListingBean datas : list()) {
Object[] newRow = new Object[] {
String.valueOf(datas.getInviteCode()),
String.valueOf(datas.getUserPhone()),
String.valueOf(datas.getMemberType())
};
data.put("" + dataIdx++, newRow);
}
try {
ExportExcle.generateExcel(response, data, fileName, sheetName, columnWidths);
} catch (IOException e) {
log.error("IOException", e);
}
}
之前导出需要几万条用户,大约在 1-10 分钟以上,调用这个方法,大约在 20 秒左右,不会超出一分钟,当然速度还跟网络和运行配置有关。
能导出了,终于可以愉快的写代码去了。
当然如果你有更好的替代方案,欢迎在评论里留言。
关于我们
程序员太辛苦了
请善待你们身边的每一位程序员~
欢迎在评论写下你的程序员自黑体呦,嗯,相信你可以滴~~~~~~
以上内容,均来自互联网~
欢迎扫描二维码加入我们的小组织
黑壳网交流群 Qq:200408242
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于