主頁 > 後端開發 > Java 操作 Excel(3)--POI 事件模式讀寫Excel

Java 操作 Excel(3)--POI 事件模式讀寫Excel

2021-04-27 13:55:35 後端開發

Apache POI 是基于 Office Open XML 標準(OOXML)和 Microsoft  的 OLE 2復合檔案格式(OLE2)處理各種檔案格式的開源框架,本文主要介紹使用 POI 的事件模式來讀寫 Excel,POI 的事件模式消耗記憶體較小但編程復雜,適合大資料量,本文中所使用到的軟體版本:jdk1.8.0_181、POI 5.0.0,

1、引入依賴

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.0.0</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.0.0</version>
</dependency>
<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.12.1</version>
</dependency>

2、行資料處理介面

撰寫行數處理的通用介面,用于讀取一行資料后的回呼

package com.abc.demo.general.excel.event;

import java.util.List;

/**
 * 行資料處理器,每讀取一行資料后會回呼該介面的handle方法
 */
public interface IRowDataHandler {

    /**
     * 每行資料處理,在該方法里實作自己的業務邏輯
     * @param sheetIndex    sheet下標(從0開始)
     * @param sheetName     sheet名稱
     * @param row           當前行號(從0開始)
     * @param rowData       當前行資料
     */
    void handle(int sheetIndex, String sheetName, int row, List<String> rowData);
}

簡單實作:

package com.abc.demo.general.excel.event;

import java.util.List;

/**
 * 簡單列印每行資料
 */
public class SimpleRowDataHandler implements IRowDataHandler {
    @Override
    public void handle(int sheetIndex, String sheetName, int row, List<String> rowData) {
        System.out.println("sheetIndex=" + sheetIndex + ",sheetName=" + sheetName + ",row=" + row + ",rowData="https://www.cnblogs.com/wuyongyin/p/+ rowData);
    }
}

2、Excel 2003 事件模式讀

參考 POI 原始碼中給出的例子,然后自己改寫;原始碼位置如下:

 自己撰寫例子如下:

package com.abc.demo.general.excel.event;

import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener;
import org.apache.poi.hssf.eventusermodel.*;
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.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Excel 2003 事件方式讀取資料
 */
public class Excel2003Reader implements HSSFListener {
    private static Logger logger = LoggerFactory.getLogger(Excel2003Reader.class);

    /**最小的列數,不足補空字串*/
    private int minColumns = -1;

    /**Should we output the formula, or the value it has?*/
    private boolean outputFormulaValues = true;

    /**For parsing Formulas*/
    private SheetRecordCollectingListener workbookBuildingListener;
    //excel2003作業薄
    private HSSFWorkbook stubWorkbook;

    // Records we pick up as we process
    private SSTRecord sstRecord;
    private FormatTrackingHSSFListener formatListener;

    //表索引
    private int sheetIndex = -1;
    private BoundSheetRecord[] orderedBSRs;
    private String sheetName;

    private ArrayList boundSheetRecords = new ArrayList();

    // For handling formulas with string results
    private boolean outputNextStringRecord;

    //行資料
    private List<String> rowData = https://www.cnblogs.com/wuyongyin/p/new ArrayList<>();

    private IRowDataHandler rowDataHandler;

    public Excel2003Reader() {

    }
    public Excel2003Reader(IRowDataHandler rowDataHandler) {
        this.rowDataHandler = rowDataHandler;
    }

    public Excel2003Reader(IRowDataHandler rowDataHandler, int minColumns) {
        this.rowDataHandler = rowDataHandler;
        this.minColumns = minColumns;
    }

    public void setRowDataHandler(IRowDataHandler rowDataHandler) {
        this.rowDataHandler = rowDataHandler;
    }

