POI事件模式读取Excel 2003文件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lipinganq/article/details/77678443

一.概述

1. Excel 2003文件(即后缀为xls)是二进制文件,存储结构为复合文档,POI读取xls文件有两种方式

  1. 用户模式(usermodel):一次性将xls文件读入到内存,创建dom结构处理
  2. 事件模式(eventusermodel):以流的形式读取xls文件,读取xls文件占用相对较小的内存

2. 事件模式适用于愿意学习一点低级API结构的中间开发人员。它使用起来相对简单,但需要对Excel文件j结构有个基本了解。


二. 存储格式

2.1 Workbook document

1.Excel 2003文件称之为一个Workbook文档(Workbook document),一个Workbook document包含一个全局设置(Workbook globals)和至少一个Sheet


2.2 文档流Workbook Stream

Excel 2003文档(Workbook document)是以复合文档的格式存储,复合文档的原理就像一个文件系统,Excel整个文件对应的流文件称为“ Workbook Stream”,Workbook stream又被分成了许多子流(Substream)
  1. Workbook Globals Substream - Workbook globals对应的流,包含workbook的全局信息
  2. Sheet Substream - Sheet对应的流,包含一个Sheet的信息

2.3 子流SubStreams

文件流是按顺序存储的:
  1. 最先存储的是Workbook Globals Substream
  2. 接着是第一个Sheet Substream
  3. Sheet Substream的存储顺序是根据Excel中Sheet的顺序来的


三. 目录结构Directory

从上面我们知道以复合文档为存储格式的Excel 2003文件是以各种单独的子流SubStreams,各种子流SubStreams安按照一定的顺序构成整个文档流Workbook Stream.
那我们解析Excel 2003文件时怎么进入各种子流SubStream呢?
这就要靠目录结构Directory了:
  1. 目录结构Directory是复合文档一种内部控制流
  2. 目录结构Directory由一系列的目录条目Directory Entry组成
  3. 每一个目录条目Directory Entry都指向复合文档的一个仓库Storage或流Stream
  4. 目录条目Directory Entry根据对应仓库或流在文件流中出现的顺序被列举
  5. 目录条目Directory Entry的索引从0开始,其索引称为DirID,见下图
  6. DirID为0的表示一个特殊的目录条目,它代表根仓库条目 - root storage entry

3.1 目录条目按序列举


3.2 目录条目结构

  1. 每个目录条目指向一个仓库Storage或流Stream
  2. 每个目录条目Directory Entry固定大小为128字节
  3. 第一个目录条目是根仓库条目 - root storage Entry
  4. 第二个目录条目的名字是“Workbook”,它表示一个流

目录Directory将每个仓库Storage的直接成员(仓库或流)放在一个独立的红黑树中
  1. 根仓库Root Storage描述的是根仓库条目root storage entry ,由于它没有父目录条目,所以无需构建红黑树
  2. 根仓库的所有直接成员(Storage1、Stream1、Stream2、Storage2、Stream3、Stream4)将组成一颗红黑树,该树的根节点的DID记录在root storage entry中
  3. Storage1只有一个直接成员Stream1,Stream1将构成一颗红黑树,此树只有一个节点,Stream1的DID入口记录在Storage1目录条目中
  4. Storage2有3个直接成员,Stream21、Stream22、Stream23这3个直接成员将组成一颗红黑树,此树根节点的DID记录在Storage2的目录条目中
通过目录条目,就可以访问到对应的仓库或流

四. 记录Record

Excel 2003文件中各种子流SubStreams会被解析为各种记录Record,每种Record包含文档中各种内容或特定的数据
  • BOFRecord : 记录了Workbook或一个sheet的开始
  • EOFRecord : 记录了Workbook或一个sheet的结尾
  • STRecord : 记录了Excel中所有文件大院个的文本值
  • .......
org.apache.poi.hssf.record中记录各种Record类,每个Record实现类都有唯一标识符sid
每个Record的存储结构如下:
  1. Identifier:Record的标识符sid,POI读取到sid就知道将流解析成对应的Record
  2. size :记录了当前Record内容占据的大小,单位为字节
  3. content:当前Record的内容
