POI实现大数据EXCLE导入导出,解决内存溢出问题

  使用POI能够导出大数据保证内存不溢出的一个重要原因是SXSSFWorkbook生成的EXCEL为2007版本,修改EXCEL2007文件后缀为ZIP打开可以看到,每一个Sheet都是一个xml文件,单元格格式和单元格坐标均用标签表示。直接使用SXSSFWorkbook来到导出EXCEL本身就是POI为了大数据量导出而量身定制的,所以导出可以直接使用SXSSFWorkbook方式。

  为了保险起见可以采用多Sheet的方式保证内存不溢出。需要注意的是Sheet名称不能重复;下载的时候需要定义好返回头。

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

  导出EXCEL较为简单,创建Workbook对象和Sheet对象往里塞值就行了。但是导入读取EXCEL的时候SXSSFWorkbook没有读取文件流的方法,只能使用XSSFWorkbook来读取,几千条数据可能就内存溢出了。

  这时候就要使用OPCPackage

public static OPCPackage open(java.io.InputStream in)
                       throws InvalidFormatException,
                              java.io.IOException

Open a package. Note - uses quite a bit more memory than open(String), which doesn‘t need to hold the whole zip file in memory, and can take advantage of native methods

Parameters:
    in - The InputStream to read the package from
Returns:
    A PackageBase object
Throws:
    InvalidFormatException
    java.io.IOException

  POI给出的API表示使用OPCPackage不需要将文件完全读取到内存中。

  调用方法

File file = uploadFile.getFile();
InputStream is = new FileInputStream(file);
excelReader.readInputStream(is);
excelReader.process();

  ExcelReader.java

/**
 * 抽象Excel2007读取器,excel2007的底层数据结构是xml文件,采用SAX的事件驱动的方法解析
 * xml,需要继承DefaultHandler,在遇到文件内容时,事件会触发,这种做法可以大大降低
 * 内存的耗费,特别使用于大数据量的文件。
 *
 */
public class Excel2007Reader extends DefaultHandler {
    //共享字符串表
    private SharedStringsTable sst;
    //上一次的内容
    private String lastContents;
    private boolean nextIsString;

    private int sheetIndex = -1;
    private List<String> rowlist = new ArrayList<String>();
    //当前行
    private int curRow = 0;
    //当前列
    private int curCol = 0;
    //日期标志
    private boolean dateFlag;
    //数字标志
    private boolean numberFlag;

    private boolean isTElement;

    private IRowReader rowReader;

    public void setRowReader(IRowReader rowReader){
        this.rowReader = rowReader;
    }

    /**只遍历一个电子表格,其中sheetId为要遍历的sheet索引,从1开始,1-3
     * @param filename
     * @param sheetId
     * @throws Exception
     */
    public void processOneSheet(String filename,int sheetId) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader r = new XSSFReader(pkg);
        SharedStringsTable sst = r.getSharedStringsTable();
        XMLReader parser = fetchSheetParser(sst);