    /**
     * 決議所有sheet資料
     * @param fileName
     * @throws IOException
     */
    public void process(String fileName) throws IOException {
        this.init();

        POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(fileName));
        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 SheetRecordCollectingListener(formatListener);
            request.addListenerForAllRecords(workbookBuildingListener);
        }
        factory.processWorkbookEvents(request, fs);
        fs.close();
    }

    private void init() {
        sheetIndex = -1;
        sheetName = "";
        boundSheetRecords.clear();
        workbookBuildingListener = null;
        stubWorkbook = null;
        orderedBSRs = null;
    }

    @Override
    public void processRecord(Record record) {
        String value;
        switch (record.getSid()) {
            case BoundSheetRecord.sid:
                BoundSheetRecord boundSheetRecord = (BoundSheetRecord) record;
                logger.info("作業表名稱: {}", boundSheetRecord.getSheetname());
                boundSheetRecords.add(record);
                break;

                //作業表或作業簿的開頭
            case BOFRecord.sid:
                BOFRecord bofRecord = (BOFRecord) record;
                if (bofRecord.getType() == BOFRecord.TYPE_WORKSHEET) {
                    //Create sub workbook if required
                    if (workbookBuildingListener != null && stubWorkbook == null) {
                        stubWorkbook = workbookBuildingListener.getStubHSSFWorkbook();
                    }
                    sheetIndex++;
                    if (orderedBSRs == null) {
                        orderedBSRs = BoundSheetRecord.orderByBofPosition(boundSheetRecords);
                    }
                    sheetName = orderedBSRs[sheetIndex].getSheetname();
                }
                break;

            case SSTRecord.sid:
                sstRecord = (SSTRecord) record;
                break;

            case BlankRecord.sid:
                rowData.add("");
                break;

                //布爾型別
            case BoolErrRecord.sid:
                BoolErrRecord boolErrRecord = (BoolErrRecord) record;
                value = boolErrRecord.getBooleanValue() + "";
                rowData.add(value);
                break;

                //公式
            case FormulaRecord.sid:
                FormulaRecord formulaRecord = (FormulaRecord) record;

                if (outputFormulaValues) {
                    if (Double.isNaN(formulaRecord.getValue())) {
                        //Formula result is a string,This is stored in the next record
                        outputNextStringRecord = true;
                    } else {
                        value = formatListener.formatNumberDateCell(formulaRecord);
                        rowData.add(value);
                    }
                } else {
                    value = '"' + HSSFFormulaParser.toFormulaString(stubWorkbook, formulaRecord.getParsedExpression()) + '"';
                    rowData.add(value);
                }

                break;

                //公式的字串
            case StringRecord.sid:
                if (outputNextStringRecord) {
                    //String for formula
                    StringRecord stringRecord = (StringRecord) record;
                    outputNextStringRecord = false;
                    rowData.add(stringRecord.getString());
                }
                break;

            case LabelRecord.sid:
                LabelRecord labelRecord = (LabelRecord) record;
                value = labelRecord.getValue().trim();
                rowData.add(value);
                break;

                //字串
            case LabelSSTRecord.sid:
                LabelSSTRecord labelSSTRecord = (LabelSSTRecord) record;
                value = "";
                if (sstRecord != null) {
                    value = sstRecord.getString(labelSSTRecord.getSSTIndex()).toString().trim();
                }
                rowData.add(value);
                break;

                //數字
            case NumberRecord.sid:
                NumberRecord numberRecord = (NumberRecord) record;
                value = formatListener.formatNumberDateCell(numberRecord).trim();
                rowData.add(value);
                break;

            default:
                //logger.warn("無效的型別:{}", record.getSid());
                break;
        }

        // 空值的操作
        if (record instanceof MissingCellDummyRecord) {
            rowData.add("");
        }

        //行結束
        if (record instanceof LastCellOfRowDummyRecord) {
            if (rowData.size() < minColumns) {
                int size = rowData.size();
                for (int i = 0; i < minColumns - size; i++) {
                    rowData.add("");
                }
            }
            rowDataHandler.handle(sheetIndex, sheetName, ((LastCellOfRowDummyRecord)record).getRow(), rowData);
            rowData.clear();
        }
    }

    public static void main(String[] args) throws IOException {
        Excel2003Reader excel2003Reader = new Excel2003Reader(new SimpleRowDataHandler(), 8);
        excel2003Reader.process("d:/a.xls");
    }
}

3、Excel 2007 事件模式讀寫

