一、知识背景

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<String, String> rowValueMap = new HashMap<>(); /**
* 处理单行数据的回调方法。
*
* @param curRow 当前行号
* @param rowValueMap 当前行的值
* @throws SQLException
*/
public abstract void optRows(int curRow, Map<String, String> 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<Map<String, String>> dataList = new ArrayList<>(); @Override
public void optRows(int curRow, Map<String, String> rowValueMap) { Map<String, String> 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<Map<String, String>> getDataList() {
return dataList;
}
}

Java小知识----POI事件模式读取Excel 2007的更多相关文章

  1. java使用poi.3.10读取excel 2007以上版本(xlsx格式)

    1.在使用过程中,一直报错 throw new ClassNotFoundException(name);原因:没有导入xmlbeans-2.6.0.jar包,建议在使用poi时,将所有包都导入进工程 ...

  2. java+反射+多线程+生产者消费者模式+读取xml(SAX)入数据库mysql-【费元星Q9715234】

    java+反射+多线程+生产者消费者模式+读取xml(SAX)入数据库mysql-[费元星Q9715234] 说明如下,不懂的问题直接我[费元星Q9715234] 1.反射的意义在于不将xml tag ...

  3. java POI读取excel 2007/2003

    2003版office excel读取 import java.io.FileNotFoundException; import java.io.IOException; import java.io ...

  4. java 读取excel 2007 .xlsx文件 poi实现

    工作需要读取excel里面的行内容,使用java实现较为简单. 在最开始,尝试使用 jxl-2.6.12 来实现读取excel 的行内容.但是按照网上的方法,程序根本无法正确处理文件流.经过谷姐的一番 ...

  5. Java中使用poi导入、导出Excel

    一.介绍 当前B/S模式已成为应用开发的主流,而在企业办公系统中,常常有客户这样子要求:你要把我们的报表直接用Excel打开(电信系统.银行系统).或者是:我们已经习惯用Excel打印.这样在我们实际 ...

  6. POI原生导入读取EXCEL

    好久没用 最近项目有冲突 所以又用到了这个 谁知道以后还会不会用 先记下来吧 直接扔项目里 调方法就OK 了. 记录一下....不想再写类似这样的东西了 import org.apache.poi.h ...

  7. 结合Poi实现可读取Excel的文件选择对话框

    第一步:ApachePoi的jar包导全,不全会出现异常. 第二步:写就完事了:此例为读取特定模板的excel,仅供参考,根据实际需求改写. package 自建包; import java.awt. ...

  8. POI 4.0 读取Excel

    ... package POIXLS; import java.io.File; import java.io.FileInputStream; import java.util.ArrayList; ...

  9. 使用Apache下poi创建和读取excel文件

    一:使用apache下poi创建excel文档 @Test /* * 使用Apache poi创建excel文件 */ public void testCreateExcel() { // 1:创建一 ...

随机推荐

  1. 【Python之路】特别篇--Django生产环境部署

    Django的部署可以有很多方式,采用nginx+uwsgi的方式是其中比较常见的一种方式. uWSGI uWSGI是一个Web服务器,它实现了WSGI协议.uwsgi.http等协议.Nginx中H ...

  2. java常见问题 ——编辑报错1

    报错1 The method add(CatNode) in the type List<CatNode> is not applicable for the arguments (Str ...

  3. javaScript高级3笔记2

    DOM0级事件 <img src = "../..."  onclick = "function()" />  // 处理事件 elment.onc ...

  4. MessagePack Jackson 数据大小

    我们在使用 MessagePack 对 List 对象数据进行序列化的时候,发现序列化以后的二进制数组数据偏大的情况. 请注意,不是所有的 List 对象都会出现这种情况,这个根据你 List 对象中 ...

  5. 你知道 GNU Binutils 吗?【binutils】

    概述 从事 Linux 开发的朋友们都不可避免地用到一些工具,比如 objcopy.nm.objdump.readelf 等等.其实这一系列的工具,就是所谓的 Binutils,当然 GNU 就表示它 ...

  6. HDU 5119 Happy Matt Friends ——(背包DP)

    题意:有最多40个数字,取任意个数字他们的异或和>=k则是可行的方案,问有多少种可行的方案. 分析:dp[now][j]表示当前这个值的种类数,那么转移方程为dp[now][j] = dp[pr ...

  7. Java String字符串的不可变

    Java 通过把String类设计为final使类不可继承,将变量value设置为private并且是final的,且value没有setter方法,不可修改. 为什么这么设计: 1.字符串常量池的需 ...

  8. flask 第四篇 模板语言jinja2

    是时候开始写个前端了,Flask中默认的模板语言是Jinja2 现在我们来一步一步的学习一下 Jinja2 捎带手把 render_template 中留下的疑问解决一下 首先我们要在后端定义几个字符 ...

  9. selenium 入门(Java)

    官网:https://www.seleniumhq.org/ 下载地址:https://sites.google.com/a/chromium.org/chromedriver/downloads 华 ...

  10. java8 lamda 表达式