使用(POI)SAX处理Excel大文件,防止内存溢出
POISAXReader
h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}
/* LINKS
=============================================================================*/
a {
color: #4183C4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* CODE
=============================================================================*/
pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}
pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}
pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
pre code, pre tt {
background-color: transparent;
border: none;
}
kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}
/* QUOTES
=============================================================================*/
blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}
blockquote>:first-child {
margin-top: 0px;
}
blockquote>:last-child {
margin-bottom: 0px;
}
/* HORIZONTAL RULES
=============================================================================*/
hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}
/* IMAGES
=============================================================================*/
img {
max-width: 100%
}
-->
解决POI读取XLSX文件内存占用过过多
poi处理excel分别提供比较友好的用户模式以及比较底层的事件模式。其中,用户模式提供良好的封装,同时兼容2003以及2007以上的格式,使用相当方便。不过,代价是花费巨大的内存。只要超过6w条以后,基本是就是内存溢出了。
好在POI团队也提供了更底层的的流处理模式eventMode,对于大数据的Xlsx文件的写入,poi 3.8 提供SXSSF,采用缓存方式写如文件。对于文件的读取采用sax的方式直接读取每个sheet对应的xml文件。添加微信,回复poi,邀请你加群
POI SheetContentsHandler 接口
在POI中已经对SAX当时读取对应的Sheet的xml文件已经做了基本的封装,所以我们仅仅需要实现接口SheetContentsHandler,就可以完成SAX的方式读取。这个接口中需要是实现三个方法
- public void startRow(int rowNum) 读取某行开始
- public void endRow(int rowNum) 读取某行结束
- public void cell(String cellReference, String formattedValue,XSSFComment comment) 读取某行中的单元格
- public void headerFooter(String text, boolean isHeader, String tagName) 暂时不清楚
POI SheetContentsHandler实现
这里我主要参照poi XLSX2CSV.java
实现方式,需要提供对应的xlsx文件最大列数。其次,我在此基础上做了扩展,在 endRow
提供了一个事件,当前处理的的行数据,让这个解析功能更加独立。
实现思路,在startRow
方法中构造一个List对象,在cell
函数中添加每个单元内容,在endRow
函数中判断当前列是否等于最大列数,如果不等循环补齐,并出发添加行事件
SheetSaxHandler详细代码
protected class SheetSaxHandler implements SheetContentsHandler {
private int currentRow = -1;
private int currentCol = -1;
private int minColumns;
public void setMinColumns(int minColumns) {
this.minColumns = minColumns;
}
public SheetSaxHandler(int minColumns) {
super();
this.minColumns = minColumns;
}
public SheetSaxHandler() {
}
private List<SheetRowListener> listeners = new ArrayList<SheetRowListener>();
private List<String> lRows = new ArrayList<String>(); // 处理一行信息
public void rowAdded(SheetRowListener add) {
listeners.add(add);
}
private void postRowAdded(List<String> row, int rowNum)
throws SQLException {
for (SheetRowListener hl : listeners)
hl.addRow(row, rowNum);
}
@Override
public void startRow(int rowNum) {
currentRow = rowNum;
currentCol = -1;
lRows.clear();
}
@Override
public void endRow(int rowNum) {
// 添加数据
for (int i = currentCol; i < minColumns; i++) {
lRows.add("");
}
try {
postRowAdded(lRows, rowNum);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void cell(String cellReference, String formattedValue,
XSSFComment comment) {
if (cellReference == null) {
cellReference = new CellAddress(currentRow, currentCol)
.formatAsString();
}
int thisCol = (new CellReference(cellReference)).getCol();
int missedCols = thisCol - currentCol - 1;//处理数据中间存在空白
for (int i = 0; i < missedCols; i++) {
this.lRows.add("");
}
currentCol = thisCol;
// TODO 数据类型处理
try {
Double.parseDouble(formattedValue);
this.lRows.add(formattedValue);
} catch (NumberFormatException e) {
this.lRows.add(formattedValue);
}
}
@Override
public void headerFooter(String text, boolean isHeader, String tagName) {
System.out.println(text + "==" + isHeader + "==" + tagName);
}
}
事件接口
interface SheetRowListener {
void addRow(List<String> row, int rowNum);
}
调用方式
- 打开文件
- 找到对应sheet的xml文件
- 使用上边的方法依次处理每一个sheet
处理文件
@Override
public int saveToOracle(String filePath, String pcId)
throws FileNotFoundException, EncryptedDocumentException,
InvalidFormatException, IOException, ClassNotFoundException,
SQLException, OpenXML4JException, SAXException,
ParserConfigurationException {
File f = new File(filePath);
OPCPackage p = null;
int num = 0;
Connection conn = null;
if (f.exists()) {
try {
JSONArray sheetCfgs = this.cfgJson.getJSONArray("sheets");
dataBuferRows = this.cfgJson.getInteger("dataBuferRows");
dataBuferRows = dataBuferRows == null ? 1000 : dataBuferRows;
conn = ca.getConnection(ca.getSqlCfg(serverPath));
String importTime = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
p = OPCPackage.open(f, PackageAccess.READ);
ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(
p);
XSSFReader xssfReader = new XSSFReader(p);
StylesTable styles = xssfReader.getStylesTable();
XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader
.getSheetsData();
HashMap<Integer, JSONObject> hSheetCfg = new HashMap<Integer, JSONObject>();
for (int i = 0; i < sheetCfgs.size(); i++) {
JSONObject sheetCfg = sheetCfgs.getJSONObject(i);
hSheetCfg.put(sheetCfg.getInteger("sheetIndex"), sheetCfg);
}
int index = 1;
while (iter.hasNext()) {
InputStream sheetStream = iter.next();
if (hSheetCfg.containsKey(index)) {
processSheet(styles, strings, new SheetSaxHandler(),
sheetStream, hSheetCfg.get(index), conn, pcId,
this.fileName, importTime);
}
index++;
}
p.close();
f = null;
conn.close();
} catch (SQLException e) {
conn.close();
conn = null;
throw e;
}
}
return num;
}
处理Sheet
public void processSheet(StylesTable styles,
ReadOnlySharedStringsTable strings, SheetSaxHandler sheetHandler,
InputStream sheetInputStream, final JSONObject sheetCfg,
final Connection conn, String PcID, String fileName,
String importTime) throws IOException,
ParserConfigurationException, SAXException, SQLException {
final PreparedStatement ps = conn.prepareStatement(ca.buildInsertSql(
sheetCfg, PcID, fileName, importTime));
final int dataStartNum = sheetCfg.getIntValue("dataStartNum");
sheetHandler.setMinColumns(sheetCfg.getJSONArray("fieldReference")
.size());
sheetHandler.rowAdded(new SheetRowListener() {
@Override
public void addRow(List<String> row, int rowNum) {
if (rowNum < dataStartNum - 1)
return;
try {
ca.setParamter(ps, sheetCfg, row, rowNum - dataStartNum);
if (rowNum % dataBuferRows == 0) {
ps.executeBatch();
ps.clearBatch();
}
} catch (SQLException e) {
try {
ps.close();
conn.close();
throw e;
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
});
XMLReader sheetParser = SAXHelper.newXMLReader();
DataFormatter formatter = new DataFormatter();
InputSource sheetSource = new InputSource(sheetInputStream);
ContentHandler handler = new XSSFSheetXMLHandler(styles, null, strings,
sheetHandler, formatter, false);
sheetParser.setContentHandler(handler);
sheetParser.parse(sheetSource);
// 处理剩下的数据
ps.executeBatch();
ps.clearBatch();
// 关闭当前ps
ps.close();
}
`
总结
在最初使用poi的用户模式,很快的就完成一个excel文件的解析,很方便。随着项目的逐渐深入,处理的excel文件越来越大,用户模式已经不能胜任。于是开始查找资料,在官网上看到了转csv的实例。
这段代码的主要功能将excel文件中的数据导入到oracle数据库对应的表中,在实现功能方面,我主要遇到了以下问题
- 解决excel文件解析内存泄露(2007以后文件采用sax方式基本解决)
- 对应大量数据的保存,速度一直很慢,尽管我这里采用了批量提交的方式(目前这问题我依然没找到很好的方案,如果有同行看到的,还希望多多指教)
使用(POI)SAX处理Excel大文件,防止内存溢出的更多相关文章
- 图片_ _Android有效解决加载大图片时内存溢出的问题 2
Android有效解决加载大图片时内存溢出的问题 博客分类: Android Android游戏虚拟机算法JNI 尽量不要使用setImageBitmap或 setImageResource或 Bit ...
- Android开发中如何解决加载大图片时内存溢出的问题
Android开发中如何解决加载大图片时内存溢出的问题 在Android开发过程中,我们经常会遇到加载的图片过大导致内存溢出的问题,其实类似这样的问题已经屡见不鲜了,下面将一些好的解决方案分享给 ...
- 使用(POI)SAX处理Excel文件,防止内存溢出
POISAXReader h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-chi ...
- POI Sax 事件驱动解析Excel2007文件
Excel2007版本的代码如下,本文主要是用于POI解析大文件Excel容易出现内存溢出的现象而提出解决方案,故此解决了大数据量的Excel文件解析的难度,在此拿出来贡献给大家,谢谢! 里面用到的相 ...
- POI Sax 事件驱动解析Excel2003文件
POI事件驱动解析Excel文件 package com.boguan.bte.util.excel; import java.io.FileInputStream; import java.io.I ...
- c++ windows下读取大文件(内存映射)
关于内存映射的基本知识以及一些函数的原型说明,参考博客:http://blog.csdn.net/wcyoot/article/details/7363393 下面是我对于读取一个104M文件大小,使 ...
- php导出为excel文件避免内存溢出
轻松解决PHPExcel导出10W行超时和内存溢出问题 使用了一个轻量级的PHP的Excel操作库-PHP_XLSXWriter 10w行excel数据导出仅需要5.26秒,再也不用担心excel ...
- java 大数据处理之内存溢出解决办法(一)
http://my.oschina.net/songhongxu/blog/209951 一.内存溢出类型 1.java.lang.OutOfMemoryError: PermGen space JV ...
- Android学习笔记_51_转android 加载大图片防止内存溢出
首先来还原一下堆内存溢出的错误.首先在SD卡上放一张照片,分辨率为(3776 X 2520),大小为3.88MB,是我自己用相机拍的一张照片.应用的布局很简单,一个Button一个ImageView, ...
随机推荐
- golang 使用pprof进行性能调优
package main import "fmt" func lengthOfNonRepeatingSubStr(s string) int { lastOccurred := ...
- SQL @@Rowcount
@@Rowcount主要是返回上次sql语句所影响的数据行数 SELECT * FROM dbo.Region AS R SELECT @@rowcount SELECT @@rowcount --我 ...
- Java实现基于token认证
随着互联网的不断发展,技术的迭代也非常之快.我们的用户认证也从刚开始的用户名密码转变到基于cookie的session认证,然而到了今天,这种认证已经不能满足与我们的业务需求了(分布式,微服务).我们 ...
- pip命令
安装包 pip install django #最新版本 pip install django==1.11.7 #指定版本 pip install django>=1.11.7 #最小版本 ...
- python 将png图片格式转换生成gif动画
先看知乎上面的一个连接 用Python写过哪些[脑洞大开]的小工具? https://www.zhihu.com/question/33646570/answer/157806339 这个哥们通过爬气 ...
- jedis keys和scan操作
关于redis的keys命令的性能问题 KEYS pattern 查找所有符合给定模式 pattern 的 key . KEYS * 匹配数据库中所有 key . KEYS h?llo 匹配 hell ...
- redux+saga+reducer
saga.js这个文件里面的函数实际没有在其他jsx中引用吧?这个文件的作用就是把异步数据拿到,放进reducer,如果jsx想取,需要结合connect来取数据.
- Java EE ----- Container/Injection
容器(container)是一个类,实际上是component的子类,因此容器本身也是一个组件,具有组件的所有性质,但是它的主要功能是容纳其他组件和容器. 对于开发人员,需要引入复杂的代码解决事务以及 ...
- Do-Now—团队 冲刺博客六
Do-Now-团队 冲刺博客六 作者:仇夏 前言 终于从四级的考试中解脱了(不过我觉得可能凉凉,呵呵),我们的APP制作也迎来了最后的两天. 自己觉得自己其实没有干成什么事情,代码什么的大都是队友们写 ...
- TortoiseGit的ssh key和Git的ssh key
情景模拟: 你使用Git+TortoiseGit对项目进行版本控制,本地库(自己电脑建立的.git)与远程库(如GitLab上建立)通信需要使用ssh验证,你用git生成公钥并保存到了Gitlab上, ...