        // 根据 rId# 或 rSheet# 查找sheet
        InputStream sheet2 = r.getSheet("rId"+sheetId);
        sheetIndex++;
        InputSource sheetSource = new InputSource(sheet2);
        parser.parse(sheetSource);
        sheet2.close();
    }

    /**
     * 遍历工作簿中所有的电子表格
     * @param filename
     * @throws Exception
     */
    public void process(String filename) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader r = new XSSFReader(pkg);
        SharedStringsTable sst = r.getSharedStringsTable();
        XMLReader parser = fetchSheetParser(sst);
        Iterator<InputStream> sheets = r.getSheetsData();
        while (sheets.hasNext()) {
            curRow = 0;
            sheetIndex++;
            InputStream sheet = sheets.next();
            InputSource sheetSource = new InputSource(sheet);
            parser.parse(sheetSource);
            sheet.close();
        }
    }

    public XMLReader fetchSheetParser(SharedStringsTable sst)
            throws SAXException {
        XMLReader parser = XMLReaderFactory
                .createXMLReader("org.apache.xerces.parsers.SAXParser");
        this.sst = sst;
        parser.setContentHandler(this);
        return parser;
    }

    public void startElement(String uri, String localName, String name,
            Attributes attributes) throws SAXException {

        // c => 单元格
        if ("c".equals(name)) {
            // 如果下一个元素是 SST 的索引,则将nextIsString标记为true
            String cellType = attributes.getValue("t");
            if ("s".equals(cellType)) {
                nextIsString = true;
            } else {
                nextIsString = false;
            }
            //日期格式
            String cellDateType = attributes.getValue("s");
            if ("1".equals(cellDateType)){
                dateFlag = true;
            } else {
                dateFlag = false;
            }
            String cellNumberType = attributes.getValue("s");
            if("2".equals(cellNumberType)){
                numberFlag = true;
            } else {
                numberFlag = false;
            }

        }
        //当元素为t时
        if("t".equals(name)){
            isTElement = true;
        } else {
            isTElement = false;
        }

        // 置空
        lastContents = "";
    }

    public void endElement(String uri, String localName, String name)
            throws SAXException {

        // 根据SST的索引值的到单元格的真正要存储的字符串
        // 这时characters()方法可能会被调用多次
        if (nextIsString) {
            try {
                int idx = Integer.parseInt(lastContents);
                lastContents = new XSSFRichTextString(sst.getEntryAt(idx))
                        .toString();
            } catch (Exception e) {

            }
        }
        //t元素也包含字符串
        if(isTElement){
            String value = lastContents.trim();
            rowlist.add(curCol, value);
            curCol++;
            isTElement = false;
            // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引
            // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符
        } else if ("v".equals(name)) {
            String value = lastContents.trim();
            value = value.equals("")?" ":value;
            //日期格式处理
            if(dateFlag){
                 Date date = HSSFDateUtil.getJavaDate(Double.valueOf(value));
                 SimpleDateFormat dateFormat = new SimpleDateFormat(
                 "dd/MM/yyyy");
                 value = dateFormat.format(date);
            }
            //数字类型处理
            if(numberFlag){
                BigDecimal bd = new BigDecimal(value);
                value = bd.setScale(3,BigDecimal.ROUND_UP).toString();
            }
            rowlist.add(curCol, value);
            curCol++;
        }else {
            //如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法
            if (name.equals("row")) {
                rowReader.getRows(sheetIndex,curRow,rowlist);
                rowlist.clear();
                curRow++;
                curCol = 0;
            }
        }

    }

    public void characters(char[] ch, int start, int length)
            throws SAXException {
        //得到单元格内容的值
        lastContents += new String(ch, start, length);
    }
}
时间: 2024-12-20 15:53:29

POI实现大数据EXCLE导入导出,解决内存溢出问题的相关文章

POI读写大数据量excel,解决超过几万行而导致内存溢出的问题

1. Excel2003与Excel2007 两个版本的最大行数和列数不同,2003版最大行数是65536行,最大列数是256列,2007版及以后的版本最大行数是1048576行,最大列数是16384列. excel2003是以二进制的方式存储,这种格式不易被其他软件读取使用:而excel2007采用了基于XML的ooxml开放文档标准,ooxml使用XML和ZIP技术结合进行文件存储,XML是一个基于文本的格式,而且ZIP容器支持内容的压缩,所以其一大优势是可以大大减小文件的尺寸. 2. 大批

使用phpExcel实现Excel数据的导入导出(完全步骤)

使用phpExcel实现Excel数据的导入导出(完全步骤) 很多文章都有提到关于使用phpExcel实现Excel数据的导入导出,大部分文章都差不多,或者就是转载的,都会出现一些问题,下面是本人研究phpExcel的使用例程总结出来的使用方法,接下来直接进入正题. 首先先说一下,本人的这段例程是使用在Thinkphp的开发框架上,要是使用在其他框架也是同样的方法,很多人可能不能正确的实现Excel的导入导出,问题基本上都是phpExcel的核心类引用路径出错,如果有问题大家务必要对路劲是否引用

Java +EasyUI+SpringMvc实现Excle导入导出(下)