@SuppressWarnings("unchecked")  
private static final Class<? extends Record>[] recordClasses = new Class[] {  
        ArrayRecord.class,  
        AutoFilterInfoRecord.class,  
        BackupRecord.class,  
        BlankRecord.class,  
        BOFRecord.class,  
        BookBoolRecord.class,  
        BoolErrRecord.class,  
        BottomMarginRecord.class,  
        BoundSheetRecord.class,  
        CalcCountRecord.class,  
        CalcModeRecord.class,  
        CFHeaderRecord.class,  
        CFHeader12Record.class,  
        CFRuleRecord.class,  
        CFRule12Record.class,  
        ChartRecord.class,  
        ChartTitleFormatRecord.class,  
        CodepageRecord.class,  
        ColumnInfoRecord.class,  
        ContinueRecord.class,  
        CountryRecord.class,  
        CRNCountRecord.class,  
        CRNRecord.class,  
        DateWindow1904Record.class,  
        DBCellRecord.class,  
        DConRefRecord.class,  
        DefaultColWidthRecord.class,  
        DefaultRowHeightRecord.class,  
        DeltaRecord.class,  
        DimensionsRecord.class,  
        DrawingGroupRecord.class,  
        DrawingRecord.class,  
        DrawingSelectionRecord.class,  
        DSFRecord.class,  
        DVALRecord.class,  
        DVRecord.class,  
        EOFRecord.class,  
        ExtendedFormatRecord.class,  
        ExternalNameRecord.class,  
        ExternSheetRecord.class,  
        ExtSSTRecord.class,  
        FeatRecord.class,  
        FeatHdrRecord.class,  
        FilePassRecord.class,  
        FileSharingRecord.class,  
        FnGroupCountRecord.class,  
        FontRecord.class,  
        FooterRecord.class,  
        FormatRecord.class,  
        FormulaRecord.class,  
        GridsetRecord.class,  
        GutsRecord.class,  
        HCenterRecord.class,  
        HeaderRecord.class,  
        HeaderFooterRecord.class,  
        HideObjRecord.class,  
        HorizontalPageBreakRecord.class,  
        HyperlinkRecord.class,  
        IndexRecord.class,  
        InterfaceEndRecord.class,  
        InterfaceHdrRecord.class,  
        IterationRecord.class,  
        LabelRecord.class,  
        LabelSSTRecord.class,  
        LeftMarginRecord.class,  
        LegendRecord.class,  
        MergeCellsRecord.class,  
        MMSRecord.class,  
        MulBlankRecord.class,  
        MulRKRecord.class,  
        NameRecord.class,  
        NameCommentRecord.class,  
        NoteRecord.class,  
        NumberRecord.class,  
        ObjectProtectRecord.class,  
        ObjRecord.class,  
        PaletteRecord.class,  
        PaneRecord.class,  
        PasswordRecord.class,  
        PasswordRev4Record.class,  
        PrecisionRecord.class,  
        PrintGridlinesRecord.class,  
        PrintHeadersRecord.class,  
        PrintSetupRecord.class,  
        ProtectionRev4Record.class,  
        ProtectRecord.class,  
        RecalcIdRecord.class,  
        RefModeRecord.class,  
        RefreshAllRecord.class,  
        RightMarginRecord.class,  
        RKRecord.class,  
        RowRecord.class,  
        SaveRecalcRecord.class,  
        ScenarioProtectRecord.class,  
        SelectionRecord.class,  
        SeriesRecord.class,  
        SeriesTextRecord.class,  
        SharedFormulaRecord.class,  
        SSTRecord.class,  
        StringRecord.class,  
        StyleRecord.class,  
        SupBookRecord.class,  
        TabIdRecord.class,  
        TableRecord.class,  
        TableStylesRecord.class,  
        TextObjectRecord.class,  
        TopMarginRecord.class,  
        UncalcedRecord.class,  
        UseSelFSRecord.class,  
        UserSViewBegin.class,  
        UserSViewEnd.class,  
        ValueRangeRecord.class,  
        VCenterRecord.class,  
        VerticalPageBreakRecord.class,  
        WindowOneRecord.class,  
        WindowProtectRecord.class,  
        WindowTwoRecord.class,  
        WriteAccessRecord.class,  
        WriteProtectRecord.class,  
        WSBoolRecord.class,  
  
        // chart records  
        BeginRecord.class,  
        ChartFRTInfoRecord.class,  
        ChartStartBlockRecord.class,  
        ChartEndBlockRecord.class,  
        // TODO ChartFormatRecord.class,  
        ChartStartObjectRecord.class,  
        ChartEndObjectRecord.class,  
        CatLabRecord.class,  
        DataFormatRecord.class,  
        EndRecord.class,  
        LinkedDataRecord.class,  
        SeriesToChartGroupRecord.class,  
  
        // pivot table records  
        DataItemRecord.class,  
        ExtendedPivotTableViewFieldsRecord.class,  
        PageItemRecord.class,  
        StreamIDRecord.class,  
        ViewDefinitionRecord.class,  
        ViewFieldsRecord.class,  
        ViewSourceRecord.class,  
};  