Excel 2007 使用 XML 來存盤資料,可以把一個 Excel 檔案的后綴改為 zip,再用解壓軟體打開,可以到里面的 XML 檔案;我們讀寫 Excel 只要使用 SAX 方法來處理 Sheet 對應的 XML 檔案,

3.1、Excel 2007 事件模式寫

先生成一個臨時的 XML 檔案來保存 Sheet 資料,然后通過 Zip 方式打開一個 Excel 模板檔案,把模板 Excel 里除了 Sheet 資料對應的 XML 檔案都拷貝到結果 Excel 檔案里,最后寫入保存了 Sheet 資料的 XML 檔案到結果檔案里,

3.1.1、輔助類

該類用于寫 XML 資料,

package com.abc.demo.general.excel.event;

import org.apache.poi.ss.util.CellReference;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;

class Excel2007WriterUtil {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    public static void beginSheet(Writer writer) throws IOException {
        writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">");
        writer.write("<sheetData>" + LINE_SEPARATOR);
    }

    public static void endSheet(Writer writer) throws IOException {
        writer.write("</sheetData>");
        writer.write("</worksheet>");
    }

    public static void beginRow(Writer writer, int rowNum) throws IOException {
        writer.write("<row r=\"" + rowNum + "\">" + LINE_SEPARATOR);
    }

    public static void endRow(Writer writer) throws IOException {
        writer.write("</row>" + LINE_SEPARATOR);
    }

    /**
     * 生成單元格節點
     * @param writer
     * @param rowIndex 行索引(從0開始)
     * @param columnIndex 列索引(從0開始)
     * @param value
     * @param styleIndex
     * @throws IOException
     */
    public static void createCell(Writer writer, int rowIndex, int columnIndex, Object value, int styleIndex) throws IOException {
        String cellReferenceString = new CellReference(rowIndex, columnIndex).formatAsString();
        String t = "";
        String valueNode = "";
        if (value instanceof Double) {
            t = "n";
            valueNode = "<v>" + value + "</v>";
        } else {
            t = "inlineStr";
            valueNode = "<is><t>" + value + "</t></is>";
        }
        writer.write("<c r=\"" + cellReferenceString + "\" t=\"" + t + "\"");
        if (styleIndex != -1) {
            writer.write(" s=\"" + styleIndex + "\"");
        }
        writer.write(">");
        writer.write(valueNode);
        writer.write("</c>");
    }

    public static void createCell(Writer writer, int rowIndex, int columnIndex, Object value) throws IOException {
        createCell(writer, rowIndex, columnIndex, value, -1);
    }

    public static void copyStream(InputStream is, OutputStream os) throws IOException {
        byte[] temp = new byte[1024];
        int count;
        while ((count = is.read(temp)) >= 0) {
            os.write(temp, 0, count);
        }
    }

}

3.1.2、實作一

package com.abc.demo.general.excel.event;

import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * Excel 2007 事件方式寫資料
 * 先生成sheet的xml檔案,然后根據excel的模板檔案覆寫其中的sheet資料檔案
 */
public class Excel2007Writer {
    private static final String TEMPLATE_FILE = "excel/2007_event_write_template.xlsx";

    /**要生成的excel檔案名稱*/
    private String fileName;

    private OutputStream os;

    /**臨時的xml檔案*/
    private File xmlFile;
    private Writer xmlWriter;
    private int row = 0;

    /**excel模板檔案*/
    private File templateFile;
    /**excel模板檔案是否為臨時檔案*/
    private boolean isTemplateFileTmp = false;
    /**作業表的xml檔案名稱 例如:/xl/worksheets/sheet1.xml*/
    private String sheetXmlName = "";

    public Excel2007Writer(String fileName) throws Exception {
        this.fileName = fileName;
        init();
    }

    public Excel2007Writer(OutputStream os) throws Exception {
        this.os = os;
        init();
    }

