从零开始SpringCloud Alibaba实战(88)——poi excel 百万数据大数据量导入

前言

POI读取Excel的方式

用户模式:也就是poi下的usermodel有关包,它对用户友好,有统一的接口在ss包下,但是它是把整个文件读取到内存中的,

对于大量数据很容易内存溢出,所以只能用来处理相对较小量的数据;

事件模式:在poi下的eventusermodel包下,相对来说实现比较复杂,但是它处理速度快,占用内存少,可以用来处理海量的Excel数据。

所以使用POI的用户模式去读取Excel大文件,会导致内存泄漏。读取大文件用事件模式。

代码

pom

  <!-- POI,excel导入需要的 start -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/xerces/xercesImpl -->
        <dependency>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
            <version>2.11.0</version>
        </dependency>

        <dependency>
            <groupId>xml-apis</groupId>
            <artifactId>xml-apis</artifactId>
            <version>1.4.01</version>
        </dependency>

工具类

package com.vstsoft.sjyw.tools;

/**
 * @author lx
 * @version 1.0
 * @description: TODO
 * @date 2021/10/19 14:49
 */
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder;
import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFRequest;
import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener;
import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord;
import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.BoolErrRecord;
import org.apache.poi.hssf.record.BoundSheetRecord;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.LabelRecord;
import org.apache.poi.hssf.record.LabelSSTRecord;
import org.apache.poi.hssf.record.NumberRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.hssf.record.StringRecord;
import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * excel大量数据读取工具类
 *
 */
public class ExcelBigDataReadUtil {

    /**
     * 2003版后缀
     */
    private String suffix2003 = ".xls";
    /**
     * 2007版后缀
     */
    private String suffix2007 = ".xlsx";

    /**
     * 读取excel文件
     *
     * @param filePath 文件路径
     * @param sheetNum 第几个sheetNum工作薄,从1开始,为null的时候表示全部读取
     * @param processRowsInterface 处理行数据的接口
     * @throws Exception
     */
    public void readExcel(String filePath,Integer sheetNum,ProcessRowsInterface processRowsInterface) throws Exception {
        if (filePath.endsWith(suffix2003)) {
            XlsReader xlsR = new XlsReader();
            xlsR.process(filePath, sheetNum, processRowsInterface);
        } else if (filePath.endsWith(suffix2007)) {
            XlsxReader xlsxR = new XlsxReader();
            xlsxR.process(filePath, sheetNum, processRowsInterface);
        } else {
            throw new Exception("Only excel files with XLS or XLSX suffixes are allowed to be read!");
        }
    }

    /**
     * 处理行数据的接口
     *
     */
    public interface ProcessRowsInterface {

        /**
         * 设置行数据
         * @param sheetName 工作薄名称
         * @param sheetIndex 工作薄索引
         * @param curRow 当前行,不包装第一行,第一默认为标题
         * @param cellMap 当前行的单元格Map集合,key为单元格所对应的标题,value为单元格内容
         */
        void setRows(String sheetName, int sheetIndex, int curRow, Map<String,Object> cellMap);
    }

    /**
     * 用于excel2003版本的读取
     *
     */
    public class XlsReader implements HSSFListener {

        private int minColums = -1;

        private POIFSFileSystem fs;
        /**
         * 处理行数据的接口
         */
        private ProcessRowsInterface processRowsInterface;
        /**
         * 上一行row的序号
         */
        private int lastRowNumber;
        /**
         * 上一单元格的序号
         */
        private int lastColumnNumber;
        /**
         * 是否输出formula,还是它对应的值
         */
        private boolean outputFormulaValues = true;
        /**
         * 用于转换formulas
         */
        private EventWorkbookBuilder.SheetRecordCollectingListener workbookBuildingListener;
        /**
         * excel2003工作簿
         */
        private HSSFWorkbook stubWorkbook;

        private SSTRecord sstRecord;

        private FormatTrackingHSSFListener formatListener;

        private final HSSFDataFormatter formatter = new HSSFDataFormatter();
        /**
         * 表索引
         */
        private int sheetIndex = 0;

        private BoundSheetRecord[] boundSheetRecords;

        private List<BoundSheetRecord> boundSheetRecordList = new ArrayList<BoundSheetRecord>();

        private int nextRow;

        private int nextColumn;