4.1 Record解析顺序




五. Workbook解析步骤

5.1 BOFRecord / EOFRecord

1.位置:org.apache.poi.hssf.record.BOFRecord、org.apache.poi.hssf.record.EOFRecord

2.BOFRecord表示Workbook或一个sheet的开始,EOFRecord表示Workbook或一个sheet的结尾

3.如图:




5.2 FormatRecord

1. 位置: org.apache.poi.hssf.record.FormatRecord
2. FormatRecord表示一个单元格样式,每个单元格样式对应一个索引和单元格字符串
3. 如图:



5.3 ExtendedFormatRecord

1. 位置: org.apache.poi.hssf.record.ExtendedFormatRecord
2. ExtendedFormatRecord记录了一个单元格的属性
  1. 单元格样式索引:XFIndex
  2. 单元格边框样式:border
  3. 单元格水平垂直样式:alignment
  4. 单元格填充色:fill



5.4 BoundSheetRecord

1. 位置:org.apache.poi.hssf.record.BoundSheetRecord
2. BoundSheetRecord记录了一个Sheet的名称:
  1. Excel中有几个Sheet,就有几个BoundSheetRecord对象
  2. BoundSheetRecord对应Sheet在Excel中出现顺序
3. 如图:


5.5 SSTRecord

1.位置:org.apache.poi.hssf.record.SSTRecord

2.SSTRecord中存储了在Excel中文本单元格中的文本值,文本单元格通过索引获取文本值

3.如图



六. WorkSheet解析步骤

6.1 BOFRecord/EOFRecord

1.  org.apache.poi.hssf.record.BOFRecord
2. type=16表示开始解析WorkSheet
3. 如图:





6.2 DimensionsRecord

1.位置:org.apache.poi.hssf.record.DimensionsRecord

2.DimensionsRecord存储了一个sheet的行列范围

DimensionsRecord 描述说明
field_1_first_row sheet中第一有效行行号
field_2_last_row sheet中最后有效行行号+1
field_3_first_col sheet中第一有效列列号
field_4_last_col sheet中最后有效列列号+1

3.如图



6.3 ColumnInfoRecord

1.位置:org.apache.poi.hssf.record.ColumnInfoRecord

2.ColumnInfoRecord存储了sheet中一列的信息

3.如图:


6.4 RowRecord

1.  位置: org.apache.poi.hssf.record.RowRecord
2. RowRecord记录了当前行行信息:
  1. 当前行索引
  2. 当前行是否隐藏
3. 如图:


6.5 LabelSSTRecord

1. 位置:org.apache.poi.hssf.record.LabelSSTRecord

2. LabelSSTRecord记录了一个sheet中的文本单元格

3. 如图:

Record 描述说明
NumberRecord 数值单元格
LabelSSTRecord 引用了SSTRecord中一个String类型的单元格值
BoolErrRecord 布尔或错误单元格,根据属性isError判断是布尔还是错误单元格
FormulaRecord 公式单元格
BlankRecord 空白单元格,单元格没有值,但是有单元格样式
StringRecord 存储文本公式的缓存结果
LabelRecord 只读,支持读取直接存储在单元格中的字符串,而不是存储在SSTRecord中,除了读取不要使用LabelRecord,应该使用SSTRecord替代


6.6 NumberRecord

1.  位置:  org.apache.poi.hssf.record.NumberRecord
2. NumberRecord记录了一个Sheet中的数值单元格:数值或日期
3. 如图:





6.7 BoolErrRecord

1. 位置:   org.apache.poi.hssf.record.BoolErrRecord
2. BoolErrRecord记录了一个Sheet中布尔单元格或错误单元格
3. 如图:


6.8 FormulaRecord

1. 位置:  org.apache.poi.hssf.record.FormulaRecord
2. FormulaRecord记录了一个Sheet中的公式单元格
3. 如图:


6.9 BlankRecord

1. 位置:  org.apache.poi.hssf.record.BlankRecord
2. BlankRecord记录了一个Sheet中一个空单元格:即单元格中没有值,但是单元格有单元格样式
3. 如图:



6.10 MergeCellsRecord

1.  位置:  org.apache.poi.hssf.record.MergeCellsRecord
2. MergeCellsRecord记录了一个Sheet中一个合并单元格
3. 如图:


七. 解析步骤

