Java大數據導出Excel解決方案(POI、JXL、IO流(CSV)對比)

2021-02-23 嗨小叔的程序猿之路

         對於開發人員來說,經常會遇到數據導出到Excel文件需求,在到處數據較大的時常會遇到各類問題,以下整理了些這類問題的常用解決方法。

         Excel導出常用方式:

         Java當中常用的Excel導出主要有POI、JXL和「直接IO流」三種方式,他們各有優缺點!

1、POI介紹

         ApachePOI是用Java編寫的免費開源的跨平臺的Java API,提供了一套對Microsoft Office格式文檔讀和寫的強大功能,由於Office 2007的文件結構完全不同於2003,POI有不同的處理API,當導出格式為2003時,POI調用的是HSSF包,當導出格式為2007時,則調用XSSF包,而SXSSF包是POI3.8版本智商對XSSF的一個擴展,用於大數據量的到處,實際應用中可有效避免內存溢出的問題。

         此方式不僅對Excel操作支持,同時也對Word、PowerPoint等支持,並且該方式的讀取效率上遠遠優於JXL方式,在數據處理量級上也遠非JXL方式可比。

推薦使用情況:

2、JXL介紹

         JXL是一個開源的JavaExcel API項目。他能作為Java Excel API的一個共同的支持庫,是因為它的基本功能是可創建,讀取和寫入電子表格。

         主要特徵:1、生成Excel文件 2、從工作簿和電子表格導入數據 3、獲得行和列的總數。

         此方式對中文支持很好,不會出現亂碼情況,支持單元格的常用操作,滿足一般需求,該方式在寫入效率上優於POI方式。但是需要注意:JXL只支持xls檔案格式,且處理的數據量非常有限。

推薦使用情況:

3、IO流介紹 

         IO流是一組有順序的,有起點和終點的字節集合,是對數據傳輸的總稱和抽象。即數據在兩設備間的傳輸稱為流,流的本質是數據傳輸,根據數據傳輸特性將流抽象為各種類,方便更直觀的進行數據操作,而上面提到的POI和JXL實際都是基於IO流封裝的工具。

         該方式採用最原始的形式進行導出工作,選擇合適的流工作效率會非常出色。但是僅支持對文本文件的操作,如:CSV、TXT等,且導出的文件會相對較大。

推薦使用情況:

一、Excel2003格式

         Excel2003導出時需要注意行數和列數的限制,每個工作表中最多有65,536行和256列。對於工作表能夠支持的最大個數,受CPU反應速度和內存大小影響。

         常用導出方式中的POI支持該格式的只有HSSF包,當導出數據量大於一頁的最大行數(65536)時,可採用分頁的形式進行存儲。

         推薦導出方式:POI和JXL

二、Excel2007格式

         Excel2007導出時也需要注意行數和列的限制,每個工作表最多支持有1,048,576行和16,384列。

         常用導方式支持該格式的只有XSSF包,保護SXSSF擴展包,並且僅有SXSSF支持大數據。(推薦POI3.8版本以上)

三、CSV格式

         CSV是一種通用的、相對簡單的文件格式,廣泛的應用在程序之間轉移表格數據,具有以下特徵:

         常用導出方式推薦:直接IO流導出

 

四、典型問題

         1、傳統POI方式下利用HSSFWorkbook到處的情況下,當數據量達到一定程度,因為查詢出的數據集會直接釋放到Java內存中,導致出現內存溢出。

 

示例一、利用HSSFWorkBook方式一次性查詢出一百萬數據導出

//傳統POI使用模式

Workbook wb = new HSSFWorkBook();

//創建Excel的Sheet

Sheet sh = wb.createSheet();

//查詢數據

List<ExcelTemplateVo> queryList = newLinkedList<ExcelTemplateVo>();

queryList =iExcelTemplateService.getExcelTemplateVoByItems(0,1000000);

for(int rownum=0;rownum <queryList.size();rownum ++){

         //創建行

         Rowrow = sh.createRow(rownum);

         ExcelTemplateVovo = queryList.get(rownum);

         Cell cell = row.createCell(0);

         cell.setCellValue(vo.getc1());

         ...

         cell= row.createCell(19);

         cell.setCellValue(vo.getc20());

}