    private void init() throws Exception {
        xmlFile = File.createTempFile("sheet", ".xml");
        xmlWriter =  new OutputStreamWriter(new FileOutputStream(xmlFile, true),"UTF-8");
        Excel2007WriterUtil.beginSheet(xmlWriter);

        XSSFSheet sheet;
        URL url = Excel2007Writer.class.getClassLoader().getResource(TEMPLATE_FILE);
        if (url != null) {
            templateFile = new File(url.getFile());
            XSSFWorkbook wb = new XSSFWorkbook(templateFile);
            sheet = wb.getSheetAt(0);

            //如果模板檔案不存在,則新建臨時的模板檔案
        } else {
            XSSFWorkbook wb = new XSSFWorkbook();
            sheet = wb.createSheet();

            isTemplateFileTmp = true;
            templateFile = File.createTempFile("template", ".xlsx");
            FileOutputStream fos = new FileOutputStream(templateFile);
            wb.write(fos);
            fos.close();
            wb.close();
        }
        sheetXmlName = sheet.getPackagePart().getPartName().getName();
    }

    /**
     * 增加一行資料
     * @param values
     * @throws IOException
     */
    public void addLine(List<Object> values) throws IOException {
        Excel2007WriterUtil.beginRow(xmlWriter, row + 1);
        for (int i = 0; i < values.size(); i++) {
            Object value = values.get(i);
            Excel2007WriterUtil.createCell(xmlWriter, row, i, value);
        }
        Excel2007WriterUtil.endRow(xmlWriter);

        row++;
    }

    /**
     * 生成excel檔案
     * @throws Exception
     */
    public void generateExcel() throws Exception {
        Excel2007WriterUtil.endSheet(xmlWriter);
        xmlWriter.close();

        if (os == null) {
            os = new FileOutputStream(fileName);
        }
        ZipOutputStream zos = new ZipOutputStream(os);
        ZipFile templateZipFile = new ZipFile(templateFile);
        Enumeration<ZipEntry> zipEntrys = (Enumeration<ZipEntry>) templateZipFile.entries();
        //先把非sheet資料檔案寫進去
        while (zipEntrys.hasMoreElements()) {
            ZipEntry zipEntry = zipEntrys.nextElement();
            if (!zipEntry.getName().equals(sheetXmlName.substring(1))) {
                zos.putNextEntry(new ZipEntry(zipEntry.getName()));
                InputStream is = templateZipFile.getInputStream(zipEntry);
                Excel2007WriterUtil.copyStream(is, zos);
                is.close();
            }
        }

        //寫sheet資料檔案
        zos.putNextEntry(new ZipEntry(sheetXmlName.substring(1)));
        InputStream is = new FileInputStream(xmlFile);
        Excel2007WriterUtil.copyStream(is, zos);
        is.close();

        templateZipFile.close();
        zos.close();
        os.close();

        if (isTemplateFileTmp) {
            //洗掉臨時的模板檔案
            templateFile.delete();
        }
        //洗掉臨時的xml檔案
        xmlFile.delete();
    }


    public static void main(String[] args) throws Exception {
        Excel2007Writer excel2007Writer = new Excel2007Writer(new FileOutputStream("d:/a.xlsx"));
        for (int i = 0; i < 100; i++) {
            excel2007Writer.addLine(Arrays.asList("第" + i + "行", "a", "b", "c", "d"));
        }
        excel2007Writer.generateExcel();
    }
}

該方式通過新建Excel2007Writer,然后不停的增加行,最后生成 Excel 檔案,

3.1.3、實作二

package com.abc.demo.general.excel.event;

import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * Excel 2007 事件方式寫資料
 * 先生成sheet的xml檔案,然后根據excel的模板檔案覆寫其中的sheet資料檔案
 */
public class Excel2007WriterStatic {
    private static final String TEMPLATE_FILE = "excel/2007_event_write_template.xlsx";
    /**最大寫入資料行數,防止死回圈*/
    private static final int MAX_LINE = 10000000;

    private static Logger logger = LoggerFactory.getLogger(Excel2007Reader.class);

    /**
     * 生成excel檔案,所有的資料都寫到第一個sheet中
     * @param fileName 檔案全路徑名稱
     * @param data 資料提供者,不停的呼叫data.get方法來獲取一行資料,直到獲取的值為null
     *             一定要在某個條件下回傳null,否則會造成死回圈
     * @throws Exception
     */
    public static void generateExcel(String fileName, Supplier<List<Object>> data) throws Exception {
        OutputStream os = new FileOutputStream(fileName);
        generateExcel(os, data);
        os.close();
    }

