1. 程式人生 > >POI(excel)匯出優化

POI(excel)匯出優化

寄語:第一次接觸百萬級別匯出,不知道極限,嘗試過百度,經歷過絕望,記憶體溢位,等了一萬年,不出來檔案….

1.要明白 極限:

excel 不同版本的行極限:

    excel 2007 及以上:1048576
          2003 : 65536  行
 不考慮列:因為最少256列夠用

2:什麼會導致記憶體溢位:

1.迴圈量過大
2.建立重複實體過多
3.map 新增多隻  
4.實體未釋放

3:怎麼檢測是什麼過大:
jconsole

4: 看過無數篇部落格,感覺敘述都沒用,但其實是業務不對,但其實是能組裝業務的
比如說:

  1. poi匯出100w的excel需要多久:測試了二十列100w行匯出 一分半鐘就能匯出來。
  2. 那為什麼在系統裡會如此慢: 列印日誌,查時間,發現mongo 查詢 很慢,而且還易報錯,比如說16mb的bson,這個會讓做大資料很麻煩。但你查詢的過程 想到分出去多少萬去查詢,這個很難,比如說一百萬 你分十次 可以,但分五次比十次要測測哪個快,我本人選擇的是二十萬,因為減少連線,可以快速的拼裝資料,而且,利用計算,使流持久化,等我寫夠一百萬在讓流釋放,如果沒寫夠,讓他一直存在。
  3. 這樣我每二十萬就手動控制寫出到workbook 裡,加快了以後刷出100萬的迴圈。也從而減少了拼裝集合。
 /**
     * @param tableID   表id
     * @param tableName 表名稱
     * @param
request * @param response * @param query 查詢 * @param isZip 是否是zip * @param userName 使用者名稱稱 用來生成excelName * @return * @author liuyu * date 2018/8/29 **/
private String becomeExcel(String tableID, String tableName, HttpServletRequest request, HttpServletResponse response, Query query, boolean
isZip, String userName) throws IOException { List<FieldEO> entities = fieldEODao.findByTableID(tableID); long count = mongoDB.count(query, tableName); String fileName; HashMap excelReturnMap = null; Workbook workbook = null; ZipOutputStream zipOutputStream = null; int countIndex = 0; if (isZip) { fileName = "《" + tableName + "-" + userName + "-" + DateUtils.dateToString(new Date(), "yyyy-MM-dd HH-mm-ss") + "》.zip"; Double countFor = count / 200000.0; // 每二十萬重新整理到excel裡面 for (int i = 0; i < countFor.intValue(); i++) { List<DBObject> resultList = mongoDB.getList(tableName, new Query(), i * 200000, 200000); if (excelReturnMap != null) { zipOutputStream = getZipOutputStream(excelReturnMap, zipOutputStream); workbook = getWorkbook(excelReturnMap, workbook); countIndex = getCount(excelReturnMap); } if (countFor - (i + 1) != 0) { excelReturnMap = ExcelUtils.exportExcel(entities, resultList, fileName, request, response, true, path, i * 200000, true, workbook, zipOutputStream, countIndex); } else { excelReturnMap = ExcelUtils.exportExcel(entities, resultList, fileName, request, response, true, path, i * 200000, false, workbook, zipOutputStream, countIndex); } } // 重新整理 if (countFor - countFor.intValue() > 0) { if (excelReturnMap != null) { zipOutputStream = getZipOutputStream(excelReturnMap, zipOutputStream); workbook = getWorkbook(excelReturnMap, workbook); countIndex = getCount(excelReturnMap); } List<DBObject> resultListSmaller = mongoDB.getList(tableName, new Query(), countFor.intValue() * 200000, 200000); if (resultListSmaller != null && !resultListSmaller.isEmpty()) { workbook = null; ExcelUtils.exportExcel(entities, resultListSmaller, fileName, request, response, true, path, countFor.intValue() * 200000, false, workbook, zipOutputStream, countIndex); } } } else { // 十萬以內直接輸出出去 fileName = "《" + tableName + "-" + DateUtils.dateToString(new Date(), "yyyy-MM-dd HH-mm-ss") + "》.xlsx"; List<DBObject> byQuery = mongoDB.getList(tableName, new Query(), 0, 100000); ExcelUtils.exportExcel(entities, byQuery, fileName, request, response, false, path, 0, true, workbook, zipOutputStream, countIndex); ExcelUtils.exportExcel(entities, byQuery, fileName, request, response, isZip, path, 0, true, workbook, zipOutputStream, countIndex); } return isZip ? path + File.separator + fileName : null;

excel程式碼:

    /**
     * @param entities     欄位集合,用來拆分表頭,和解析時間型別
     * @param results      資料集
     * @param excelName    表名
     * @param request      請求頭
     * @param response     請求頭
     * @param isZip        是否是zip檔案
     * @param path         path是匯出的路徑
     * @param fooIf        是用來判別是第幾個excel的
     * @param hasNext      區別是否還需要拼裝zip裡的一個excel
     * @param workbook     帶有未組合好的excel
     * @param zipOutStream 帶有未組合好的壓縮流
     * @param countIndex   第幾個檔名字首
     * @return
     * @author liuyu
     * date 2018/8/29
     **/
    public static HashMap<String, Object> exportExcel(List<FieldEO> entities, List<DBObject> results,
                                                      String excelName, HttpServletRequest request,
                                                      HttpServletResponse response, boolean isZip, String path,
                                                      int fooIf, boolean hasNext, Workbook workbook,
                                                      ZipOutputStream zipOutStream, int countIndex) throws IOException {
        HashMap<String, Object> returnMap = new HashMap<>();
        if (isZip) {
            File zipFile = null;
            XSSFWorkbook xssfWorkbook = null;
            ByteArrayOutputStream outputStream = null;
            try {
                if (workbook == null) {
                    xssfWorkbook = new XSSFWorkbook();
                    workbook = new SXSSFWorkbook(xssfWorkbook);
                    workbook.createSheet();
                }

                createSheet(entities, results, workbook, fooIf);
                int lastRowNum = workbook.getSheetAt(0).getLastRowNum();
                if (!hasNext || lastRowNum >= 1000000) {
                    zipFile = new File(path + File.separator + excelName);
                    if (!zipFile.exists()) {
                        if (!zipFile.getParentFile().exists()) {
                            boolean mkdirs = zipFile.getParentFile().mkdirs();
                        }
                        boolean newFile = zipFile.createNewFile();
                    }
                    if (zipOutStream == null) {
                        zipOutStream = new ZipOutputStream(new FileOutputStream(zipFile));
                    }

                    outputStream = new ByteArrayOutputStream();
                    try {
                        workbook.write(outputStream);
                    } catch (IOException e) {
                        e.printStackTrace();
                        logger.error(e.getMessage());
                    } finally {
                        ((SXSSFWorkbook) workbook).dispose();
                        workbook.close();
                    }
                    int count;
                    ByteArrayInputStream zipTemp = null;
                    zipTemp = new ByteArrayInputStream(outputStream.toByteArray());

                    try {
                        ZipEntry zipEntry = new ZipEntry(countIndex + ".xlsx");
                        zipOutStream.putNextEntry(zipEntry);
                        while ((count = zipTemp.read()) != -1) {
                            zipOutStream.write(count);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        zipOutStream.closeEntry();
                        zipTemp.close();
                        outputStream.close();
                    }
                    countIndex++;
                    //先關閉源頭
                    xssfWorkbook = new XSSFWorkbook();
                    workbook = new SXSSFWorkbook(xssfWorkbook);
                    workbook.createSheet();
                }
                if (hasNext) {
                    returnMap.put(Constant.EXECEL_EXPORT_WORK_STREAM, workbook);
                    returnMap.put(Constant.EXECEL_EXPORT_ZIP_STREAM, zipOutStream);
                    returnMap.put(Constant.EXECEL_EXPORT_ZIP_COUNT, countIndex);
                    return returnMap;
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (!hasNext && workbook != null) {
                    workbook.close();
                }
                if (!hasNext && zipOutStream != null) {
                    zipOutStream.close();
                }
            }
        } else {
            XSSFWorkbook xssfWorkbook = null;
            ServletOutputStream outputStream = null;
            try {
                xssfWorkbook = new XSSFWorkbook();
                workbook = new SXSSFWorkbook(xssfWorkbook);
                workbook.createSheet();
                createSheet(entities, results, workbook, 0);
                String rtn = ContentDispositionUtils.handler(request, excelName);
                response.setCharacterEncoding("UTF-8");
                response.addHeader("Content-disposition", "attachment;" + rtn);
                outputStream = response.getOutputStream();
                workbook.write(outputStream);
                outputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
                logger.error("exception:" + e);
            } finally {
                if (outputStream != null) {
                    outputStream.close();
                }
                if (workbook != null) {
                    workbook.close();
                }
                if (xssfWorkbook != null) {
                    xssfWorkbook.close();
                }
            }

        }
        return null;
    }

    /**
     * @param entities 欄位集合
     * @param results  結果集
     * @param workbook 工作流
     * @param fooIf    用來判定是否需要生成表頭
     * @return
     * @author liuyu
     * date 2018/8/29
     **/
    private static void createSheet(List<FieldEO> entities, List<DBObject> results, Workbook workbook, int fooIf) {
        Sheet sheet;
        sheet = workbook.getSheetAt(0);
//        style.setShrinkToFit(true);
        //設定行內居中
//        style.setAlignment(HorizontalAlignment.CENTER);
        //使用這個可以優化公式。
//        XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);
        Map<String, String> fieldEOMap = entities.stream().collect(Collectors.toMap(FieldEO::getFieldNameZH,
            FieldEO::getFieldName));
        Map<String, Integer> fieldNameAndType = entities.stream().collect(Collectors.toMap(FieldEO::getFieldName,
            FieldEO::getFieldType));
        if (fooIf == 0 || fooIf % 1000000 == 0) {
            results.add(0, new BasicDBObject(fieldEOMap));
        }
        List<String> fieldList = new ArrayList<>(fieldEOMap.values());
        int lastRowNum = sheet.getLastRowNum();
        int count = lastRowNum;
        if (count != 0) {
            count = count + 1;
        }
        for (int i = 0; i < results.size(); i++) {
            final DBObject paramsMap = results.get(i);
            Row row = sheet.createRow(count + i);
            if ((lastRowNum + i) == 0) {
                Set entries = paramsMap.toMap().entrySet();
                Iterator iterator = entries.iterator();
                for (int j = 0; iterator.hasNext(); j++) {
                    Map.Entry<String, Object> next = (Map.Entry<String, Object>) iterator.next();
                    //設定單元格的值
                    Object value = next.getValue();
                    Object key = next.getKey();
                    Cell cell = row.createCell(j);
                    cell.setCellValue(key + "(" + value + ")\t");
                }
            } else {
                for (int z = 0; z < fieldList.size(); z++) {
                    Cell cell = row.createCell(z);
                    String key = fieldList.get(z);
                    Object value = paramsMap.get(key);
                    if (fieldNameAndType.get(key).equals(FieldTypeEnum.TIME.getId())) {
                        value = DateUtils.dateToString((Date) value, DateUtils.YYYY_MM_DD_HH_MM_SS_EN);
                        cell.setCellType(CellType.STRING);
                    } else if (fieldNameAndType.get(key).equals(FieldTypeEnum.DATE.getId())) {
                        value = DateUtils.dateToString((Date) value, DateUtils.YYYY_MM_DD_EN);
                        cell.setCellType(CellType.STRING);
                    }
                    if (value == null) {
                        value = "";
                    }
                    cell.setCellValue(value + "");
                }
            }
        }
    }