FileOutputStream out = newFileOutputStream("/excelbigdata.xmlsx");

wb.write(out);

out.close;

如上代碼會拋出異常:

Packet for query is too large(3750197 >1048576).you can change this value on the server by setting themax_allowed_packet… …

分析:

         對於示例代碼,可能是由於一次性查詢的條數過大,也可能是過多用戶同時使用內存造成,所以設置一次性查詢條數參數時,請綜合考慮多方面因素,給出最合理的值。

 

示例二、傳統POI方式下採用HSSFWorkbook導出,採用傳統JDBC解決查詢數據集一次性全部放到內存中導致內存溢出的問題,但由於POI方式HSSFWorkbook的限制,仍出現內存溢出問題。

示例代碼:採用HSSFWorkbook方式,使用傳統JDBC查詢導出100萬數據

//傳統POI使用模式

Workbook wb = new HSSFWorkBook();

//創建Excel的Sheet

Sheet sh = wb.createSheet();

//使用JDBC返回結果集

ResultSet rs =iExcelTemplateJdbcDao.getExcelTemplateVoByItemsForjJdbc(0,items);

Int rownum = 0;

While(rs.next()){

Row row = sh.createRow(rownum);

         Cellcell = row.createCell(0);

         cell.setCellValue(rs.getString(「c1」));

         ...

         cell= row.createCell(19);

         cell.setCellValue(rs.getString(「c20」));

         rownum++;

}

拋出異常:

         Java.lang.outofMemoryError:Javaheap space

分析:

         為了避免示例一中問題出現,此處採用傳統JDBC方式獲取大數據的方法,當對於大數據導出,仍然報內存溢出問題,主要是由於該方式需要把整個sheet數據全部生成到內存後再同意寫到文件,這樣子會超過了虛擬機的最大可用內存,所以報錯。

小結:

         大數據導出Excel時發生內存溢出問題主要原因有:

         1、從資料庫獲取到的數據放到List時,因數據量過大,佔用內存過多發生內存溢出。

         解決方法:應採用從resultSet中讀一條數據寫一條到Excel的方式,不要把查詢出來的數據集先放到List,再逐條寫到Excel中。

         2、將獲取到的數據轉換成Excel時,因轉換工具需要把Excel全部轉換好後再一次性寫文件,佔用內存過大發生內存溢出。

         解決方法:使用POI3.8以上版本的SXSSFWorkbook方式進行到處。

         3、每個導出線程佔用內存較大,因並行導數據的線程較多而發生內存溢出。

         解決方法:加大伺服器JVM內存、使用伺服器集群或控制導出線程數分擔壓力。

解決方案:

         方案一

         使用POI3.8以上版本,使用SXSSFWorkbook方式導出Excel2007.導出文件單頁限制為1,048,576行和16,384列,默認可創建255個工作表,實際創建數受內存大小影響,可解決百萬級以上數據量的導出。

         POI3.8及以上版本的SXSSF包是XSSF的一個擴展本,支持流處理,在生成大數據量的電子表格且堆空間有限時使用。SXSSF通過限制內存中可訪問的記錄行數的方法來實現其低內存的利用,當記錄行數達到限定值時,新一行數據的加入會引起當前最老一行的數據被刷新到硬碟。比如內存中限制行數為100,當行號到達101行時,行號為0的記錄刷新到硬碟並從內存中刪除,以此類推。因此生成Excel時不會出現內存溢出問題。

 該方案執行流程大致如下

示例代碼:

         IntcacheItems = 100;

         Workbookwb = new SXSSFWorkbook(100);

         Sheetsh = sb.createSheet();

         //使用JDBC返回結果集

ResultSet rs =iExcelTemplateJdbcDao.getExcelTemplateVoByItemsForjJdbc(0,items);

Int rownum=0;

While(rs.next()){

Row row = sh.createRow(rownum);

                  Cell cell = row.createCell(0);

                  cell.setCellValue(rs.getString(「c1」));

                  ...

                  cell = row.createCell(19);

                  cell.setCellValue(rs.getString (「c20」));

                   rownum++;

//當行數達到設置的值就刷新數據到硬碟,以清理內存

If(rownum%cacheItems==0){

         ((SXSSFSheet)sh).flushRows();

}

}

