使用事件模式(Event API)讀取Excel2007(.xlsx)文件


POI的事件模式占用內存更小,它利用基礎的XML數據進行處理,適用於願意學習.xlsx文件結構以及在java中處理XML的開發人員;也能有效預防出現java.lang.OutOfMemoryError: GC overhead limit exceeded問題。

1.了解下Excel文件的XML結構

1.1、了解文件結構之前先來看一下准備的文件,這個文件只有一個sheet頁,結構也很簡單。

markdown

1.2、Excel2007是用XMl格式儲存,將要讀取的文件后綴名改為.zip或者直接用解壓縮工具打開,就可以看到這個Excel文件的結構

markdown

1.3、[Content_Types].xml文件描述了整個Excel文件的結構,也將根據這個文件組織后面的讀取工作

markdown

1.4、xl文件夾包括了需要的數據和格式信息,是重點關注的對象
  • workbook.xml: 記錄了工作表基本信息,是我們重點關注的文件之一。
  • styles.xml: 記錄了每個單元格的樣式。
  • worksheets: 里面包括了我們的每個sheet的信息,儲存在xml文件中。
    markdown
1.5、workbook.xml重點關注的就是sheets和sheet兩個標簽
  • sheet標簽中name屬性記錄的就是sheet的名稱
  • sheet標簽中r:id屬性記錄了當前sheet和之前提到的記錄sheet信息的xml之間的對應關系,儲存在_rels文件夾下的xml文件中。
  • sheet標簽還有一個屬性state標識來是否隱藏。
    重點備注信息:r:id="rId3"是獲取數據關鍵
    markdown
1.6、一般一個Excel文件有幾個sheet頁,就會有幾個XML文件與之對應。其中sheet頁和xml文件就是根據【新建 Microsoft Excel 工作表xl_relsworkbook.xml.rels】文件對應起來的

重點備注信息:如下圖所示,所有的信息都是在 標簽中,使用需要根據自己的當前sheel1中的格式獲得數據
markdown

.讀取.xlsx文件實例(java代碼)

import com.inspur.evaluation.message.consume.receive.utils.StringHelper;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
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;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class POIEventModelUtil {

    public static void main(String[] args) throws Exception {
        OPCPackage pkg = OPCPackage.open("E:/ceshi/廣州市評價詳情明細數據20191105.xlsx", PackageAccess.READ);
        XSSFReader r = new XSSFReader(pkg);
		//根據workbook.xml中r:id的值獲得流
        InputStream is = r.getSheet("rId3");
        //debug 查看轉換的xml原始文件,方便理解后面解析時的處理,
        byte[] isBytes = IOUtils.toByteArray(is);
        //讀取流,查看文件內容
        streamOut(new ByteArrayInputStream(isBytes));

        //下面是SST 的索引會用到的
        SharedStringsTable sst = r.getSharedStringsTable();
        System.out.println("excel的共享字符表sst------------------start");
        sst.writeTo(System.out);
        System.out.println();
        System.out.println("excel的共享字符表sst------------------end");

        XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        List<List<String>> container = new ArrayList<>();
        parser.setContentHandler(new Myhandler(sst, container));

        InputSource inputSource = new InputSource(new ByteArrayInputStream(isBytes));
        parser.parse(inputSource);

        is.close();

        printContainer(container);
    }

    /**
     * 輸出獲得excel內容
     * @param container
     */
    public static void printContainer(List<List<String>> container) {
        System.out.println("excel內容------------- -start");
        for (List<String> stringList : container) {
            for (String str : stringList) {
                System.out.printf("%3s", str + " | ");
            }
            System.out.println();
        }
        System.out.println("excel內容---------------end");
    }

    /**
     * 讀取流,查看文件內容
     * @param in
     * @throws Exception
     */
    public static void streamOut(InputStream in) throws Exception {
        System.out.println("excel轉為xml------------start");
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) != -1) {
            System.out.write(buf, 0, len);
        }
        System.out.println();
        System.out.println("excel轉為xml------------end");
    }
}

class Myhandler extends DefaultHandler {

    //取SST 的索引對應的值
    private SharedStringsTable sst;

    public void setSst(SharedStringsTable sst) {
        this.sst = sst;
    }

    //解析結果保存
    private List<List<String>> container;

    public Myhandler(SharedStringsTable sst, List<List<String>> container) {
        this.sst = sst;
        this.container = container;
    }

    /**
     * 存儲cell標簽下v標簽包裹的字符文本內容
     * 在v標簽開始后,解析器自動調用characters()保存到 lastContents
     * 【但】當cell標簽的屬性 s是 t時, 表示取到的lastContents是 SharedStringsTable 的index值
     * 需要在v標簽結束時根據 index(lastContents)獲取一次真正的值
     */
    private String lastContents;

    //有效數據矩形區域,A1:Y2
    private String dimension;

