SXSSF位於org.apache.poi.xssf.streaming包中,在兼容XSSF的同時,能夠應對大數據量和內存空間有限的情況。SXSSF每次獲取的行數是在一個數值范圍內,這個范圍被稱為“滑動窗口”,在這個窗口內的數據均存在於內存中,超出這個窗口大小時,數據會被寫入磁盤,由此控制內存使用,相比較而言,XSSF則每次都是獲取全部行。
這個“滑動窗口”的大小在定義SXSSF實例的時候,可以由構造函數中的參數指定,方法如下:
1 new SXSSFWorkbook(int windowSize)
SXSSFWorkbook.DEFAULT_WINDOW_SIZE是一個默認的窗口大小,其值為100。窗口大小為-1,表示不限制窗口大小,這里普及一下,excel 2003最多只允許存儲65536條數據,excel2007以上版本可以支持1048576條數據,單個sheet表就支持近104萬條數據了。如果窗口大小=-1,則只有手動調用flushRows()時,數據才會被寫入磁盤。
createRow()可以創建一個新的行,此時內存中的"窗口"大小就增加,如果超出了窗口限制,索引值最小的行會先被“刷入”磁盤中,一旦某行數據被寫入磁盤,則不能使用getRow()方法獲取該數據。
需要注意,SXSSF會自動分配臨時文件,這些臨時文件需要我們手動清除,清除的方式是使用dispose()方法,例如:
SXSSFWorkbook wb = new SXSSFWorkbook(100); wb.dispose();
SXSSFWorkbook默認使用內聯字符(inline strings),而不是共享字符(shared strings),兩者的區別在於,如果兩個單元格存儲了相同的字符串,inline strings把它們當做兩個字符串對待,每個單元格都保留此字符串的值,而shared strings則只在內存中保留一個值,單元格中只保留這個字符串的引用。
inline strings的好處是比較高效,因為不需要在內存中保留字符串的內容,壞處是有可能存在兼容性問題。而shared strings的好處是當文檔中存在很多重復的字符內容時,該方式能節省空間,產生的文檔也會相對更小,壞處是需要把所有字符串內容保存在內存中,這樣插入新的字符數據的時候才能知道是否已經存在。因此需要根據內存的實際情況,判斷使用哪種string。
除了字符之外,諸如合並單元格、超鏈接、批注等是直接存儲在內存里面的,因此如果文檔中大量使用這些特性,需要留意內存的空間。
下面這個例子將“窗口”大小設置為100,當行數達到101時,第0行的數據被寫入磁盤,然后當行數=102時,第1行的數據被寫入磁盤,以此類推:
import junit.framework.Assert; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellReference; import org.apache.poi.xssf.streaming.SXSSFWorkbook; public static void main(String[] args) throws Throwable { SXSSFWorkbook wb = new SXSSFWorkbook(100); // 在內存中保留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); } } // 行號小於900的數據被寫入磁盤,在內存中無法訪問 for(int rownum = 0; rownum < 900; rownum++){ Assert.assertNull(sh.getRow(rownum)); } // 最后的100行數據仍然在內存中 for(int rownum = 900; rownum < 1000; rownum++){ Assert.assertNotNull(sh.getRow(rownum)); } FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx"); wb.write(out); out.close(); // 將此workbook對應的臨時文件刪除 wb.dispose(); }
第二個例子演示,在關閉自動寫入磁盤的屬性(即將窗口大小設置為-1)時,手動控制何時寫入磁盤:
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellReference; import org.apache.poi.xssf.streaming.SXSSFWorkbook; public static void main(String[] args) throws Throwable { SXSSFWorkbook wb = new SXSSFWorkbook(-1); // 關閉自動寫入磁盤的功能,所有數據將放在內存中 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); } // 手動控制寫入磁盤的時機 if(rownum % 100 == 0) { ((SXSSFSheet)sh).flushRows(100); // 保留最后100行數據,將其余數據寫入磁盤中 // ((SXSSFSheet)sh).flushRows() 相當於 ((SXSSFSheet)sh).flushRows(0),表示將所有數據寫入磁盤 } } FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx"); wb.write(out); out.close(); // 刪除臨時文件 wb.dispose(); }