JdbcUtil.closeJdbc(rs);

FileOutputStreamout = new FileOutputStream(「/excelbigdata.xmlsx」);

         wb.write(out);

         out.close();

 

         方案二

         使用緩衝流寫入數據,到處csv文件。此方式對於POI和JXL來說,可以採用分割數據包,每個數據包單獨使用一個線程分別寫入數據的方法來提升導出效率,在這僅提議,不做具體實現。

         該方案使用緩衝流的方式來寫入文件,生成csv格式的文本文件,便於開發人員對數據進行二次解析,以減少不必要的開發時間。

 該方案大致流程如下:

創建目標文件xxx.csv

打開流

寫入表頭信息

使用JDBC得到結果集

循環數據,寫入文件

關閉JDBC連接

關閉流

示例代碼

         FilecsvFile = null;

         BufferedWritercsvWriter = null;

         csvFile= new File(「/excelbigdata.csv」);

         Fileparent = csvFile.getParentFile();

         if(parent!= null && !parent.exists()){

                   Parent.mkdir();

}

         cvsFile.createNewFile();

         //UTF-8使用正確讀取分隔符」,」

         csvWriter= new BufferedWriter(new OutputStreamWriter(new FileOutputStream(csvFile),」UTF-8」),1024);

         //寫入文件頭部(省略部分)

         Object[]headArr={「C1」,」C2」,」C3」……」C20」};

         List<Object>head = Arrays.asList(headArr);

         StringBuffersb = new StringBuffer();

         for(inti=0;i<head.size();i++){

                   Objectdata = head.get(i);

                   if(I== head.size()-1){

         sb.append(「\」」).append(data).append(「\」」).toString();

}else{

         sb.append(「\」」).append(data).append(「\」;」).toString();

}

                   csvWriter.write(sb.toString());

}

         csvWriter.newLine();

ResultSet rs =iExcelTemplateJdbcDao.getExcelTemplateVoByItemsForjJdbc(0,items);

         while(rs.next()){

         ExcelTemplateVovo = new ExcelTemplateVo();

         vo.setc1(rs.getString(「c1」));

         vo.setc2(rs.getString(「c2」));

         。。。。。。

         vo.setc20(rs.getString(「20」));

         writeRowData(vo,csvWriter);

}

         //寫一行數據方法

         Privatestatic void writeRowData(ExcelTemplateVo vo,BufferedWriter csvWriter) throwsException{

         if(vo!=null){

         stringBuffersb = new StringBuffer();

         sb.append(「\」」).append(vo.getc1()).append(「\」;」);

         。。。。。

         sb.append(「\」」).append(vo.getc20()).append(「\」」);

         csvWriter.write(sb.toString());

}

}

 

         方案三(不推薦)

         使用JXL方式導出Excel,單頁受行數和列數的限制,默認可創建255個工作表,實際創建受內存大小影響,由於使用JXL方式使用內存開銷過大,實際可到處數據量遠遠小於Excel2003可容納量。

         該方案採用JXL方式到處EXCEL,生成文件後綴為xls,該方式對於中文支持非常好,操作簡單。

大致流程如下:

示例代碼:

         WorkbookSettingswbSetting = new WorkbookSettings();

         wbSetting.setUserTemporaryFileDuringWrite(true);

         wbSetting.setTemporaryFileDuringWriteDirectory(newFile(「/」));//臨時文件夾的位置

         WritableWorkbookbook = jxl.Workbook.createWorkbook(new File(「/excelbigdata.xls」),wbSetting);

         //設置樣式、字體

         WritableFontfont1 = new WritableFont(WritableFont.createFont(「微軟雅黑」),10,WritableFont.BOLD);

WritableFontfont2 = new WritableFont(WritableFont.createFont(「微軟雅黑」),9,WritableFont.NO_BOLD);

         WritableCellFormatwcf1 = new WritableCellFormat(font1);

WritableCellFormatwcf2 = new WritableCellFormat(font2);

//平行居中

wcf1.setVerticalAlignment(VerticalAlignment.CENTRE);

wcf2.setAlignment(Alignment.CENTRE);