    //根據dimension得出每行的數據長度
    private int longest;

    //上個有內容的單元格id,判斷空單元格
    private String lastCellid;

    //上一行id, 判斷空行
    private String lastRowid;

    // 判斷單元格cell的c標簽下是否有v,否則可能數據錯位
    private boolean hasV = false;


    //行數據保存
    private List<String> currentRow;

    //單元格內容是SST 的索引
    private boolean isSSTIndex = false;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
      //System.out.println("startElement:"+qName);
        lastContents = "";
        if (qName.equals("dimension")) {
            dimension = attributes.getValue("ref");
            longest = covertRowIdtoInt(dimension.substring(dimension.indexOf(":") + 1));
        }
        //行開始
        if (qName.equals("row")) {
            String rowNum = attributes.getValue("r");
            //判斷空行
            if (lastRowid != null) {
                //與上一行相差2, 說明中間有空行
                int gap = Integer.parseInt(rowNum) - Integer.parseInt(lastRowid);
                if (gap > 1) {
                    gap -= 1;
                    while (gap > 0) {
                        container.add(new ArrayList<>());
                        gap--;
                    }
                }
            }

            lastRowid = attributes.getValue("r");
            currentRow = new ArrayList<>();
        }
        if (qName.equals("c")) {
            String rowId = attributes.getValue("r");
            //空單元判斷,添加空字符到list
            if (lastCellid != null) {
                int gap = covertRowIdtoInt(rowId) - covertRowIdtoInt(lastCellid);
                for (int i = 0; i < gap - 1; i++) {
                    currentRow.add("");
                }
            } else {
                //第一個單元格可能不是在第一列
                if (!"A1".equals(rowId)) {
                    for (int i = 0; i < covertRowIdtoInt(rowId) - 1; i++) {
                        currentRow.add("");
                    }
                }
            }
            lastCellid = rowId;

            //判斷單元格的值是SST的索引,不能直接characters方法取值
            if (attributes.getValue("t") != null && attributes.getValue("t").equals("s")) {
                isSSTIndex = true;
            } else {
                isSSTIndex = false;
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {

        //行結束,存儲一行數據
        if (qName.equals("row")) {
            //判斷最后一個單元格是否在最后,補齊列數
            //【注意】有的單元格只修改單元格格式,而沒有內容,會出現c標簽下沒有v標簽,導致currentRow少
            if (covertRowIdtoInt(lastCellid) < longest) {
                int min = Math.min(currentRow.size(), covertRowIdtoInt(lastCellid));
                for (int i = 0; i < longest - min; i++) {
                    currentRow.add("");
                }
            }
            container.add(currentRow);
            lastCellid = null;
        }

        //單元格結束,沒有v時需要補位
        if (qName.equals("c")) {
            if (!hasV) currentRow.add("");
            hasV = false;
        }

        //單元格內容標簽結束,characters方法會被調用處理內容
        //2019-12-29 13:09:14  因為當前讀取的sheel1.xml中內容存儲大多都在<t></t>標簽中,因此在此新增此單元格
        if (qName.equals("v") || qName.equals("t")) {
            hasV = true;
            //單元格的值是SST 的索引
            if (isSSTIndex) {
                String sstIndex = lastContents.toString();
                try {
                    int idx = Integer.parseInt(sstIndex);
                    XSSFRichTextString rtss = new XSSFRichTextString(
                            sst.getEntryAt(idx));
                    lastContents = rtss.toString();
                    if (StringHelper.isNotEmpty(lastContents)) {
                        currentRow.add(lastContents);
                    } else {
                        currentRow.add("");
                    }
                } catch (NumberFormatException ex) {
                    System.out.println(lastContents);
                }
            } else {
                currentRow.add(lastContents);
            }
        }
    }


    /**
     * 獲取element的文本數據
     *
     * @see org.xml.sax.ContentHandler#characters
     */
    public void characters(char[] ch, int start, int length) throws SAXException {
        lastContents += new String(ch, start, length);
    }

    /**
     *
     * @param cellId 單元格定位id,行列號
     * @return
     */
    public static int covertRowIdtoInt(String cellId) {
        StringBuilder sb = new StringBuilder();
        String column = "";
        //從cellId中提取列號
        for (char c : cellId.toCharArray()) {
            if (Character.isAlphabetic(c)) {
                sb.append(c);
            } else {
                column = sb.toString();
            }
        }
        //列號字符轉數字
        int result = 0;
        for (char c : column.toCharArray()) {
            result = result * 26 + (c - 'A') + 1;
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(Myhandler.covertRowIdtoInt("AB7"));
    }
}
###本文出自以下文章,
[POI事件模式指北(二)-Excel2007](https://yq.aliyun.com/articles/690513 "POI事件模式指北(二)-Excel2007")


免責聲明!

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



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