使用POI事件模式解析Excel 2003文件,需要先将Excel 2003文件转化为POI中POIFSFileSystem对象
  1. 实现接口HSSFListener,实现自己的监听器listener
  2. 通过Record。sid为某些特定的Record设置监听listener
  3. 根据Excel 2003文件路径获取该文件的输入流FileInputStream - in
  4. 根据输入流in创建POIFSFileSysytem实例对象poifs
  5. 第二个目录条目名字是Workbook,找到第二目录条目,根据对应的流创建一个输入流DocumentInputStream
  6. 根据输入流DocumentInputStream,解析为一个个记录Record
  7. 如果解析出的Record设置了监听,触发监听事件
  8. 处理监听器中事件

7.1 设置监听的Record

Excel XLS文档最终被解析为一个个Record,如果某些Record设置了监听器,会触发监听器事件
解析Excel XLS数据通常需要设置下面这些Record的监听器:
BOFRecord.sid, 				// HSSFWorkbook、HSSFSheet的开始
EOFRecord.sid, 				// HSSFWorkbook、HSSFSheet的结束
BoundSheetRecord.sid, 		// BoundSheetRecord记录了sheetName
SSTRecord.sid, 				// SSTRecord记录了所有Sheet的文本单元格的文本
DimensionsRecord.sid, 		// DimensionsRecord记录了每个Sheet的有效起始结束行列索引
MergeCellsRecord.sid, 		// MergeCellsRecord记录了每个Sheet中的合并单元格信息
ExtendedFormatRecord.sid, 	// ExtendedFormatRecord记录了扩展的单元格样式
FormatRecord.sid, 			// FormatRecord记录单元格样式信息
ColumnInfoRecord.sid,		// ColumnInfoRecord记录了Sheet中列信息,如列是否隐藏
RowRecord.sid,				// RowRecord记录了Sheet中行信息,如行索引,行是否隐藏
BlankRecord.sid, 			// Sheet中空单元格,存在单元格样式
BoolErrRecord.sid, 			// Sheet中布尔或错误单元格
FormulaRecord.sid, 			// Sheet中公式单元格
LabelSSTRecord.sid, 		// Sheet中文本单元格
NumberRecord.sid			// Sheet中数值单元格:数字单元格和日期单元格

7.2 org.apache.poi.poifs.filesystem.POIFSFileSystem类

  1. 根据fs可以获取到根目录条目DirectoryNode - root entry
  2. 根目录root entry根据Entry实体名Workbook获取对应的Entry
  3. 根据Workbook Entry创建该实体的输入流DocumentInputStream - ds
  4. 解析输入流ds,根据Excel XLS文档格式解析为各种Record
  5. 解析到设置监听器的Record,触发监听器时间listener,处理Record
/ xls文件的输入流
FileInputStream fin = newFileInputStream("C:\\Users\\Administrator\\Desktop\\测试.xls");
// 创建一个POIFSFileSystem实例
POIFSFileSystem poifs = newPOIFSFileSystem(fin);
// 从流中获取Excel的WorkBook流
InputStream workBookInputStream = poifs.createDocumentInputStream("Workbook");


7.3 org.apache.poi.hssf.eventusermodel.HSSFListener

HSSFListener是与HSSFRequest和HSSFEventFactory一起使用的接口
  • 用户应该实现接口HSSFListener,创建一个自己的监听器类Workbook
  • listener可以注册到HSSFRequest实例request中,用于监听特定的Record
  • 一个Record可以设置多个监听器,处理不同的事


7.4 org.apache.poi.hssf.eventusermodel.HSSFRequest

HSSFRequest中有一个Map,用于存储所有特定Record的监听器,一个Record可以设置多个监听器
HSSFRequest 类方法描述
addListener(HSSFListener lsnr, short sid) 为sid的记录record注册一个监听器lsnr
addListenerForAllRecords(HSSFListener lsnr)

为org.apache.poi.hssf.record.Record包中所有的记录注册一个监听器lsnr

不推荐用这种方法,影响性能

processRecord(Record rec)

由HSSFEventFactory调用,处理记录rec

记录rec可能注册了多个监听器,循环触发每个注册的监听器,处理记录record


7.5 org.apache.poi.hssf.eventusermodel.HSSFEventFactory

根据POIFSFileSystem实例解析Excel XLS文件的类
HSSFEventFactory 类方法描述
processWorkbookEvents(HSSFRequest req, POIFSFileSystem fs)

将一个文件处理为基本的Record事件

@param req 一个HSSFRequest实例,记录了Record的所有监听器

@param fs 包含WorkBook的POIFS文件系统

processWorkbookEvents(HSSFRequest req, DirectoryNode dir)

将一个文件处理为基本的Record事件

@param req 一个HSSFRequest实例,记录了Record的所有监听器

@param dir 包含WorkBook的DirectoryNode

processEvents(HSSFRequest req, InputStream in)

