zoukankan      html  css  js  c++  java
  • 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);
        }
    }
  • 相关阅读:
    8.26 Linux的目录机构与目录管理
    8.26 基础命令
    8.26 系统基础
    8.26 课程介绍
    8.21-23 awk
    8.20 数组与字符
    8.19 函数
    8.16 sed命令
    Linux操作系统--初级--Linux的目录结构与目录管理
    Linux操作系统--初级--基础命令
  • 原文地址:https://www.cnblogs.com/huangjian2/p/6238237.html
Copyright © 2011-2022 走看看