1.解析Excel的幾種方式
用戶模式:加載並讀取Excel時,是通過一次性的將所有數據加載到內存中再去解析每個單元格內容。當Excel數據量較大時,由於不同的運行環境可能會造成內存不足甚至 OOM異常。
事件模式:它逐行掃描文檔,一邊掃描一邊解析。由於應用程序只是在讀取數據時檢查數據,因此不需要將數據存儲在內存中,這對於大型文檔的解析是個巨大優勢。
2.原理
我們都知道對於Excel2007的實質是一種特殊的XML存儲數據,那就可以使用基於SAX的方式解析XML完成Excel的讀取。SAX提供了一種從XML文檔中讀取數據的機制。它逐行掃描文檔,一邊掃描一邊解析。由於應用程序只是在讀取數據時檢查數據,因此不需要將數據存儲在內存中,這對於大型文檔的解析是個巨大優勢
代碼實現
自定義處理器
package com.zhao.common.util; import com.zhao.domain.User; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; import org.apache.poi.xssf.usermodel.XSSFComment; /** * 自定義的事件處理器 * 處理每一行數據讀取 * 實現接口 */ public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler { private int a=0; private User user; /** * 當開始解析某一行的時候觸發 * i:行索引 */ @Override public void startRow(int i) { //實例化對象,開始解析excel的第二行也就是下標為1的行 初始化對象 excel的第一行是標題 if(i>0) { user = new User(); } } /** * 當結束解析某一行的時候觸發 * i:行索引 */ @Override public void endRow(int i) { //使用對象進行業務操作, 一般是添加對象到數據庫中 a++; System.out.println(a+" "+user); } /** * 對行中的每一個表格進行處理 ,也就是一行中有多少個單元格 這個方法就會被調用幾次 * cellReference: 單元格名稱 * value:數據 * xssfComment:批注 */ @Override public void cell(String cellReference, String value, XSSFComment xssfComment) { //對對象屬性賦值 if(user != null) { String pix = cellReference.substring(0,1); switch (pix) { // case "A": // user.setName(value); // break; case "B": //excel的第2列 user.setName(value); break; case "C": //excel的第3列 user.setPassword(new Double(value).intValue()); break; // case "D": // user.setNegative(value); // break; // case "E": // user.setStaining(value); // break; // case "F": // user.setSupportive(value); // break; default: break; } } } }
自定義解析
web端
/** * 基於sax事件模型讀取百萬Excel數據 */ @PostMapping("readMillionExcelData") public void readMillionExcelData(@RequestParam(name = "file") MultipartFile file) throws Exception { //1.根據excel報表獲取基於事件模型的 OPCPackage對象 其實就是把這個Excel文件以壓縮包的形式打開成xml文件 PackageAccess.READ 只讀 OPCPackage opcPackage = OPCPackage.open(file.getInputStream()); //2.創建XSSFReader XSSFReader reader = new XSSFReader(opcPackage); //3.獲取SharedStringTable對象 SharedStringsTable table = reader.getSharedStringsTable(); //4.獲取styleTable對象 StylesTable stylesTable = reader.getStylesTable(); //5.創建Sax的xmlReader對象 XMLReader xmlReader = XMLReaderFactory.createXMLReader(); //6.注冊自定義的事件處理器 XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable, table, new SheetHandler(), false); xmlReader.setContentHandler(xmlHandler); //7.逐行讀取 得到的是所有的 sheet XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData(); while (sheetIterator.hasNext()) { InputStream stream = sheetIterator.next(); //每一個sheet的流數據 InputSource is = new InputSource(stream); //每一個sheet對象 xmlReader.parse(is); } }
PC端
package com.zhao.common; import com.zhao.common.util.SheetHandler; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; import java.io.InputStream; /** * 使用事件模型解析百萬數據excel報表 * * 基於事件模型,讀一行刪除一行, 不可逆的讀取 */ public class readMillionRowExcel { public static void main(String[] args) throws Exception { String path = "D:\\projectCode\\poiUtils\\demoRead.xlsx"; //1.根據excel報表獲取基於事件模型的 OPCPackage對象 其實就是把這個Excel文件以壓縮包的形式打開成xml文件 PackageAccess.READ 只讀 OPCPackage opcPackage = OPCPackage.open(path, PackageAccess.READ); //2.創建XSSFReader XSSFReader reader = new XSSFReader(opcPackage); //3.獲取SharedStringTable對象 SharedStringsTable table = reader.getSharedStringsTable(); //4.獲取styleTable對象 StylesTable stylesTable = reader.getStylesTable(); //5.創建Sax的xmlReader對象 XMLReader xmlReader = XMLReaderFactory.createXMLReader(); //6.注冊自定義的事件處理器 XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable, table, new SheetHandler(), false); xmlReader.setContentHandler(xmlHandler); //7.逐行讀取 得到的是所有的 sheet XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData(); while (sheetIterator.hasNext()) { InputStream stream = sheetIterator.next(); //每一個sheet的流數據 InputSource is = new InputSource(stream); //每一個sheet對象 xmlReader.parse(is); } } }
封裝的實體類
package com.zhao.domain; import com.zhao.common.util.ExcelAttribute; import com.zhao.common.util.ExcelImportAnnotation; import lombok.Data; import lombok.NoArgsConstructor; import java.text.DecimalFormat; @Data @NoArgsConstructor public class User { @ExcelAttribute(sort = 0) @ExcelImportAnnotation(sort = 2) private String name; @ExcelAttribute(sort = 1) @ExcelImportAnnotation(sort = 3) private Integer password; public User(Object[] args){ /** DecimalFormat 用法 * https://www.jianshu.com/p/b3699d73142e * Integer.valueOf 返回的時包裝類 Integer.parseInt() 返回的是int */ //因為傳進來的args 的賦值是從1開始的 this.name=args[1].toString(); //new DecimalFormat("#").format(args[2]).toString(); //因為從Excel中讀取的數字是double類型的 所以不能用 Integer.valueOf this.password=new Double(args[2].toString()).intValue(); } }
這種模式讀取一行刪除一行,過程不可逆,不會造成內存溢出。
