Java小知识----POI事件模式读取Excel 2007

时间:2023-01-31 23:30:25

一、知识背景

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读取就会报内存溢出

二、解析步骤

Java小知识----POI事件模式读取Excel 2007

当使用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&period;3&period;10读取excel 2007以上版本(xlsx格式)

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

  2. java&plus;反射&plus;多线程&plus;生产者消费者模式&plus;读取xml&lpar;SAX&rpar;入数据库mysql-【费元星Q9715234】

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

  3. java POI读取excel 2007&sol;2003

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

  4. java 读取excel 2007 &period;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&period;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. 我的JavaScript笔记

    JavaScript 一种基于对象(object)和事件驱动(Event Driven)的嵌入式脚本语言. 简单的例子 <html> <head> <title>D ...

  2. sql 分组查询及格不及格人数

    select score as 类别,count(*) as 人数 from (select case when fen>=60 then '及格' else '不及格' end as scor ...

  3. pro8

    1.本次课学到的知识点 函数程序设计 结构化程序设计思想 程序解析 局部变量和全局变量 2.实验过程中遇到的问题及解决方法 实验过程中会遇到自定义函数的逻辑错误 与缺少定义变量 从主函数开始理清函数关 ...

  4. Sqlserver作业-手把手带你体验

    所谓Sql Server作业就是按照规定的时间执行指定的脚本,如果在SQL Server 里需要定时或者每隔一段时间执行某个存储过程或3200字符以内的SQL语句时,可以用管理-SQL Server代 ...

  5. jqueryMobile中select样式自定义

    要去掉引入的jqueryMobile给下拉框组件的样式,有两种办法. 第一种:全局的去掉所有的下拉框样式: <link rel="stylesheet" href=&quot ...

  6. 背景透明IE和rgba

    opacity:0.5; filter:Alpha(opacity=40); //IE8以下 当我们设置opacity透明时,opacity后代元素会随着一起具有透明性,所以我们Opacity中的文字 ...

  7. python之socket模块

    UDP client #!/usr/bin/env python2.7 #-*-coding:utf-8 -*- import socket s=socket.socket(socket.AF_INE ...

  8. 【MySQL】查看支持的字符集show character set&semi;

  9. 【转】GAMITBLOBK中固定解、浮点解、约束解、松弛解等解类型解释

    在GAMIT/GLOBK的使用过程中,经常会碰到固定解.浮点解.约束解.松弛解及其相关组合解(如约束固定解)等词汇,对于初学者,一时难以弄明白其中的含义,一般只有按部就班按照教程中,怎么说就怎么弄,不 ...

  10. Linux中的Buffer 与 Cache

    A buffer is something that has yet to be "written" to disk.       A cache is something tha ...