前言 接上篇,在上篇文章我们介绍了要实现Excle导入做的一些配置和Excel导入的前端EasyUI代码的书写和后台controller的具体书写,这篇我们我们主要来学习Excle导出的实现和ExcelUtil类的编写. 正题 Excel导出就是根据前台条件将参数传到controller,根据参数去数据库中进行查询,查询出list集合,调用ExcelUtil工具类,将list集合转为成excle数据,输出到浏览器. 导出实现 首先我们先来看下前台代码,前台获取参数,将参数传到对于的control

Oracle之数据泵导入/导出数据

以前习惯用imp/exp导入导出数据,现在我们来搞一把数据泵导入导出的方法.它比之于imp/exp的方式的优点自不必说---速度快! 但是很不幸,我在导出数据的时候报错了,我擦,这特么就尴尬了.于是,神技乍现,康哥就擅长解决问题有木有?呀?乍一看这报错也是没谁了,完全看不懂啊!别害怕,遇到这样的报错就是文件问题,与文件相关的要么权限要么路径,这里是指数据库内部不存在这个文件,但是我在Oracle用户下面创建了啊.没用的,你要在数据库内部创建哦,记住是内部.来,我们操作一把. 来吧朋友,开始我们的

Oracle 12c pdb的数据泵导入导出

12c推出了可插拔数据库,在一个容器cdb中以多租户的形式同时存在多个数据库pdb.在为pdb做数据泵导入导出时和传统的数据库有少许不同.           1,需要为pdb添加tansnames           2,导入导出时需要在userid参数内指定其tansnames的值,比如 userid=user/[email protected]   数据泵导入导出例子 1.查看当前的SID,查看pdb并切换到容器数据库,这里的pluggable数据库是pdborcl [[email pro

MATLAB中文件的读写和数据的导入导出

http://blog.163.com/tawney_daylily/blog/static/13614643620111117853933/ 在编写一个程序时,经常需要从外部读入数据,或者将程序运行的结果保存为文件.MATLAB使用多种格式打开和保存数据.本章将要介绍 MATLAB中文件的读写和数据的导入导出. 13.1 数据基本操作 本节介绍基本的数据操作,包括工作区的保存.导入和文件打开.13.1.1 文件的存储 MATLAB支持工作区的保存.用户可以将工作区或工作区中的变量以文件的形式保

Sql server与Excel的数据互通导入导出

现在,我先从Sql server数据表导出到Excel中,再从Excel数据表导出到Sql server中: 一.Sql server数据表导出到Excel中: 1.新建一个Excel,选择"数据"菜单: 2.依次选择   "导入外部数据"--"导入数据" 后: 3.双击"新的SQL Server 连接.odc": 4.点击"下一步": 5.点击"下一步": 6.点击"完成&q

Oracle 数据泵导入导出总结

Oracle 数据泵(IMPDP/EXPDP)导入导出总结 Oracle数据泵导入导出是日常工作中常用的基本技术之一,它相对传统的逻辑导入导出要高效,这种特性更适合数据库对象数量巨大的情形,因为我日常运维的数据库对象少则几千,多则几万甚至几十万,所以传统exp/imp就会非常耗时,而数据泵方式就因此脱引而出,下面就详细总结一下数据泵的使用方法,希望能给初学者带来帮助. 一.新建逻辑目录 最好以system等管理员创建逻辑目录,Oracle不会自动创建实际的物理目录“D:\oracleData”(

客户关系管理系统中对客户及相关数据的导入导出操作

在很多系统,我们都知道,Excel数据的导入导出操作是必不可少的一个功能,这种功能能够给使用者和外部进行数据交换,也能批量迅速的录入数据到系统中:但在一些系统中,为了方便,可能把很多个基础表或者相关的数据综合到一个Excel表格文件里面,然后希望通过接口进行导入,这种需求处理就显得比较复杂一点了.本文探讨在我的客户关系管理系统中,对于单个Excel表格中,集合了客户基础数据及相关数据的导入和导出操作的处理. 1.导入导出的需求分析 本随笔主要介绍如何在系统中,导入单一文件中的数据到系统中,这个文