    /**
     * 生成excel檔案,所有的資料都寫到第一個sheet中
     * @param os 輸出流
     * @param data 資料提供者,不停的呼叫data.get方法來獲取一行資料,直到獲取的值為null
     *             一定要在某個條件下回傳null,否則會造成死回圈
     * @throws Exception
     */
    public static void generateExcel(OutputStream os, Supplier<List<Object>> data) throws Exception {
        File templateFile;
        //模板是否為臨時檔案
        boolean isTemplateFileTmp = false;

        XSSFSheet sheet;
        URL url = Excel2007WriterStatic.class.getClassLoader().getResource(TEMPLATE_FILE);
        if (url != null) {
            templateFile = new File(url.getFile());
            XSSFWorkbook wb = new XSSFWorkbook(templateFile);
            sheet = wb.getSheetAt(0);

            //如果模板檔案不存在,則新建臨時的模板檔案
        } else {
            XSSFWorkbook wb = new XSSFWorkbook();
            sheet = wb.createSheet();

            isTemplateFileTmp = true;
            templateFile = File.createTempFile("template", ".xlsx");
            FileOutputStream fos = new FileOutputStream(templateFile);
            wb.write(fos);
            fos.close();
            wb.close();
        }
        //作業表的xml檔案名 例如:/xl/worksheets/sheet1.xml
        String sheetXmlName = sheet.getPackagePart().getPartName().getName();

        File xmlFile = File.createTempFile("sheet", ".xml");
        Writer writer =  new OutputStreamWriter(new FileOutputStream(xmlFile, true),"UTF-8");
        //寫入資料到臨時xml檔案
        Excel2007WriterUtil.beginSheet(writer);
        int row = 0;
        while (true) {
            List<Object> rowData =https://www.cnblogs.com/wuyongyin/p/ data.get();
            if (rowData =https://www.cnblogs.com/wuyongyin/p/= null) {
                break;
            }
            if (row >= MAX_LINE) {
                logger.warn("請確認Supplier的get方法是否在某個條件下回傳null");
                break;
            }
            Excel2007WriterUtil.beginRow(writer, row + 1);
            for (int i = 0; i < rowData.size(); i++) {
                Object o = rowData.get(i);
                Excel2007WriterUtil.createCell(writer, row, i, o);
            }
            Excel2007WriterUtil.endRow(writer);
            row++;
        }
        Excel2007WriterUtil.endSheet(writer);
        writer.close();

        ZipOutputStream zos = new ZipOutputStream(os);
        ZipFile templateZipFile = new ZipFile(templateFile);
        Enumeration<ZipEntry> zipEntrys = (Enumeration<ZipEntry>) templateZipFile.entries();
        //先把非sheet資料檔案寫進去
        while (zipEntrys.hasMoreElements()) {
            ZipEntry zipEntry = zipEntrys.nextElement();
            if (!zipEntry.getName().equals(sheetXmlName.substring(1))) {
                zos.putNextEntry(new ZipEntry(zipEntry.getName()));
                InputStream is = templateZipFile.getInputStream(zipEntry);
                Excel2007WriterUtil.copyStream(is, zos);
                is.close();
            }
        }

        //寫sheet資料檔案
        zos.putNextEntry(new ZipEntry(sheetXmlName.substring(1)));
        InputStream is = new FileInputStream(xmlFile);
        Excel2007WriterUtil.copyStream(is, zos);
        is.close();

        templateZipFile.close();
        zos.close();

        if (isTemplateFileTmp) {
            //洗掉臨時的模板檔案
            templateFile.delete();
        }
        //洗掉臨時的xml檔案
        xmlFile.delete();
    }


    public static void main(String[] args) throws Exception {
        generateExcel("d:/a2.xlsx", new Supplier<List<Object>>() {
            private int num = 0;
            @Override
            public List<Object> get() {
                if (num >= 100) {
                    return null;
                }
                num++;
                return Arrays.asList("第" + num + "行", "a", "b", "c");
            }
        });
    }
}