        private boolean outputNextStringRecord;
        /**
         * 当前行
         */
        private int curRow = 0;
        /**
         * 存储一行记录所有单元格的Map容器
         */
        private Map<String,Object> cellMap = new HashMap<String,Object>();
        /**
         * 存储标题行所有单元格的Map容器
         */
        private Map<Integer,String> titles = new HashMap<Integer,String>();
        /**
         * 判断整行是否为空行的标记
         */
        private boolean flag = false;

        private String sheetName;
        /**
         * 指定的sheet,默认值为-1,当为-1时,表示传入的值为null,将读取所有的sheet
         */
        private int sheetNum = -1;

        /**
         * 处理数据
         * @param filePath 文件路径
         * @param sheetNum 第几个sheetNum工作薄,从1开始,为null的时候表示全部读取,小于或等于0时默认为第1个工作薄
         * @param processRowsInterface 处理行数据的接口
         * @throws Exception
         */
        public void process(String filePath, Integer sheetNum, ProcessRowsInterface processRowsInterface) throws Exception {
            this.processRowsInterface = processRowsInterface;
            if (Objects.nonNull(sheetNum) && sheetNum <= 0) {
                sheetNum = 1;
            }
            if (Objects.nonNull(sheetNum)) {
                this.sheetNum = sheetNum;
            }
            this.fs = new POIFSFileSystem(new FileInputStream(filePath));
            MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this);
            formatListener = new FormatTrackingHSSFListener(listener);
            HSSFEventFactory factory = new HSSFEventFactory();
            HSSFRequest request = new HSSFRequest();
            if (outputFormulaValues) {
                request.addListenerForAllRecords(formatListener);
            } else {
                workbookBuildingListener = new EventWorkbookBuilder.SheetRecordCollectingListener(formatListener);
                request.addListenerForAllRecords(workbookBuildingListener);
            }
            factory.processWorkbookEvents(request, fs);
        }

