分类 标签 存档 程序员漫画 黑壳相关站点 订阅 搜索

Java 大数据量高性能 Excel 导出

1898 浏览2 评论

本文由黑壳博客发布

本文来源 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


11a84075a304ac57f6d37323512fd24cde9836350b9d80148b282eeaa188b196c2358d4ffd7006cbpng

黑壳博客 blog.bhusk.com

E-mail:keshu@bhusk.com

本文由 黑壳博客的壳叔 创作或转载,采用 知识共享署名 3.0 中国大陆许可协议 进行许可。

可自由转载、引用,但需署名作者且注明文章

评论  
2 评论
alanfans • 2018-03-30
回复 删除

github 上有个阿里的 easyexcel 很方便。

1 回复
ykz200 回复 alanfans • 2018-03-31
回复 删除

看完说明,感觉也是很稳,不知道具体效果如何。下次写一篇体验导出文章,说一下效果。https://github.com/alibaba/easyexcel/blob/master/README.md

推荐阅读