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

POI的事件模式占用内存更小,它利用基础的XML数据进行处理,适用于愿意学习.xlsx文件结构以及在java中处理XML的开发人员;也能有效预防出现java.lang.OutOfMemoryError: GC overhead limit exceeded问题。

1.了解下Excel文件的XML结构

1.1、了解文件结构之前先来看一下准备的文件,这个文件只有一个sheet页,结构也很简单。

1.2、Excel2007是用XMl格式储存,将要读取的文件后缀名改为.zip或者直接用解压缩工具打开,就可以看到这个Excel文件的结构

1.3、[Content_Types].xml文件描述了整个Excel文件的结构,也将根据这个文件组织后面的读取工作

1.4、xl文件夹包括了需要的数据和格式信息,是重点关注的对象
  • workbook.xml: 记录了工作表基本信息,是我们重点关注的文件之一。
  • styles.xml: 记录了每个单元格的样式。
  • worksheets: 里面包括了我们的每个sheet的信息,储存在xml文件中。
1.5、workbook.xml重点关注的就是sheets和sheet两个标签
  • sheet标签中name属性记录的就是sheet的名称
  • sheet标签中r:id属性记录了当前sheet和之前提到的记录sheet信息的xml之间的对应关系,储存在_rels文件夹下的xml文件中。
  • sheet标签还有一个属性state标识来是否隐藏。
    重点备注信息:r:id="rId3"是获取数据关键
1.6、一般一个Excel文件有几个sheet页,就会有几个XML文件与之对应。其中sheet页和xml文件就是根据【新建 Microsoft Excel 工作表xl_relsworkbook.xml.rels】文件对应起来的

重点备注信息:如下图所示,所有的信息都是在标签中,使用需要根据自己的当前sheel1中的格式获得数据

.读取.xlsx文件实例(java代码)

```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://www.cnblogs.com/xiaoBlog2016/p/12114832.html

时间: 2024-10-02 20:02:40

使用事件模式(Event API)读取Excel2007(.xlsx)文件的相关文章

java使用poi读取excel(.xlsx)文件

经过一番搜索发现,java操纵excel文件常用的有jxl和poi两种方式,孰好孰坏看自己需求而定. 其中最主要的区别在于jxl不支持.xlsx,而poi支持.xlsx 这里介绍的使用poi方式(XSSFWorkbook),实际上poi提供了HSSFWorkbook和XSSFWorkbook两个实现类.区别在于HSSFWorkbook是针对.xls文件,XSSFWorkbook是针对.xslx文件. 首先明确一下基本概念: 先创建一个工作簿,一个工作簿可以有多个工作表,一个工作表可以有多个行,一

ArcGIS / C#开发 无法读取Excel(*.xlsx)文件

ArcGIS打不开Excel(*.xlsx)文件 此问题也存在于软件开发过程中,无法读取*.xlsx时,都需要此组件. 1.ArcGIS报错: Failed to connect to ddatabase. An underlying database error occured. 没有注册类  2.原因: 缺少2007 Office System 驱动程序(AccessDatabaseEngine.exe) 3.解决方案: 需要安装  2007 Office System 驱动程序(Acces

JXLS使用方法(文件上传读取)xlsx文件读取

1.官方文档:http://jxls.sourceforge.net/reference/reader.html 2.demo git地址:https://bitbucket.org/leonate/jxls-demo 3.maven添加 <dependency>    <groupId>org.jxls</groupId>    <artifactId>jxls-reader</artifactId>    <version>2.0

nodejs读取xlsx文件

依赖包:multiparty,XLSX,代码如下: var multiparty = require('multiparty'); var XLSX = require("xlsx"); var form = new multiparty.Form(); function to_json(workbook,id){ // 获取 Excel 中所有表名,返回 ['sheet1', 'sheet2'] var sheetNames = workbook.SheetNames; sheetN

TI-RTOS 之 事件同步(Event, 类似semaphore)

TI-RTOS 之 事件同步(Event, 类似semaphore) Event 是类似Semaphore的存在,官方如下描述: SYS/BIOS events are a means of communication between Tasks and other threads such as Hwis, Swis, and other Tasks, or between Tasks and other SYS/BIOS objects. Other SYS/BIOS objects inc

C#事件(Event)学习日记

event 关键字的来由,为了简化自定义方法的构建来为委托调用列表增加和删除方法. 在编译器处理 event 关键字的时候,它会自动提供注册和注销方法以及任何必要的委托类型成员变量. 这些委托成员变量总是声明为私有的,因此不能直接从触发事件对象访问它们. 温馨提示:如果您对于委托不是很了解,您可以先看 C#委托(Delegate) ,这对您理解本章会有所帮助. 定义一个事件的步骤: 需要定义一个委托,它包含事件触发时将要调用方法 通过 event 关键字用相关委托声明这个事件 话不多说,我们来看

File API 读取文件小结

简单地说,File API只规定怎样从硬盘上提取文件,然后交给在网页中运行的JavaScript代码. 与以往文件上传不一样,File API不是为了向服务器提交文件设计的. 关于File API不能做什么也非常值得注意.它不能修改文件,也不能创建新文件. 想保存任何数据,你可以通过Ajax把数据发送到服务器,或者把它保存在本地存储空间中. 取得文件 使用input元素.将其type属性设置为file,这样是最常见的标准上传文本框 隐藏的input元素.为了保证风格一致,可以把input元素隐藏

Java 操作 Excel (读取Excel2007,Poi实现)

关于Java读取Excel2007的文章在Google.百度上搜索一下,没有太好的例子,实现的也不算太好.查看了一下Poi,最新的 POI 3.5 beta 4 支持读写 Excel2007和PPT2007(XLSX and PPTX),自己来实现Java读取Excel2007了. 1,下载 POI 3.5 beta 4 解压,把其中的jar包导入项目文件.以我的读取为例,导入了以下jar包.  没有配置 log4j,测试时报告警报信息,应该为加载顺序导致的初始化问题造成(暂时没有找原因). 2

[DOM Event Learning] Section 2 概念梳理 什么是事件 DOM Event

[DOM Event Learning] Section 2 概念梳理 什么是事件 DOM Event 事件 事件(Event)是用来通知代码,一些有趣的事情发生了. 每一个Event都会被一个Event对象所表示,这个对象可能还会有一些自定义的字段或者方法,来获取发生什么事情的更多信息. Event对象实现了Event接口(https://developer.mozilla.org/en-US/docs/Web/API/Event). 事件可以是任何事情,从最基本的用户交互,到renderin