将一个文件处理为基本的Record事件

@param req 一个HSSFRequest实例,记录了Record的所有监听器

@param in 包含WorkBook的DirectoryNode的输入流

short abortableProcessWorkbookEvents(HSSFRequest req, POIFSFileSystem fs)

将一个文件处理为基本的Record事件

返回数值,如果监听器是继承AbortableHSSFListener,返回值不为0,则不会触发当前记录的其他监听器,
就会继续处理下一个记录
short abortableProcessWorkbookEvents(HSSFRequest req, DirectoryNode dir)

将一个文件处理为基本的Record事件

返回数值,如果监听器是继承AbortableHSSFListener,返回值不为0,则不会触发当前记录的其他监听器,
就会继续处理下一个记录
short abortableProcessEvents(HSSFRequest req, InputStream in)

将一个文件处理为基本的Record事件

返回数值,如果监听器是继承AbortableHSSFListener,返回值不为0,则不会触发当前记录的其他监听器,
就会继续处理下一个记录

八.一个事件模式实例

8.1 HSSFListener接口的实现类

package poi.hssf.event;
import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.BoundSheetRecord;
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.RowRecord;
import org.apache.poi.hssf.record.SSTRecord;
public class HSSFListenerImpl implements HSSFListener {
    private SSTRecord sstrec;
    /**
     * This method listens for incoming records and handles them as required.
     * @param record    The record that was found while reading.
     */
    public void processRecord(Record record) {
        switch (record.getSid()) {
            // the BOFRecord can represent either the beginning of a sheet or the workbook
            case BOFRecord.sid:
                BOFRecord bof = (BOFRecord) record;
                if (bof.getType() == BOFRecord.TYPE_WORKBOOK) {
                    System.out.println("处理 workbook");
                    // assigned to the class level member
                } else if (bof.getType() == BOFRecord.TYPE_WORKSHEET) {
                    System.out.println("处理sheet");
                }
                break;
            case BoundSheetRecord.sid:
                BoundSheetRecord bsr = (BoundSheetRecord) record;
                System.out.println("New sheet named: " + bsr.getSheetname());
                break;
            case RowRecord.sid:
                RowRecord rowrec = (RowRecord) record;
                System.out.println("Row found, first column at "
                        + rowrec.getFirstCol() + " last column at " + rowrec.getLastCol());
                break;
            case NumberRecord.sid:
                NumberRecord numrec = (NumberRecord) record;
                System.out.println("Cell found with value " + numrec.getValue()
                        + " at row " + numrec.getRow() + " and column " + numrec.getColumn());
                break;
                // SSTRecords store a array of unique strings used in Excel.
            case SSTRecord.sid:
                sstrec = (SSTRecord) record;
                for (int k = 0; k < sstrec.getNumUniqueStrings(); k++) {
                    System.out.println("String table value " + k + " = " + sstrec.getString(k));
                }
                break;
            case LabelSSTRecord.sid:
                LabelSSTRecord lrec = (LabelSSTRecord) record;
                System.out.println("String cell found with value "
                        + sstrec.getString(lrec.getSSTIndex()));
                break;
        }
    }
}

8.2 Test

packagepoi.hssf.event;
importjava.io.FileInputStream;
importjava.io.IOException;
importjava.io.InputStream;
importorg.apache.poi.hssf.eventusermodel.HSSFEventFactory;
importorg.apache.poi.hssf.eventusermodel.HSSFRequest;
importorg.apache.poi.poifs.filesystem.POIFSFileSystem;
publicclass TestEventAPI {
    publicstatic void main(String[] args) throwsIOException {
        FileInputStream fin = newFileInputStream("C:\\Users\\Administrator\\Desktop\\测试.xls");
        try{
            POIFSFileSystem poifs = newPOIFSFileSystem(fin);
            try{
                // 从流中获取Excel的WorkBook流
                InputStream workBookInputStream = poifs.createDocumentInputStream("Workbook");
                try{
                    HSSFRequest hssfRequest = newHSSFRequest();
                    // 为所有的record注册一个监听器
                    hssfRequest.addListenerForAllRecords(newHSSFListenerImpl());
                    // 创建事件工厂
                    HSSFEventFactory factory = newHSSFEventFactory();
                    // 根据WorkBook输入流处理所有事件
                    factory.processEvents(hssfRequest, workBookInputStream);
                }finally{
                    workBookInputStream.close();
                }
            }finally{
                poifs.close();
            }
        }finally{
            // 一旦所有的监听器处理完成,关闭文件输入流
            fin.close();
        }
    }
}


猜你喜欢

转载自blog.csdn.net/lipinganq/article/details/77678443