該方式通過靜態方法來呼叫,但需要實作 Supplier 介面來提供資料;通過不斷呼叫 Supplier 的 get 方法來獲取資料直到獲取的值為 null,所以 Supplier 一定要在某個條件下回傳null,否則會造成死回圈,

3.3、Excel 2007 事件模式讀

參考 POI 原始碼中給出的例子,然后自己改寫;原始碼位置如下:

  自己撰寫例子如下:

package com.abc.demo.general.excel.event;

import com.abc.demo.general.util.DateUtil;
import org.apache.commons.lang3.StringUtils;
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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * Excel 2007 事件方式讀取資料
 */
public class Excel2007Reader extends DefaultHandler {
    private static Logger logger = LoggerFactory.getLogger(Excel2007Reader.class);

    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    private int sheetIndex;
    private String sheetName;

    /**最小的列數,不足補空字串*/
    private int minColumns = -1;

    /**單元格型別*/
    private String cellType;

    /**單元格樣式*/
    private String cellStyle;

    /**當前單元格坐標*/
    private String currentXy;

    /**當前單元格行坐標*/
    private String currentY;

    /**前一單元格坐標*/
    private String preXy;

    /**節點值*/
    private String text;

    /**c節點下是否包含子節點*/
    private boolean cHasChild;

    /**行資料*/
    private List<String> rowData = https://www.cnblogs.com/wuyongyin/p/new ArrayList<>();

    private SharedStringsTable sharedStringsTable;
    private StylesTable stylesTable;
    private IRowDataHandler rowDataHandler;

    public Excel2007Reader() {

    }
    public Excel2007Reader(IRowDataHandler rowDataHandler) {
        this.rowDataHandler = rowDataHandler;
    }
    public Excel2007Reader(IRowDataHandler rowDataHandler, int minColumns) {
        this.rowDataHandler = rowDataHandler;
        this.minColumns = minColumns;
    }

