使用事件模式(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")); }
}