wcf2.setVerticalAlignment(VerticalAlignment.CENTRE);

ResultSet rs =iExcelTemplateJdbcDao.getExcelTemplateVoByItemsForjJdbc(0,items);

int rownum = 0;

int sheetCount =50000;

int sheetNum =0;

WritableSheetsheet = null;

while(rs.next()){

         if(rownum % sheetCount == 0){

         sheetNum ++;

         rownum = 0 ;

         wheet = book.createSheet(「第」+sheetNum+」頁」,sheetNum-1);

}

//頁設置對象

sheet.addCell(newLabel(0,rownum,rs.getString(「c1」),wcf2));

sheet.addCell(newLabel(1,rownum,rs.getString(「c2」),wcf2));

。。。。。。

sheet.addCell(newLabel(19,rownum,rs.getString(「c20」),wcf2));

//設置行高

sheet.setRowView(rownum,370);

Rownum ++;

}

jdbcUtil.closeJdbc(rs);

//關閉數據並關閉文件

book.write();

book.close();

    註:因為代碼為在Word文檔中純手打,所以會存在部分代碼首字母自動變大些等情況,還有些部分無關緊要的代碼被我省略


相關焦點

  • 程式設計師:java導出Excel,附帶依賴、後端代碼和前端JS
    -- Apache POI庫用於導出Excel --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId>
  • SpringBoot實現POI報表操作
    POI報表入門在mymes管理系統中,人員管理,訂單等操作需要報表的導入導出等邏輯。需求看是複雜,實際上就是對資料庫表的基本操作,本文介紹Excel的導出,下次介紹數據的導入POI報表的概述需求數碼 在企業應用開發中,Excel報表是一種最常見的報表需求。
  • 優雅 | 今天很水的文章-Excel導入導出
    'xlsx':type),bookSST: false, type: 'binary'}//這裡的數據是用來定義導出的格式類型 ))], { type: "" }); //創建二進位對象寫入轉換好的字節流 saveAs(tmpDown,fileName);}function saveAs(obj, fileName
  • 每日一課 | Apache POI –用Java讀寫Excel文件
    該代碼使用二維數據數組來保存數據。數據被寫入XSSFWorkbook對象。XSSFSheet是正在處理的工作表。import org.apache.poi.xssf.usermodel.XSSFSheet;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.FileNotFoundException;
  • Java實現文件批量導入導出實踐(兼容xls,xlsx)
    1、介紹java 實現文件的導入導出資料庫,目前在大部分系統中是比較常見的功能了,今天寫個小 demo 來理解其原理,沒接觸過的同學也可以看看參考下。目前我所接觸過的導入導出技術主要有 POI 和 iReport,poi 主要作為一些數據批量導入資料庫,iReport 做報表導出。
  • 數據驅動框架(Apache POI – Excel)
    通常,當我們手動測試應用程式時,我們針對多個測試數據運行相同的方案。此外,我們將相同的測試數據保存在某些文件中,例如Excel文件,文本文件,CSV文件或任何資料庫。自動化也是如此,我們希望對多個測試數據運行相同的測試方案。
  • 後端:Hutool Java 工具類庫導出 Excel,超級簡單!
    在開發應用系統的時候,導出文件是必不可放的功能。以前用過POI、easyexcel等工具的導入導出功能,但總感覺太麻煩了,代碼特別多,感覺並不是很好用。今天給大家介紹一款新工具,java工具類庫Hutool。
  • Hutool Java 工具類庫導出 Excel,超級簡單!
    以前用過POI、easyexcel等工具的導入導出功能,但總感覺太麻煩了,代碼特別多,感覺並不是很好用。今天給大家介紹一款新工具,java工具類庫Hutool。Hutool中的工具方法來自於每個用戶的精雕細琢,它涵蓋了Java開發底層代碼中的方方面面,它既是大型項目開發中解決小問題的利器,也是小型項目中的效率擔當;Hutool是項目中「util」包友好的替代,它節省了開發人員對項目中公用類和公用工具方法的封裝時間,使開發專注於業務,同時可以最大限度的避免封裝不完善帶來的
  • SpringBoot整合EasyPoi實現Excel的導入和導出(帶圖片)
    EasyPoi文檔:http://easypoi.mydoc.io/項目地址:https://gitee.com/lihongmin5211/springboot_easypoi準備工作環境:SpringBoot2.4.0、jdk8、Mysql5.7表結構:
  • EasyPOI:Excel/Word 導入/導出, 註解使用,完美,便捷,高效
    讓一個沒接觸過poi的人員就可以方便的寫出Excel導出,Excel導出,Excel導入,Word模板導出,通過簡單的註解和模板語言(熟悉的表達式語法),完成以前複雜的寫法;    提供下官網地址,方便查閱最新動態以及細節問題:    關於easypoi可參考http://easypoi.mydoc.io/
  • Springboot整合easyExcel導入導出Excel
    excel,因此在此記錄學習一下如何使用Springboot整合easyExcel;需求:資料庫中有張user表,有個業務要求可以導入、導出「用戶名單.xls」表一、準備:創建項目:關於springboot項目如何創建這裡不再贅述,放一張項目結構圖:1、導入easyexcel、mybatis、mysql依賴
  • java 讀寫 wps xlsx 文件
    ;import org.apache.poi.xssf.usermodel.XSSFCell;import org.apache.poi.xssf.usermodel.XSSFRow;import org.apache.poi.xssf.usermodel.XSSFSheet;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.FileInputStream
  • 我去,還在這樣讀寫 excel 這也太低效了吧!
    作者:樓下小黑哥前言博文地址:studyidea.cn/easyexcel最近讀者小 H 給小黑哥發來私信:小黑哥,最近我在負責公司報表平臺開發,需要導出報表到 excel 中。小黑哥還記得當初使用 poi 導出一個二十多欄位的 excel,不斷複製粘貼,行號一不小心就寫錯了,那叫個一個心酸。今天小黑哥就來推薦一個阿里開源的項目『EasyExcel』,帶大家徹底告別上面又長又臭的代碼,徹底解決這個問題。
  • Java如何操作Word, Excel, PDF文檔?
    參考:  http://danadler.com/jacob/  http://jakarta.apache.org/poi/  http://www.onjava.com/pub/a/onjava/2003/01/22/poi.html  http://www.csdn.net/develop/article/15/15311
  • MySQL導出數據為csv的方法
    將資料庫中的數據導出成csv格式的文件CSV格式,其要點包括:(1)欄位之間以逗號分隔,數據行之間以\r\n分隔;
  • R語言數據導入與導出
    在用R語言分析數據時,我們首先要進行數據的導入與導出。R支持多種文件格式,包括但不限於常見的txt,csv,xlsx,和tsv等等。數據的導入依賴於各種R包,方法大同小異,可根據實際情況舉一反三。getwd()# output will be "D:/Folder_name"在R中導出 .CSV 文件read.csv()是R中默認的讀取.csv文件的函數,是read.table()函數的簡易形式,讀.csv文件時非常方便實用。
  • 數據科學 | pandas數據導入與導出
    當我們開始著手做一個數據分析項目時,選擇和導入數據集是第一個步驟,而導出數據雖然非必需,但有時候我們也需要保存處理或者分析後的結果,方便下次使用。在pandas中,它已經為我們提供了很多不同格式數據的導入和導出方法,下面這篇文章將具體介紹一些較為常用的方法,包括excel、csv文件以及資料庫的導入導出等。數據導入和導出是pandas中很基礎且重要的一個部分。
  • Python爬蟲 | 0xc - 數據存儲:CSV和Excel
    CSV(Comma-Separated Values,逗號分隔值) 以純文本形式存儲表格數據(數字和文本),記錄間以某種換行符分隔。跟我們上面用-作為分隔符保存數據非常類似,CSV文件除了可以用普通文本編輯工具打開外,還可使用Excel打開。Python中內置一個csv模塊供我們處理CSV文件。
  • 百萬級別數據Excel導出優化
    這篇文章不是標題黨,下文會通過一個仿真例子分析如何優化百萬級別數據Excel導出。筆者負責維護的一個數據查詢和數據導出服務是一個相對遠古的單點應用,在上一次雲遷移之後擴展為雙節點部署,但是發現了服務經常因為大數據量的數據導出頻繁Full GC,導致應用假死無法響應外部的請求。