    public void setRowDataHandler(IRowDataHandler rowDataHandler) {
        this.rowDataHandler = rowDataHandler;
    }

    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        preXy = "";
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        //單元格
        if ("c".equals(qName)) {
            cHasChild = false;

            this.cellType = attributes.getValue("t");
            this.cellStyle = attributes.getValue("s");

            currentXy = attributes.getValue("r");
            String currentX = currentXy.replaceAll("\\d", "").trim();
            currentY = currentXy.replaceAll("[A-Za-z]", "").trim();

            if (StringUtils.isBlank(preXy)) {
                for (int i = 0; i < colXToNum(currentX); i++) {
                    rowData.add("");
                }
            } else {
                String preX = preXy.replaceAll("\\d", "").trim();
                String preY = preXy.replaceAll("[A-Za-z]", "").trim();

                int differ = colXToNum(currentX) - colXToNum(preX);
                //當前列和前一列之前存在空列
                if (differ > 1) {
                    for (int i = 1; i < differ; i++) {
                        rowData.add("");
                    }
                }

                //換行且新行不從A列開始,補充前幾列的空值
                if (currentY.compareTo(preY) > 0 && !"A".equalsIgnoreCase(currentX)) {
                    for (int i = 0; i < colXToNum(currentX); i++) {
                        rowData.add("");
                    }
                }
            }

            preXy = currentXy;
        } else if ("v".equals(qName) || "t".equals(qName)) {
            cHasChild = true;
        }
        text = "";
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if ("v".equals(qName) || "t".equals(qName)) {
            rowData.add(getValue());
        } else if ("c".equals(qName)) {
            //c節點補包含子節點
            if (!cHasChild) {
                rowData.add("");
            }
        } else if (qName.equals("row")) {
            if (rowData.size() < minColumns) {
                int size = rowData.size();
                for (int i = 0; i < minColumns - size; i++) {
                    rowData.add("");
                }
            }
            rowDataHandler.handle(sheetIndex, sheetName, Integer.parseInt(currentY) - 1, rowData);
            rowData.clear();
        }
    }

    private String getValue() {
        if (StringUtils.isBlank(text)) {
            return text;
        }
        String result  = "";

        //布爾型別
        if ("b".equals(cellType)) {
            result = text.charAt(0) == '0' ? "false" : "true";

            //錯誤
        } else if ("e".equals(cellType)) {
            result = "error:" + text;

            //SSTINDEX
        } else if ("s".equals(cellType)) {
            int idx = Integer.parseInt(text);
            result = sharedStringsTable.getItemAt(idx).toString();

            //INLINESTR
        } else if ("inlineStr".equals(cellType)) {
            result = new XSSFRichTextString(text).toString();

            //FORMULA
        } else if ("str".equals(cellType)) {
            result = text;

            //NUMBER
        } else if ("n".equals(cellType) || StringUtils.isBlank(cellType)) {
            short dataFormat = -1;
            String dataFormatString = "";

            if (StringUtils.isNotBlank(cellStyle)) {
                int styleIndex = Integer.parseInt(cellStyle);
                XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
                dataFormat = style.getDataFormat();
                dataFormatString = style.getDataFormatString();
            }

            double value =https://www.cnblogs.com/wuyongyin/p/ Double.parseDouble(text);
            if (org.apache.poi.ss.usermodel.DateUtil.isADateFormat(dataFormat, dataFormatString)) {
                Date date = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(value);
                result = DateUtil.getDateString(date, DEFAULT_DATE_FORMAT);
            } else {
                long valueLong = (long)value;
                if (valueLong - value =https://www.cnblogs.com/wuyongyin/p/= 0) {
                    result = String.valueOf(valueLong);
                } else {
                    result = String.valueOf(value);
                }
            }
        } else {
            throw new RuntimeException("不支持的單元格型別,currentXy=" + currentXy + ",cellType=" + cellType);
        }
        return result;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        text += new String(ch, start, length);
    }

    /**
     * Excel列字母轉列索引(從0開始)
     * @param colX 列字母
     * @return
     */
    private int colXToNum(String colX) {
        if (StringUtils.isBlank(colX)) {
            throw new RuntimeException("列字母不能為空 : [" + colX + "]");
        }
        colX = colX.toUpperCase();
        int length = colX.length();

        int result = 0;
        for (int i = 0; i < length; i++) {
            char ch = colX.charAt(length - i - 1);
            int num = ch - 'A' + 1;
            num *= Math.pow(26, i);
            result += num;
        }
        return result - 1;
    }

    /**
     * 決議指定sheet資料
     * @param fileName
     * @param sheetIndexes
     * @throws Exception
     */
    public void process(String fileName, List<Integer> sheetIndexes) throws Exception {
        if (rowDataHandler == null) {
            throw new Exception("請設定行資料處理器");
        }
        OPCPackage opcPackage = OPCPackage.open(fileName, PackageAccess.READ);
        XSSFReader xssfReader = new XSSFReader(opcPackage);
        sharedStringsTable = xssfReader.getSharedStringsTable();
        stylesTable = xssfReader.getStylesTable();
        XMLReader xmlReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        xmlReader.setContentHandler(this);
        XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
        sheetIndex = 0;
        while (sheets.hasNext()) {
            if (sheetIndexes != null && !sheetIndexes.contains(sheetIndex)) {
                continue;
            }
            InputStream sheet = sheets.next();
            sheetName = sheets.getSheetName();
            InputSource sheetSource = new InputSource(sheet);
            xmlReader.parse(sheetSource);
            sheet.close();
            sheetIndex++;
        }
        opcPackage.close();
    }

    /**
     * 決議所有sheet資料
     * @param fileName
     * @throws Exception
     */
    public void process(String fileName) throws Exception {
        this.process(fileName, null);
    }

    /**
     * 決議第一個sheet的資料
     * @param fileName
     * @throws Exception
     */
    public void processFirstSheet(String fileName) throws Exception {
        this.process(fileName, Arrays.asList(0));
    }

    public static void main(String[] args) throws Exception {
        Excel2007Reader excel2007Handler = new Excel2007Reader(new SimpleRowDataHandler());
        excel2007Handler.process("d:/a.xlsx");
    }
}

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/280602.html

標籤:Java

上一篇:拷貝建構式第一個引數最好使用const

下一篇:jdbc使用PreparedStatement批量插入資料

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more