        /**
         * HSSFListener 监听方法,处理Record(处理每个单元格)
         *
         * @param record
         */
        @Override
        public void processRecord(Record record) {
            int thisRow = -1;
            int thisColumn = -1;
            String thisStr = null;
            String value = null;
            switch (record.getSid()) {
                case BoundSheetRecord.sid:
                    boundSheetRecordList.add((BoundSheetRecord) record);
                    break;
                // 开始处理每个sheet
                case BOFRecord.sid:
                    BOFRecord br = (BOFRecord) record;
                    if (br.getType() == BOFRecord.TYPE_WORKSHEET) {
                        // 如果有需要,则建立子工作簿
                        if (Objects.nonNull(workbookBuildingListener) && Objects.isNull(stubWorkbook)) {
                            stubWorkbook = workbookBuildingListener.getStubHSSFWorkbook();
                        }
                        if (Objects.isNull(boundSheetRecords)) {
                            boundSheetRecords = BoundSheetRecord.orderByBofPosition(boundSheetRecordList);
                        }
                        sheetName = boundSheetRecords[sheetIndex].getSheetname();
                        sheetIndex++;
                    }
                    break;
                case SSTRecord.sid:
                    sstRecord = (SSTRecord) record;
                    break;
                // 单元格为空白
                case BlankRecord.sid:
                    BlankRecord brec = (BlankRecord) record;
                    thisRow = brec.getRow();
                    thisColumn = brec.getColumn();
                    thisStr = "";
                    if (curRow == 0) {
                        titles.put(thisColumn, thisStr);
                    } else {
                        cellMap.put(titles.get(thisColumn), thisStr);
                    }
                    break;
                // 单元格为布尔类型
                case BoolErrRecord.sid:
                    BoolErrRecord berec = (BoolErrRecord) record;
                    thisRow = berec.getRow();
                    thisColumn = berec.getColumn();
                    thisStr = String.valueOf(berec.getBooleanValue());
                    if (curRow == 0) {
                        titles.put(thisColumn, thisStr);
                    } else {
                        cellMap.put(titles.get(thisColumn), thisStr);
                    }
                    // 如果里面某个单元格含有值,则标识该行不为空行
                    checkRowIsNull(thisStr);
                    break;
                // 单元格为公式类型
                case FormulaRecord.sid:
                    FormulaRecord frec = (FormulaRecord) record;
                    thisRow = frec.getRow();
                    thisColumn = frec.getColumn();
                    if (outputFormulaValues) {
                        if (Double.isNaN(frec.getValue())) {
                            outputNextStringRecord = true;
                            nextRow = frec.getRow();
                            nextColumn = frec.getColumn();
                        } else {
                            thisStr = '"' + HSSFFormulaParser.toFormulaString(stubWorkbook, frec.getParsedExpression())
                                    + '"';
                        }
                    } else {
                        thisStr = '"' + HSSFFormulaParser.toFormulaString(stubWorkbook, frec.getParsedExpression()) + '"';
                    }
                    if (curRow == 0) {
                        titles.put(thisColumn, thisStr);
                    } else {
                        cellMap.put(titles.get(thisColumn), thisStr);
                    }
                    // 如果里面某个单元格含有值,则标识该行不为空行
                    checkRowIsNull(thisStr);
                    break;
                // 单元格中公式的字符串
                case StringRecord.sid:
                    if (outputNextStringRecord) {
                        StringRecord srec = (StringRecord) record;
                        thisStr = srec.getString();
                        thisRow = nextRow;
                        thisColumn = nextColumn;
                        outputNextStringRecord = false;
                    }
                    break;
                case LabelRecord.sid:
                    LabelRecord lrec = (LabelRecord) record;
                    curRow = thisRow = lrec.getRow();
                    thisColumn = lrec.getColumn();
                    value = lrec.getValue().trim();
                    if (curRow == 0) {
                        value = value.length() == 0 ? "空标题" + thisColumn : value;
                        titles.put(thisColumn, value);
                    } else {
                        value = value.length() == 0 ? "" : value;
                        cellMap.put(titles.get(thisColumn), value);
                    }
                    // 如果里面某个单元格含有值,则标识该行不为空行
                    checkRowIsNull(value);
                    break;
                // 单元格为字符串类型
                case LabelSSTRecord.sid:
                    LabelSSTRecord lsrec = (LabelSSTRecord) record;
                    curRow = thisRow = lsrec.getRow();
                    thisColumn = lsrec.getColumn();
                    if (Objects.isNull(sstRecord)) {
                        if (curRow == 0) {
                            titles.put(thisColumn, "空标题" + thisColumn);
                        } else {
                            cellMap.put(titles.get(thisColumn), "");
                        }
                    } else {
                        value = sstRecord.getString(lsrec.getSSTIndex()).toString().trim();
                        if (curRow == 0) {
                            value = value.length() == 0 ? "空标题" + thisColumn : value;
                            titles.put(thisColumn, value);
                        } else {
                            value = value.length() == 0 ? "" : value;
                            cellMap.put(titles.get(thisColumn), value);
                        }
                        // 如果里面某个单元格含有值,则标识该行不为空行
                        checkRowIsNull(value);
                    }
                    break;
                // 单元格为数字类型
                case NumberRecord.sid:
                    NumberRecord numrec = (NumberRecord) record;
                    curRow = thisRow = numrec.getRow();
                    thisColumn = numrec.getColumn();
                    // 第一种方式,这个被写死,采用的m/d/yy h:mm格式,不符合要求
                    /// value = formatListener.formatNumberDateCell(numrec).trim();
                    // 第二种方式,参照formatNumberDateCell里面的实现方法编写
                    Double valueDouble = ((NumberRecord) numrec).getValue();
                    String formatString = formatListener.getFormatString(numrec);
                    if (formatString.contains("m/d/yy")) {
                        formatString = "yyyy-MM-dd hh:mm:ss";
                    }
                    int formatIndex = formatListener.getFormatIndex(numrec);
                    value = formatter.formatRawCellContents(valueDouble, formatIndex, formatString).trim();

                    if (curRow == 0) {
                        value = value.length() == 0 ? "空标题" + thisColumn : value;
                        titles.put(thisColumn, value);
                    } else {
                        value = value.length() == 0 ? "" : value;
                        // 向容器加入列值
                        cellMap.put(titles.get(thisColumn), value);
                    }
                    // 如果里面某个单元格含有值,则标识该行不为空行
                    checkRowIsNull(value);
                    break;
                default:
                    break;
            }
            // 遇到新行的操作
            if (thisRow != -1 && thisRow != lastRowNumber) {
                lastColumnNumber = -1;
            }
            // 空值的操作
            if (record instanceof MissingCellDummyRecord) {
                MissingCellDummyRecord mc = (MissingCellDummyRecord) record;
                curRow = thisRow = mc.getRow();
                thisColumn = mc.getColumn();
                if (curRow == 0) {
                    titles.put(thisColumn, "空标题" + thisColumn);
                } else {
                    cellMap.put(titles.get(thisColumn), "");
                }
            }
            // 更新行和列的值
            if (thisRow > -1) {
                lastRowNumber = thisRow;
            }
            if (thisColumn > -1) {
                lastColumnNumber = thisColumn;
            }
            // 行结束时的操作
            if (record instanceof LastCellOfRowDummyRecord) {
                if (minColums > 0) {
                    // 列值重新置空
                    if (lastColumnNumber == -1) {
                        lastColumnNumber = 0;
                    }
                }
                lastColumnNumber = -1;
                // 该行不为空行且该行不是第一行,并且sheet索引等于指定sheet或指定sheet为默认值-1(即sheetNum==-1表示读取全部sheet),发送(第一行为列名,不需要)
                if (flag && curRow != 0 && (sheetIndex == sheetNum || sheetNum == -1)) {
                    processRowsInterface.setRows(sheetName, sheetIndex, curRow + 1, cellMap);
                }
                flag = false;
            }
        }

