百萬行Excel的數據讀取(使用POI基於事件模式解析Excel文件)


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;
            }
        }
    }
}
View Code

自定義解析

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);
        }

    }
View Code

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);
        }
    }
}
View Code

封裝的實體類 

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();
    }

}
View Code

 這種模式讀取一行刪除一行,過程不可逆,不會造成內存溢出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM