java 導出百萬級數據到excel


最近修改了一個導出員工培訓課程的歷史記錄(一年數據),導出功能本來就有的,不過前台做了時間限制(只能選擇一個月時間內的),還有一些必選條件, 導出的數據非常有局限性。心想:為什么要做出這么多條件限制呢?條件限制無所謂了,能限制導出數據的准確性,但是時間? 如果我想導出一年的數據,還要一月一月的去導出,這也太扯了。於是我試着放開時間js限制,讓用戶自己隨便選好了,然后自己選了一段時間,選了幾門課程,點擊按鈕導出,MD報錯了,看后台日志說什么IO流報異常,看了下代碼,代碼也很簡單,查詢數據,用HSSFWorkbook 寫入數據,關閉流,導出,似乎沒什么問題。於是去把查詢的sql拉出來,放入數據庫,查詢數據,20w條數據,好吧,這下終於知道為什么加時間限制了,數據量過大!!!程序處理不了,改代碼吧。 雖說實際工作中很少有百萬數據導入excel,但不缺少一些會excel的高手,分析對比數據,像我這種手殘黨是不行,他們怎么用暫時不用管,能不能實現,就是我們應該考慮的事了。

此案例能解決2個問題:

1.用戶導出速度過慢

2.采用分頁導出,以解決單個sheet頁數據量過大,打開速度過慢

簡單介紹下我的操作:

1.HSSFWorkbook 和SXSSFWorkbook區別

 HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,擴展名是.xls,一張表最大支持65536行數據,256列,也就是說一個sheet頁,最多導出6w多條數據

XSSFWorkbook:是操作Excel2007-2010的版本,擴展名是.xlsx對於不同版本的EXCEL文檔要使用不同的工具類,如果使用錯了,
會提示如下錯誤信息。

org.apache.poi.openxml4j.exceptions.InvalidOperationException    

org.apache.poi.poifs.filesystem.OfficeXmlFileException

它的一張表最大支持1048576行,16384列,關於兩者介紹,對下面導出百萬數據很重要,不要使用錯了!

2.使用SXSSFWorkbook對象,導出百萬數據

SXSSFWorkbook使用方法和 HSSFWorkbook差不多,如果你之前和我一樣用的HSSFWorkbook,現在想要修改,則只需要將HSSFWorkbook改成SXSSFWorkbook即可,下面有我介紹,具體使用也可參考API

3.如何將百萬數據分成多個sheet頁,導出到excel

導出百萬數據到excel,很簡單,只需要將原來的HSSFWorkbook修改成SXSSFWorkbook,或者直接使用SXSSFWorkbook對象,它是直接用來導出大數據用的,官方文檔 有介紹,但是如果有300w條數據,一下導入一個excel的sheet頁中,想想打開excel也需要一段時間吧,慢的話有可能導致程序無法加載,或者直接結束進程的情況發生,曾看到過一段新聞 ,這里對老外的毅力也是深表佩服。

這里給出部分代碼,供參考研究,分頁已實現:

@SuppressWarnings({ "deprecation", "unchecked" })
    @RequestMapping("export-TrainHistoryRecord")
    @ResponseBody
    protected void buildExcelDocument(EmployeeTrainHistoryQuery query,ModelMap model,
            SXSSFWorkbook workbook, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        try {
            response.reset();
            // 獲得國際化語言
            RequestContext requestContext = new RequestContext(request);
            String CourseCompany = requestContext
                    .getMessage("manage-student-trainRecods");
            response.setContentType("APPLICATION/vnd.ms-excel;charset=UTF-8");
            // 注意,如果去掉下面一行代碼中的attachment; 那么也會使IE自動打開文件。
            response.setHeader(
                    "Content-Disposition",
                    "attachment; filename="
                            + java.net.URLEncoder.encode(
                                    DateUtil.getExportDate() + ".xlsx", "UTF-8"));//Excel 擴展名指定為xlsx  SXSSFWorkbook對象只支持xlsx格式
            OutputStream os = response.getOutputStream();
            CellStyle style = workbook.createCellStyle();
            // 設置樣式
            style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);//設置單元格着色
            style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);  //設置單元格填充樣式
            style.setBorderBottom(HSSFCellStyle.BORDER_THIN);//設置下邊框
            style.setBorderLeft(HSSFCellStyle.BORDER_THIN);//設置左邊框
            style.setBorderRight(HSSFCellStyle.BORDER_THIN);//設置右邊框
            style.setBorderTop(HSSFCellStyle.BORDER_THIN);//上邊框
            style.setAlignment(HSSFCellStyle.ALIGN_CENTER);// 居中
            //獲取國際化文件
            String employeeCode = requestContext.getMessage("employeeCode");
            String employeeName = requestContext.getMessage("employeeName");
            String orgName = requestContext.getMessage("orgName");
            String startDate = requestContext.getMessage("start.date");
            String endDate = requestContext.getMessage("end.date");
            String courseCode = requestContext.getMessage("courseCode");
            String courseName = requestContext.getMessage("courseName");
            String sessionName = requestContext.getMessage("sessionName");

            List<EmployeeTrainHistoryModel> list = null;
            try {
                            //查詢數據庫中共有多少條數據
                            query.setTotalItem(employeeTrainHistoryService.fetchCountEmployeeTrainHistoryByQuery(query));
                
                    int page_size = 100000;// 定義每頁數據數量
                    int list_count =query.getTotalItem();
                    //總數量除以每頁顯示條數等於頁數
                    int export_times = list_count % page_size > 0 ? list_count / page_size
                            + 1 : list_count / page_size;
                     //循環獲取產生每頁數據
                    for (int m = 0; m < export_times; m++) {
                        query.setNeedQueryAll(false);
                        query.setPageSize(100000);//每頁顯示多少條數據
                        query.setCurrentPage(m+1);//設置第幾頁
                         list=employeeTrainHistoryService.getEmployeeTrainHistoryByQuery(query);
                        //新建sheet
                         Sheet sheet = null;
                            sheet = workbook.createSheet(System.currentTimeMillis()
                                    + CourseCompany+m);
                            // 創建屬於上面Sheet的Row,參數0可以是0~65535之間的任何一個,
                            Row header = sheet.createRow(0); // 第0行
                            // 產生標題列,每個sheet頁產生一個標題
                             Cell cell;
                            String[] headerArr = new String[] { employeeCode, employeeName,
                                    orgName, startDate, endDate, courseCode, courseName, sessionName,
                                    hoursNunber };
                            for (int j = 0; j < headerArr.length; j++) {
                                cell = header.createCell((short) j);
                                cell.setCellStyle(style);
                                cell.setCellValue(headerArr[j]);
                            }
                            // 迭代數據
                             if (list != null && list.size() > 0) {
                                 int rowNum = 1;
                                 for (int i = 0; i < list.size(); i++) {
                                     EmployeeTrainHistoryModel history=list.get(i);
                                         sheet.setDefaultColumnWidth((short) 17);
                                     Row row = sheet.createRow(rowNum++);
                                     row.createCell((short) 0).setCellValue(
                                             history.getEmployeeCode());
                                     row.createCell((short) 1).setCellValue(
                                             history.getEmployeeName());
                                     row.createCell((short) 2)
                                             .setCellValue(history.getOrgName());
                                     if (history.getTrainBeginTime() != null) {
                                         row.createCell((short) 3).setCellValue(
                                                 DateUtil.toString(history.getTrainBeginTime()));
                                     } else {
                                         row.createCell((short) 3).setCellValue("");
                                     }
                                     if (history.getTrainEndTime() != null) {
                                         row.createCell((short) 4).setCellValue(
                                                 DateUtil.toString(history.getTrainEndTime()));
                                     } else {
                                         row.createCell((short) 4).setCellValue("");
                                     }
                                     row.createCell((short) 5).setCellValue(
                                             history.getCourseCode());
                                     row.createCell((short) 6).setCellValue(
                                             history.getCourseName());
                                     row.createCell((short) 7).setCellValue(
                                             history.getSessionName());
                                     if (history.getHoursNumber() != null)
                                         row.createCell((short) 8).setCellValue(
                                                 history.getHoursNumber().toString());
                                 }
                             }
                             
                             list.clear();
                         }                            
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                workbook.write(os);
                os.close();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

4.如何高效導出數據

第3部分,大數據量導出數據,分頁都已實現,但怎樣才能去壓榨時間,高效導出?Apache POI既然提供了導出excel的方法,想必也考慮到了效率問題,查看官方文檔 ,  果不其然,看文檔,大概意思就是說SXSSF在必須生成大型電子表格時使用,堆空間有限  官方提供了2種方法:

1.  SXSSFWorkbook wb = new SXSSFWorkbook(100);  // keep 100 rows in memory, exceeding rows will be flushed to disk   

2.SXSSFWorkbook wb = new SXSSFWorkbook(-1);   // turn off auto-flushing and accumulate all rows in memory

值100  在內存中保留100行,超過行將被刷新到磁盤

值-1表示無限制訪問。 在這種情況下所有,沒有被調用flush()刷新的記錄可用,用於隨機訪問。

 

文章在最后說,當臨時文件過大時,可使用setCompressTempFiles方法進行壓縮,

比較貪心,這里我用了兩個,一個用來設置臨時文件,另一個用來輸入數據,測試數據為30w數據,結果如圖,不過還是感覺花費時間太多,不知道是不是我的程序寫的有問題,知道的小伙伴,留個言吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM