博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java小知识----POI事件模式读取Excel 2007
阅读量:4347 次
发布时间:2019-06-07

本文共 8338 字,大约阅读时间需要 27 分钟。

一、知识背景

1.读取excel的方法选择问题

java中读excel中的时间,我们通常用POI去解析,在使用new HSSFWorkbook(NEW FileInputStream(excelFile))来读取Workbook,对Excel2003以前(包括2003)的版本没有问题,但读取Excel2007时发生如下异常:

org.apache.poi.poifs.filesystem.OfficeXmlFileException: The supplied DATA appears TO be IN the Office 2007+ XML. You are calling the part of POI that deals WITH OLE2 Office Documents. You need TO CALL a different part of POI TO PROCESS this DATA (eg XSSF instead of HSSF)
        该错误意思是说,文件中的数据是用Office2007+XML保存的,而现在却调用OLE2 Office文档处理,应该使用POI不同的部分来处理这些数据,比如使用XSSF来代替HSSF。
        于是按提示使用XSSF代替HSSF,用new XSSFWorkbook(excelFile)来读取Workbook,对Excel2007没有问题了,可是在读取Excel2003以前(包括2003)的版本时却发生了如下新异常:
org.apache.poi.openxml4j.exceptions.InvalidOperationException: Can't open the specified file: '*.xls'
        该错误是说,操作无效,不能打开指定的xls文件。
        下载POI的源码后进行单步调试,发现刚开始的时候还是对的,但到ZipFile类后就找不到文件了,到网上查了下,原来是XSSF不能读取Excel2003以前(包括2003)的版本,这样的话,就需要在读取前判断文件是2003前的版本还是2007的版本,然后对应调用HSSF或XSSF来读取。
        简而言之:由于HSSFWorkbook只能操作excel2003一下版本,XSSFWorkbook只能操作excel2007以上版本,所以利用Workbook接口创建对应的对象操作excel来处理兼容性

2.读取excel包含多sheet多数据的时候,出现内存溢出的问题。

POI提供UserModel和事件驱动两种方式读取excel。UserModel方式操作简洁,但是内存消耗大,稍微大一点的excel读取就会报内存溢出

二、解析步骤

当使用POI事件模式解析Excel XLSX文档时:

  • POI根据xlsx文档的路径path获取到文件File - file
  • 使用java.util.zip.ZipFile打开file文件 - zip
  • 从zip中获取到[Content_Types].xml
  • 解析[Content_Types].xml,记录解析出Excel各个xml名称:ArrayList
  • Excel解析成ZipPackage实例对象

三、代码样例

1、从DefaultHandler派生事件处理类ExcelAbstract

 

import java.io.InputStream;import java.sql.SQLException;import java.util.HashMap;import java.util.Map;import org.apache.poi.xssf.eventusermodel.XSSFReader;import org.apache.poi.xssf.model.SharedStringsTable;import org.apache.poi.xssf.usermodel.XSSFRichTextString;import org.apache.poi.openxml4j.opc.OPCPackage;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;/** * POI事件驱动读取Excel文件的抽象类。 *  * @author  Charles *  */public abstract class ExcelAbstract extends DefaultHandler {    private SharedStringsTable sst;    private String lastContents;    private boolean nextIsString;    private String curCellName= "";    private int curRow = 0;   private boolean numberFlag;    private boolean isTElement;    /**     * 读取当前行的数据。key是单元格名称如A1,value是单元格中的值。如果单元格式空,则没有数据。     */    private Map
rowValueMap = new HashMap<>(); /** * 处理单行数据的回调方法。 * * @param curRow 当前行号 * @param rowValueMap 当前行的值 * @throws SQLException */ public abstract void optRows(int curRow, Map
rowValueMap); /** * 读取Excel指定sheet页的数据。 * * @param filePath 文件路径 * @param sheetNum sheet页编号.从1开始。 * @throws Exception */ public void readOneSheet(String filePath, int sheetNum) throws Exception { OPCPackage pkg = OPCPackage.open(filePath); XSSFReader r = new XSSFReader(pkg); SharedStringsTable sst = r.getSharedStringsTable(); XMLReader parser = getSheetParser(sst); // 根据 rId# 或 rSheet# 查找sheet InputStream sheet2 = r.getSheet("rId" + sheetNum); InputSource sheetSource = new InputSource(sheet2); parser.parse(sheetSource); sheet2.close(); pkg.close(); } public void readAllSheet(String filePath) throws Exception { OPCPackage pkg = OPCPackage.open(filePath); XSSFReader r = new XSSFReader(pkg); SharedStringsTable sst = r.getSharedStringsTable(); XMLReader parser = getSheetParser(sst); SheetIterator sheets = (SheetIterator) r.getSheetsData(); while(sheets.hasNext){ InputStream sheet = sheets.next(); InputSource sheetSource = new InputSource(sheet2); parser.parse(sheetSource); sheet2.close(); } pkg.close(); } @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // c => 单元格 if (name.equals("c")) { // 如果下一个元素是 SST 的索引,则将nextIsString标记为true String cellType = attributes.getValue("t"); if (cellType != null && cellType.equals("s")) { nextIsString = true; } else { nextIsString = false; } String cellNumberType = attributes.getValue("s"); if (cellNumberType .equals("2")) { numberFlag= true; } else { numberFlag= false; } } if (name.equals("t")) { isTElement= true; } else { isTElement= false; } // 置空 lastContents = ""; /** * 记录当前读取单元格的名称 */ String cellName = attributes.getValue("r"); if (cellName != null && !cellName.isEmpty()) { curCellName = cellName; } } @Override public void endElement(String uri, String localName, String name) throws SAXException { // 根据SST的索引值的到单元格的真正要存储的字符串 // 这时characters()方法可能会被调用多次 if (nextIsString) { int idx = Integer.parseInt(lastContents); lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString(); } if(isTElement){ String value = lastContents.trim(); rowValueMap.put(curCellName,value); isTElement = false; } // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引 // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符 else if (name.equals("v")) { String value = lastContents.trim(); value = value.equals("") ? " " : value; if(numberFlag){ BigDecimal bd = new BigDecimal(value); value = bd.setScale(3,BigDecimal.ROUND_UP).toString(); } rowValueMap.put(curCellName, value); } else { // 如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法 if (name.equals("row")) { optRows(curRow, rowValueMap); rowValueMap.clear(); curRow++; } } } public void characters(char[] ch, int start, int length) throws SAXException { // 得到单元格内容的值 lastContents += new String(ch, start, length); } /** * 获取单个sheet页的xml解析器。 * @param sst * @return * @throws SAXException */ private XMLReader getSheetParser(SharedStringsTable sst) throws SAXException { XMLReader parser = XMLReaderFactory.createXMLReader("com.sun.org.apache.xerces.internal.parsers.SAXParser"); this.sst = sst; parser.setContentHandler(this); return parser; }}

 

 

2、从ExcelAbstract派生ExcelReaderUtil处理每一行数据

 

import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;import org.apache.poi.hssf.usermodel.HSSFDateUtil;/** * Excel读取公共类。 * @author Charles *  */public class ExcelReaderUtil extends ExcelAbstract {        /**     * 提取列名称的正则表达式     */    private static final String DISTILL_COLUMN_REG = "^([A-Z]{1,})";        /**     * 读取excel的每一行记录。map的key是列号(A、B、C...), value是单元格的值。如果单元格是空,则没有值。     */    private List
> dataList = new ArrayList<>(); @Override public void optRows(int curRow, Map
rowValueMap) { Map
dataMap = new HashMap<>(); rowValueMap.forEach((k,v)->dataMap.put(removeNum(k), v)); dataList.add(dataMap); } /** * 日期数字转换为字符串。 * * @param dateNum excel中存储日期的数字 * @return 格式化后的字符串形式 */ public static String dateNum2Str(String dateNum) { Date date = HSSFDateUtil.getJavaDate(Double.parseDouble(dateNum)); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); return formatter.format(date); } /** * 删除单元格名称中的数字,只保留列号。 * @param cellName 单元格名称。如:A1 * @return 列号。如:A */ private String removeNum(String cellName) { Pattern pattern = Pattern.compile(DISTILL_COLUMN_REG); Matcher m = pattern.matcher(cellName); if (m.find()) { return m.group(1); } return ""; } public List
> getDataList() { return dataList; }}

 

转载于:https://www.cnblogs.com/charles8866/p/10973064.html

你可能感兴趣的文章
C语言的第一次实验报告
查看>>
spring JDBC 批量插入数据
查看>>
状态压缩题目小结
查看>>
Android WebView 开发具体解释(三)
查看>>
2016-2017-2 20155325实验二《Java面向对象程序设计》实验报告
查看>>
POJ.3145.Common Substrings(后缀数组 倍增 单调栈)
查看>>
BZOJ.1935.[SHOI2007]Tree园丁的烦恼(CDQ分治 三维偏序)
查看>>
c++可变参数(示例)
查看>>
4923: [Lydsy1706月赛]K小值查询 平衡树 非旋转Treap
查看>>
MySQL 配置文件详解
查看>>
maven 中央仓库地址
查看>>
实现页面查看xml或json数据类似控制台效果
查看>>
Kali配置教程
查看>>
Leetcode: Combination Sum IV && Summary: The Key to Solve DP
查看>>
获取动态类型变量的属性值
查看>>
C++冒泡排序
查看>>
js 数组操作
查看>>
第522篇--DataTable to Excel C#
查看>>
C++\virtual 虚函数、纯虚函数
查看>>
asp.net mvc 4多级area实现技巧
查看>>