基于流的EXCEL文件导出,SXSSFWorkbook源码解析
當(dāng)我們在實(shí)現(xiàn)excel導(dǎo)出時(shí),在數(shù)據(jù)量過大的情況下,總是容易發(fā)生內(nèi)存溢出的情況。我們可以使用POI提供的 SXSSFWorkbook 類來避免內(nèi)存溢出。
注:基于POI4.10版本源碼
以下是官方文檔對SXSSF包的說明:
SXSSF (package: org.apache.poi.xssf.streaming) is an API-compatible streaming extension of XSSF to be used when very large spreadsheets have to be produced, and heap space is limited. SXSSF achieves its low memory footprint by limiting access to the rows that are within a sliding window, while XSSF gives access to all rows in the document. Older rows that are no longer in the window become inaccessible, as they are written to the disk.
大致翻譯如下:
SXSSF是XSSF的一個(gè)與API兼容的流擴(kuò)展,在需要生成非常大的電子表格時(shí)使用,堆空間有限。SXSSF通過限制對滑動窗口中的行的訪問來實(shí)現(xiàn)其低內(nèi)存占用,而XSSF允許訪問文檔中的所有行。當(dāng)將舊行寫入磁盤時(shí),不再在窗口中的舊行變得不可訪問。
使用示例
// 內(nèi)存中保持100條數(shù)據(jù), 超出的部分刷新到磁盤上SXSSFWorkbook wb = new SXSSFWorkbook(100);Sheet sh = wb.createSheet();for(int rownum = 0; rownum < 1000; rownum++){Row row = sh.createRow(rownum);for(int cellnum = 0; cellnum < 10; cellnum++){Cell cell = row.createCell(cellnum);String address = new CellReference(cell).formatAsString();cell.setCellValue(address);}}// rownum < 900 的數(shù)據(jù)被刷新到磁盤,不能被隨機(jī)訪問for(int rownum = 0; rownum < 900; rownum++){Assert.assertNull(sh.getRow(rownum));}// 最后的100條數(shù)據(jù)仍然在內(nèi)存中,可以隨機(jī)訪問for(int rownum = 900; rownum < 1000; rownum++){Assert.assertNotNull(sh.getRow(rownum));}FileOutputStream out = new FileOutputStream("d:\\sxssf.xlsx");wb.write(out);out.close();// 從磁盤上釋放臨時(shí)文件wb.dispose();臨時(shí)文件分析
在wb.write(out)此行斷點(diǎn),debug運(yùn)行到此處時(shí),可以在windows路徑C:\Users\ADMINI~1\AppData\Local\Temp\下發(fā)現(xiàn)類似以下格式的文件:
此文件就是被刷新到磁盤上的數(shù)據(jù)臨時(shí)文件。此文件是怎么生成的呢?接下來我們就進(jìn)入到源碼分析的階段。進(jìn)入方法:
/*** Sreate an Sheet for this Workbook, adds it to the sheets and returns* the high level representation. Use this to create new sheets.** @return Sheet representing the new sheet.*/@Overridepublic SXSSFSheet createSheet(){return createAndRegisterSXSSFSheet(_wb.createSheet());}SXSSFSheet createAndRegisterSXSSFSheet(XSSFSheet xSheet){final SXSSFSheet sxSheet;try{sxSheet=new SXSSFSheet(this,xSheet);}catch (IOException ioe){throw new RuntimeException(ioe);}registerSheetMapping(sxSheet,xSheet);return sxSheet;}再進(jìn)入new SXSSFSheet(this,xSheet)方法:
public SXSSFSheet(SXSSFWorkbook workbook, XSSFSheet xSheet) throws IOException {_workbook = workbook;_sh = xSheet;_writer = workbook.createSheetDataWriter();setRandomAccessWindowSize(_workbook.getRandomAccessWindowSize());_autoSizeColumnTracker = new AutoSizeColumnTracker(this);}接著進(jìn)入workbook.createSheetDataWriter()方法,最后我們會發(fā)現(xiàn)以下代碼:
public SheetDataWriter() throws IOException {_fd = createTempFile();_out = createWriter(_fd);}由此我們知道,在創(chuàng)建sheet頁時(shí),就創(chuàng)建了臨時(shí)文件目錄(即每一個(gè)sheet頁都會對應(yīng)創(chuàng)建一個(gè)臨時(shí)文件)。那么臨時(shí)文件是建立在什么目錄下,是否可以手動修改呢?我們繼續(xù)跟進(jìn)_fd = createTempFile()方法:
public File createTempFile() throws IOException {return TempFile.createTempFile("poi-sxssf-sheet", ".xml");}上面代碼我們可以知道,POI會生成一個(gè)前綴為’poi-sxssf-sheet’,后綴為’xml’的臨時(shí)文件來存放表格數(shù)據(jù)的DOM結(jié)構(gòu)。
POI提供了TempFileCreationStrategy接口的默認(rèn)實(shí)現(xiàn)DefaultTempFileCreationStrategy來決定臨時(shí)文件生成的目錄:
JAVA_IO_TMPDIR實(shí)際上是JVM的系統(tǒng)變量java.io.tmpdir,由此我們可以知道SXSSF默認(rèn)獲取了JVM的臨時(shí)文件目錄來作為自己存放臨時(shí)文件的目錄。所以我們可以通過以下三種方式(推薦采用第三種)改變SXSSF臨時(shí)文件的目錄:
- 設(shè)置JVM系統(tǒng)變量 -Djava.io.tmpdir=xxx 此方法會改變JVM所有的臨時(shí)文件目錄。
- 實(shí)現(xiàn)TempFileCreationStrategy的接口
- DefaultTempFileCreationStrategy的構(gòu)造方法提供了 dir參數(shù)的構(gòu)造:
public DefaultTempFileCreationStrategy(File dir) { this.dir = dir; }
重新構(gòu)造DefaultTempFileCreationStrategy實(shí)例傳值給org.apache.poi.util.TempFile類:
臨時(shí)文件的壓縮
SXSSF在臨時(shí)文件中刷新表數(shù)據(jù)(每頁一個(gè)臨時(shí)文件),這些臨時(shí)文件的大小可以增長到非常大的值。例如,對于20 MB的CSV數(shù)據(jù),臨時(shí)XML的大小超過了1000MB。如果臨時(shí)文件的大小有問題,可以告訴SXSSF使用gzip壓縮:
SXSSFWorkbook wb = new SXSSFWorkbook(100); TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(new File("H:\\Temp"))); //壓縮臨時(shí)文件 wb.setCompressTempFiles(true);在SXSSFWorkbook類中有以下判斷,我們可以看出,當(dāng)設(shè)置了以上參數(shù),SXSSF會用GZIP的方式壓縮臨時(shí)文件:
protected SheetDataWriter createSheetDataWriter() throws IOException {if(_compressTmpFiles) {return new GZIPSheetDataWriter(_sharedStringSource);}return new SheetDataWriter(_sharedStringSource);}壓縮后的臨時(shí)文件如下:
可以看到,原本621MB的臨時(shí)文件壓縮后只有42MB。但是采用壓縮顯而易見的會影響到EXCEL導(dǎo)出的性能,期間的權(quán)衡應(yīng)該以真實(shí)的業(yè)務(wù)場景來考慮。
實(shí)際上SXSSF所有對DOM文檔的操作都直接映射在了XSSF上,只是在外層提供了刷新磁盤的功能。具體是如何實(shí)現(xiàn)的,且聽下回分解。
總結(jié)
以上是生活随笔為你收集整理的基于流的EXCEL文件导出,SXSSFWorkbook源码解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pom.xml中依赖的<optional
- 下一篇: 第四届工业大数据创新竞赛-Top1方案