        /**
         * 如果里面某个单元格含有值,则标识该行不为空行
         * @param value
         */
        public void checkRowIsNull(String value) {
            if (Objects.nonNull(value) && value.length() != 0) {
                flag = true;
            }
        }
    }

    /**
     * 单元格中的数据可能的类型
     */
    enum CellType {
        /**空值*/
        NULL,
        /**日期类型*/
        DATE,
        /**INLINESTR*/
        INLINESTR,
        /**数字类型*/
        NUMERIC,
        /**字符串类型 */
        STRING,
        /**公式类型*/
        FORMULA,
        /**布尔值类型*/
        BOOLEAN,
        /**错误类型*/
        ERROR;
    }

    /**
     * 用于excel2007版本的读取
     */
    public class XlsxReader extends DefaultHandler {

        /**
         * 共享字符串表
         */
        private SharedStringsTable sst;
        /**
         * 标题行Map集合
         */
        private Map<Integer,String> titles = new HashMap<Integer,String>();
        /**
         * 上一次的索引值
         */
        private String lastIndex;
        /**
         * 工作表索引
         */
        private int sheetIndex = 0;
        /**
         * sheet名称
         */
        private String sheetName = "";
        /**
         * 一行内cell集合
         */
        private Map<String,Object> cellMap = new HashMap<String,Object>();
        /**
         * 判断整行是否为空行的标记
         */
        private boolean flag = false;
        /**
         * 当前行
         */
        private int curRow = 1;
        /**
         * 当前列
         */
        private int curCol = 0;
        /**
         * T元素标识
         */
        private boolean isT;
        /**
         * 判断上一单元格是否为文本空单元格
         */
        private boolean startElementFlag = true;
        private boolean endElementFlag = false;
        private boolean charactersFlag = false;
        /**
         * 单元格数据类型,默认为字符串类型
         */
        private CellType nextCellType = CellType.STRING;

        private final DataFormatter formatter = new DataFormatter();
        /**
         * 单元格日期格式的索引
         */
        private short formatIndex;
        /**
         * 日期格式字符串
         */
        private String formatString;
        /**
         * 定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等
         */
        private String preRef = null, ref = null;
        /**
         * 定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格
         */
        private String maxRef = null;
        /**
         * 单元格样式表
         */
        private StylesTable stylesTable;
        /**
         * 处理行数据的接口
         */
        private ProcessRowsInterface processRowsInterface;

        /**
         * 处理数据
         * @param filePath 文件路径
         * @param sheetNum 第几个sheetNum工作薄,从1开始,为null的时候表示全部读取,小于或等于0时默认为第1个工作薄
         * @param processRowsInterface 处理行数据的接口
         * @throws Exception
         */
        public void process(String filePath, Integer sheetNum, ProcessRowsInterface processRowsInterface) throws Exception {
            if (Objects.nonNull(sheetNum) && sheetNum <= 0) {
                sheetNum = 1;
            }
            this.processRowsInterface = processRowsInterface;
            OPCPackage pkg = OPCPackage.open(filePath);
            XSSFReader xssfReader = new XSSFReader(pkg);
            stylesTable = xssfReader.getStylesTable();
            SharedStringsTable sst = xssfReader.getSharedStringsTable();
            // xml读取类
            XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
            this.sst = sst;
            parser.setContentHandler(this);
            XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
            // 遍历sheet
            while (sheets.hasNext()) {
                // 标记初始行为第一行
                curRow = 1;
                sheetIndex++;
                // sheets.next()和sheets.getSheetName()不能换位置,否则sheetName报错
                InputStream sheet = sheets.next();
                if (Objects.isNull(sheetNum) || sheetNum == sheetIndex) {
                    sheetName = sheets.getSheetName();
                    InputSource sheetSource = new InputSource(sheet);
                    // 解析excel的每条记录,在这个过程中startElement()、characters()、endElement()这三个函数会依次执行
                    parser.parse(sheetSource);
                    sheet.close();
                }
            }
        }

        /**
         * 重写DefaultHandler的startElement方法,该方法第一个执行
         *
         * @param uri 名称空间URI,如果元素没有名称空间URI,或者没有执行名称空间处理,则为空字符串。
         * @param localName 本地名称(没有前缀),如果没有执行名称空间处理,则为空字符串。
         * @param qName 限定名(带前缀),如果没有限定名,则使用空字符串。
         * @param attributes 附加到元素的属性。如果没有属性,则为空属性对象。
         * @throws SAXException
         */
        @Override
        public void startElement(String uri, String localName,
                                 String qName, Attributes attributes) throws SAXException {
            // c => 单元格
            if ("c".equals(qName)) {
                // 前一个单元格的位置
                if (Objects.isNull(preRef)) {
                    preRef = attributes.getValue("r");
                } else {
                    // 中部文本空单元格标识 ‘endElementFlag’
                    // 判断前一次是否为文本空字符串,true则表明不是文本空字符串,false表明是文本空字符串跳过把空字符串的位置赋予preRef
                    if (endElementFlag) {
                        preRef = ref;
                    }
                }
                // 当前单元格的位置
                ref = attributes.getValue("r");
                // 首部文本空单元格标识 ‘startElementFlag’
                // 判断前一次,即首部是否为文本空字符串,true则表明不是文本空字符串,false表明是文本空字符串,且已知当前格,即第二格带“B”标志,则ref赋予preRef
                // 上一个单元格为文本空单元格,执行下面的,使ref=preRef;flag为true表明该单元格之前有数据值,即该单元格不是首部空单元格,则跳过
                if (!startElementFlag && !flag) {
                    // 这里只有上一个单元格为文本空单元格,且之前的几个单元格都没有值才会执行
                    preRef = ref;
                }
                // 设定单元格类型
                this.setNextCellType(attributes);
                endElementFlag = false;
                charactersFlag = false;
                startElementFlag = false;
            }
            // 当元素为t时
            if ("t".equals(qName)) {
                isT = true;
            } else {
                isT = false;
            }
            // 置空
            lastIndex = "";
        }

        /**
         * 重写DefaultHandler的characters方法,该方法第二个执行
         * 得到单元格对应的索引值或是内容值
         * <pre>
         * 如果单元格类型是字符串、INLINESTR、数字、日期,lastIndex则是索引值
         * 如果单元格类型是布尔值、错误、公式,lastIndex则是内容值
         * </pre>
         * @param ch 字符数组
         * @param start 字符数组中的起始位置。
         * @param length 要从字符数组中使用的字符数。
         * @throws SAXException
         */
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            startElementFlag = true;
            charactersFlag = true;
            lastIndex += new String(ch, start, length);
        }

        /**
         * 重写DefaultHandler的endElement方法,该方法第三个执行
         *
         * @param uri 名称空间URI,如果元素没有名称空间URI,或者没有执行名称空间处理,则为空字符串。
         * @param localName 本地名称(没有前缀),如果没有执行名称空间处理,则为空字符串。
         * @param qName 限定名(带前缀),如果没有限定名,则使用空字符串。
         * @throws SAXException
         */
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            // t元素也包含字符串
            // 这个程序没经过
            if (isT) {
                // 将单元格内容加入cellMap中,在这之前先去掉字符串前后的空白符
                String value = lastIndex.trim();
                // 默认第一行为标题,把标题装入数组
                if (curRow == 1) {
                    // 补全标题行单元格之间的空单元格
                    if (!ref.equals(preRef)) {
                        int len = countNullCell(ref, preRef);
                        for (int i = 0; i < len; i++) {
                            titles.put(curCol, "空标题" + curCol);
                            curCol++;
                        }
                        // ref等于preRef,且以B或者C...开头,表明首部为空格
                    } else if (ref.equals(preRef)) {
                        int len = countNullCell(ref, "A");
                        for (int i = 0; i < len; i++) {
                            titles.put(curCol, "空标题" + curCol);
                            curCol++;
                        }
                    }
                    titles.put(curCol, value);
                } else {
                    // 补全单元格之间的空单元格
                    if (!ref.equals(preRef)) {
                        int len = countNullCell(ref, preRef);
                        for (int i = 0; i < len; i++) {
                            cellMap.put(titles.get(curCol), "");
                            curCol++;
                        }
                        // ref等于preRef,且以B或者C...开头,表明首部为空格
                    } else if (ref.equals(preRef)) {
                        cellMap.put(titles.get(curCol), "");
                        int len = countNullCell(ref, "A");
                        for (int i = 0; i < len; i++) {
                            cellMap.put(titles.get(curCol), "");
                            curCol++;
                        }
                    }
                    cellMap.put(titles.get(curCol), value);
                }
                curCol++;
                endElementFlag = true;
                isT = false;
                // 如果里面某个单元格含有值,则标识该行不为空行
                if (Objects.nonNull(value) && value.length() != 0) {
                    flag = true;
                }
            } else if ("v".equals(qName)) {
                // v => 单元格的值,如果单元格是字符串,则v标签的值为该字符串在SST中的索引
                // 根据索引值获取对应的单元格值
                String value = this.getDataValue(lastIndex.trim(), "");
                // 默认第一行为标题,把标题装入数组
                if (curRow == 1) {
                    // 补全标题行单元格之间的空单元格
                    if (!ref.equals(preRef)) {
                        int len = countNullCell(ref, preRef);
                        for (int i = 0; i < len; i++) {
                            titles.put(curCol, "空标题" + curCol);
                            curCol++;
                        }
                        // ref等于preRef,且以B或者C...开头,表明首部为空格
                    } else if (ref.equals(preRef)) {
                        int len = countNullCell(ref, "A");
                        for (int i = 0; i < len; i++) {
                            titles.put(curCol, "空标题" + curCol);
                            curCol++;
                        }
                    }
                    titles.put(curCol, value);
                } else {
                    // 补全单元格之间的空单元格
                    if (!ref.equals(preRef)) {
                        int len = countNullCell(ref, preRef);
                        for (int i = 0; i < len; i++) {
                            cellMap.put(titles.get(curCol), "");
                            curCol++;
                        }
                        // ref等于preRef,且以B或者C...开头,表明首部为空格
                    } else if (ref.equals(preRef)) {
                        cellMap.put(titles.get(curCol), "");
                        int len = countNullCell(ref, "A");
                        for (int i = 0; i < len; i++) {
                            cellMap.put(titles.get(curCol), "");
                            curCol++;
                        }
                    }
                    cellMap.put(titles.get(curCol), value);
                }
                curCol++;
                endElementFlag = true;
                // 如果里面某个单元格含有值,则标识该行不为空行
                if (Objects.nonNull(value) && value.length() != 0) {
                    flag = true;
                }
            } else {
                // 如果标签名称为row,这说明已到行尾,调用setRows()方法
                if ("row".equals(qName)) {
                    // 默认第一行为表头,以该行单元格数目为最大数目
                    if (curRow == 1) {
                        maxRef = ref;
                    }
                    // 补全一行尾部可能缺失的单元格
                    if (Objects.nonNull(maxRef)) {
                        int len = -1;
                        // 前一单元格,true则不是文本空字符串,false则是文本空字符串
                        if (charactersFlag) {
                            len = countNullCell(maxRef, ref);
                        } else {
                            len = countNullCell(maxRef, preRef);
                        }
                        for (int i = 0; i <= len; i++) {
                            cellMap.put(titles.get(curCol), "");
                            curCol++;
                        }
                    }
                    // 该行不为空行且该行不是第一行,则设置(第一行为列名,不需要)
                    if (flag && curRow != 1) {
                        processRowsInterface.setRows(sheetName, sheetIndex, curRow, cellMap);
                    }
                    curRow++;
                    curCol = 0;
                    preRef = null;
                    ref = null;
                    flag = false;
                }
            }
        }

        /**
         * 处理数据类型
         * @param attributes
         */
        public void setNextCellType(Attributes attributes) {
            // cellType为空,则表示该单元格类型为数字
            nextCellType = CellType.NUMERIC;
            formatIndex = -1;
            formatString = null;
            // 单元格类型
            String cellType = attributes.getValue("t");
            String cellStyleStr = attributes.getValue("s");
            // 处理布尔值
            if ("b".equals(cellType)) {
                nextCellType = CellType.BOOLEAN;
                // 处理错误
            } else if ("e".equals(cellType)) {
                nextCellType = CellType.ERROR;
            } else if ("inlineStr".equals(cellType)) {
                nextCellType = CellType.INLINESTR;
                // 处理字符串
            } else if ("s".equals(cellType)) {
                nextCellType = CellType.STRING;
                // 处理公式
            } else if ("str".equals(cellType)) {
                nextCellType = CellType.FORMULA;
            }
            // 处理日期
            if (Objects.nonNull(cellStyleStr)) {
                int styleIndex = Integer.parseInt(cellStyleStr);
                XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
                formatIndex = style.getDataFormat();
                formatString = style.getDataFormatString();
                if (formatString.contains("m/d/yyyy") || formatString.contains("yyyy/mm/dd")
                        || formatString.contains("yyyy/m/d")) {
                    nextCellType = CellType.DATE;
                    formatString = "yyyy-MM-dd hh:mm:ss";
                }
                if (Objects.isNull(formatString)) {
                    nextCellType = CellType.NULL;
                    formatString = BuiltinFormats.getBuiltinFormat(formatIndex);
                }
            }
        }

        /**
         * 对解析出来的数据进行类型处理
         * @param value 单元格的值
         * value代表解析:BOOL的为0或1, ERROR的为内容值,FORMULA的为内容值,INLINESTR的为索引值需转换为内容值,
         * SSTINDEX的为索引值需转换为内容值, NUMBER为内容值,DATE为内容值
         * @param thisStr 一个空字符串
         * @return
         */
        public String getDataValue(String value, String thisStr) {
            // 这几个的顺序不能随便交换,交换了很可能会导致数据错误
            switch (nextCellType) {
                // 布尔值
                case BOOLEAN:
                    char first = value.charAt(0);
                    thisStr = first == '0' ? "FALSE" : "TRUE";
                    break;
                // 错误
                case ERROR:
                    thisStr = "\"ERROR:" + value.toString() + '"';
                    break;
                // 公式
                case FORMULA:
                    thisStr = '"' + value.toString() + '"';
                    break;
                case INLINESTR:
                    XSSFRichTextString rtsi = new XSSFRichTextString(value.toString());
                    thisStr = rtsi.toString();
                    rtsi = null;
                    break;
                // 字符串
                case STRING:
                    String sstIndex = value.toString();
                    try {
                        int idx = Integer.parseInt(sstIndex);
                        // 根据idx索引值获取内容值,旧版本XSSFRichTextString rtss = new XSSFRichTextString(sst.getEntryAt(idx));
                        XSSFRichTextString rtss = (XSSFRichTextString) sst.getItemAt(idx);
                        thisStr = rtss.toString();
                        // 有些字符串是文本格式的,但内容却是日期
                        rtss = null;
                    } catch (NumberFormatException ex) {
                        thisStr = value.toString();
                    }
                    break;
                // 数字
                case NUMERIC:
                    if (Objects.nonNull(formatString)) {
                        thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString).trim();
                    } else {
                        thisStr = value;
                    }
                    thisStr = thisStr.replace("_", "").trim();
                    break;
                // 日期
                case DATE:
                    thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString);
                    // 对日期字符串作特殊处理,去掉T
                    thisStr = thisStr.replace("T", " ");
                    break;
                default:
                    thisStr = " ";
                    break;
            }
            return thisStr;
        }

        /**
         * 得到两个单元格之间的空单元格数量
         * @param ref 当前单元格位置
         * @param preRef 前一个单元格位置
         * @return
         */
        public int countNullCell(String ref, String preRef) {
            // excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD
            String xfd = ref.replaceAll("\\d+", "");
            String xfd1 = preRef.replaceAll("\\d+", "");

            xfd = fillChar(xfd, 3, '@', true);
            xfd1 = fillChar(xfd1, 3, '@', true);

            char[] letter = xfd.toCharArray();
            char[] letter1 = xfd1.toCharArray();
            int res = (letter[0] - letter1[0]) * 26 * 26 + (letter[1] - letter1[1]) * 26 + (letter[2] - letter1[2]);
            return res - 1;
        }

        public String fillChar(String str, int len, char let, boolean isPre) {
            int strLen = str.length();
            if (strLen < len) {
                if (isPre) {
                    for (int i = 0; i < (len - strLen); i++) {
                        str = let + str;
                    }
                } else {
                    for (int i = 0; i < (len - strLen); i++) {
                        str = str + let;
                    }
                }
            }
            return str;
        }
    }
}



