根據前面咱們的方式解決百萬導出,那麼就要作百萬次循環apache
//獲取數據 private static Object getValue(Cell cell) { Object value = null; switch (cell.getCellType()) { case STRING: //字符串類型 value = cell.getStringCellValue(); break; case BOOLEAN: //boolean類型 value = cell.getBooleanCellValue(); break; case NUMERIC: //數字類型(包含日期和普通數字) if(DateUtil.isCellDateFormatted(cell)) { value = cell.getDateCellValue(); }else{ value = cell.getNumericCellValue(); } break; case FORMULA: //公式類型 value = cell.getCellFormula(); break; default: break; } return value; }
//1.根據上傳流信息建立工做簿 Workbook workbook = WorkbookFactory.create(attachment.getInputStream()); //2.獲取第一個sheet Sheet sheet = workbook.getSheetAt(0); List<User> users = new ArrayList<>(); //3.從第二行開始獲取數據 for (int rowNum = 1; rowNum <sheet.getLastRowNum(); rowNum++) { //獲取行對象 Row row = sheet.getRow(rowNum); //獲取該行的全部列單元格數量 Object objs[] = new Object[row.getLastCellNum()]; //從第二列獲取數據 for(int cellNum = 0; cellNum < row.getLastCellNum();cellNum++) { Cell cell = row.getCell(cellNum); objs[cellNum] = getValue(cell); } //根據每一列構造用戶對象 User user = new User(objs); users.add(user); } //第一個參數:用戶列表,第二個參數:部門編碼 userService.save(users); return Result.SUCCESS();
這種方式去執行百萬數據的讀取,那麼咱們在Jvisualvm看看效率segmentfault
觀看如圖所示,咱們發現會不斷的佔用內存,直到吃滿跑出oom異常xss
那麼爲何會這樣呢?ide
那麼爲何會佔用那麼多內存呢?工具
加載並讀取Excel時,是經過一次性的將全部數據加載到內存中再去解析每一個單元格內容。大數據
當Excel數據量較大時,因爲不一樣的運行環境可能會形成內存不足甚至OOM異常優化
在ApachePoi 官方提供了對操做大數據量的導入導出的工具和解決辦法,操做Excel2007使用XSSF對象,能夠分爲三種模式:編碼
建立太多的對象,很是耗內存
(以前使用的方法)基於SAX方式解析XML
,SAX全稱Simple API for XML,它是一個接口,也是一個軟件包。它是一種XML解析的替代方法,不一樣於DOM解析XML文檔時把全部內容一次性加載到內存中的方式,它逐行掃描文檔,一邊掃描,一邊解析
。主要原理是藉助臨時存儲空間生成excel
Apache POI官方提供有一張圖片,描述了基於用戶模式,事件模式,以及使用SXSSF三種方式操做Excel的特性以及CUP和內存佔用狀況spa
咱們剛剛進行問題分析知道當咱們加載並讀取Excel時,是經過一次性的將全部數據加載到內存中再去解析每一個單元格內容線程
當百萬數據級別的Excel導入時,隨着單元格的不斷讀取,內存中對象愈來愈多,直至內存溢出。
上面提到POI提供剛給excel大數據時讀取有兩種模式:
1.用戶模式:使用系列封裝好的API操做Excel,使得操做簡單但佔用內存
2.事件驅動:基於sax的讀取方式,逐行掃描文檔,一邊掃描,一邊解析
那麼爲何使用事件驅動就能夠解決這個問題呢?
事件驅動通常來講會有一個事件監聽的主線程,而全部要處理的事件則須要註冊意思指發生某件事情的時候,委託給事件處理器進行處理
好比說:有一個事件處理器當每月發工資時準時存款一半工資
咱們剛剛提到過SAX是基於事件驅動的一種xml解析方案
那麼事件處理器在解析excel的時候是如何去使用的呢?
其實就是指定xml裏的一些節點,在指定的節點解析後觸發事件
按照這樣的思路方法:
咱們在解析excel時每一行完成後進行觸發事件,事件作的事情就是將解析的當前行的內存進行銷燬
這樣咱們進行百萬數據處理的時候,則處理完一行就釋放一行
可是也有缺點:由於咱們是處理完一行就釋放一行,則用完就銷燬了不可在用
那麼根據SAX事件驅動進行讀取數據主要分幾種步驟
1.設置POI的事件模式,指定使用事件驅動去解析EXCEL來作
2.使用Sax進行解析
接下來咱們根據步驟思路,去實現百萬數據的讀取,首先咱們將excel對應的數據實體類建立
public class PoiEntity { private String id; private String name; private String tel; //省略get、set方法 }
接着建立咱們的自定義Sheet基於Sax的解析處理器SheetHandler
/** * 自定義Sheet基於Sax的解析處理器 * 處理每一行數據讀取 * 實現接口org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler */ public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler { //封裝實體對象 private PoiEntity entity; /** * 解析行開始 */ @Override public void startRow(int rowNum) { if (rowNum >0 ) { entity = new PoiEntity(); } } /** * 解析每個單元格 * cellReference 單元格名稱 * formattedValue 單元格數據值 * comment 單元格批註 */ @Override public void cell(String cellReference, String formattedValue, XSSFComment comment){ if(entity != null) { //由於單元格名稱比較長,因此截取首字母 switch (cellReference.substring(0, 1)) { case "A": entity.setId(formattedValue); break; case "B": entity.setName(formattedValue); break; case "C": entity.setTel(formattedValue); break; default: break; } } } /** * 解析每一行結束時觸發 */ public void endRow(int rowNum) { System.out.println(entity); //通常進行使用對象進行業務處理..... } }
接下來咱們使用事件模型來解析百萬數據excel報表
//excel文檔路徑 String path = "e:\\demo.xlsx"; //============設置POI的事件模式,指定使用事件驅動去解析EXCEL來作============ //1.根據Excel獲取OPCPackage對象 OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ); //2.建立XSSFReader對象 XSSFReader reader = new XSSFReader(pkg); //3.獲取String類型表格SharedStringsTable對象 SharedStringsTable sst = reader.getSharedStringsTable(); //4.獲取樣式表格StylesTable對象 StylesTable styles = reader.getStylesTable(); //============使用Sax進行解析============ //5.建立Sax的XmlReader對象 XMLReader parser = XMLReaderFactory.createXMLReader(); //6.設置Sheet的事件處理器 parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst,new SheetHandler(), false)); //7.逐行讀取(由於有多個sheet因此須要迭代讀取) XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator)reader.getSheetsData(); while (sheets.hasNext()) { InputStream sheetstream = sheets.next();//每個sheet的數據流 InputSource sheetSource = new InputSource(sheetstream); try { parser.parse(sheetSource); } finally { sheetstream.close(); } }
讓咱們使用Jvisualvm 看看事件驅動的方式是什麼效率吧
經過簡單的分析以及運行兩種模式進行比較,能夠看到用戶模式下使用更簡單的代碼實現了Excel讀取。
可是在讀取大文件時CPU和內存都不理想;而事件模式雖然代碼寫起來比較繁瑣,可是在讀取大文件時CPU和內存更加佔優。