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(); } }
这种模式读取一行删除一行,过程不可逆,不会造成内存溢出。