测试

package com.vstsoft.sjyw.tools;

import java.util.Map;

/**
 * excel大量数据读取工具测试类
 */
public class ExcelBigDataReadUtilTest {

   // private ExcelBigDataReadUtil e = new ExcelBigDataReadUtil();


   // public void readExcel2003() throws Exception {
       /* String filePath = "E:\\excel\\测试数据_60000条.xls";
        e.readExcel(filePath, 1, new ExcelBigDataReadUtil.ProcessRowsInterface() {
            @Override
            public void setRows(String sheetName, int sheetIndex, int curRow, Map<String, Object> cellMap) {
                StringBuffer sb = new StringBuffer();
                sb.append("xls-sheet" + sheetIndex);
                sb.append("::" + sheetName);
                sb.append("--");
                sb.append("row_" + curRow);
                sb.append("{");
                for (Map.Entry<String, Object> cell : cellMap.entrySet()) {
                    sb.append(cell.getKey());
                    sb.append(":");
                    sb.append(cell.getValue());
                    sb.append("|");
                }
                String line = sb.toString();
                if (line.endsWith("|")) {
                    line = line.substring(0, line.lastIndexOf("|"));
                } // 去除最后一个分隔符
                line += "}";
                System.out.println(line);
            }
        });
    }
*/

   /* public void readExcel2007() throws Exception {
        String filePath = "E:\\excel\\测试数据_100万条.xlsx";
        e.readExcel(filePath, null, new ExcelBigDataReadUtil.ProcessRowsInterface() {
            @Override
            public void setRows(String sheetName, int sheetIndex, int curRow, Map<String, Object> cellMap) {
                StringBuffer sb = new StringBuffer();
                sb.append("xlsx-sheet" + sheetIndex);
                sb.append("::" + sheetName);
                sb.append("--");
                sb.append("row_" + curRow);
                sb.append("{");
                for (Map.Entry<String, Object> cell : cellMap.entrySet()) {
                    sb.append(cell.getKey());
                    sb.append(":");
                    sb.append(cell.getValue());
                    sb.append("|");
                }
                String line = sb.toString();
                if (line.endsWith("|")) {
                    line = line.substring(0, line.lastIndexOf("|"));
                } // 去除最后一个分隔符
                line += "}";
                System.out.println(line);
            }
        });
    }*/
    public static void main(String[] args) throws Exception {
        ExcelBigDataReadUtil e = new ExcelBigDataReadUtil();
        String filePath = "E:\\bp\\补贴名单数据导出 (3).xlsx";
        e.readExcel(filePath, 0, new ExcelBigDataReadUtil.ProcessRowsInterface() {
            @Override
            public void setRows(String sheetName, int sheetIndex, int curRow, Map<String, Object> cellMap) {
                StringBuffer sb = new StringBuffer();
                sb.append("xlsx-sheet" + sheetIndex);
                sb.append("::" + sheetName);
                sb.append("--");
                sb.append("row_" + curRow);
                sb.append("{");
                for (Map.Entry<String, Object> cell : cellMap.entrySet()) {
                    sb.append(cell.getKey());
                    sb.append(":");
                    sb.append(cell.getValue());
                    sb.append("|");
                }
                String line = sb.toString();
                if (line.endsWith("|")) {
                    line = line.substring(0, line.lastIndexOf("|"));
                } // 去除最后一个分隔符
                line += "}";
                System.out.println(line);
            }
        });

    }
}



读取结果

xlsx-sheet1::xxxx明细表--row_2{姓名:xx|补贴xx:集体xx|生成日期:2021-03-10|街xxxx中心:龙xxx|序号:1|证件号码:xxxxx|所属机构:龙潭xx社xx所|补贴类型:xx补助|补贴xx:2021}
xlsx-sheet1::xxx细表--row_3{姓名:杨xx1|补贴xx:xx补助|生成xx:2021-03-10|街乡xx中心:xxxx街道|序号:2|证件号码:xxxxx|所属机构:建国xx街道xx保x|xx类型:xx补助|补贴xx:2021}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值