• 解决POI读取大Excel文件内存溢出问题


    一、复现问题

    构造测试数据

    根据笛卡尔积算法,生成数据量大的Excel文件,示例代码如下:

    package com.test.demo;
    
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.poi.excel.BigExcelWriter;
    import cn.hutool.poi.excel.ExcelUtil;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @description 构造测试数据
     * @author rongrong
     * @version 1.0
     * @date 2020/11/7 19:17
     */
    public class ConstructData {
        public static void main(String[] args) {
            List<String> row1 = CollUtil.newArrayList("aa", "bb", "cc", "dd","aa", "bb", "cc", "dd","aa", "bb", "cc", "dd","aa", "bb", "cc", "dd");
            List<String> row2 = CollUtil.newArrayList("aa1", "bb1", "cc1", "dd1", "bb1", "cc1", "dd1", "bb1", "cc1", "dd1", "bb1", "cc1", "dd1", "bb1", "cc1", "dd1", "bb1", "cc1", "dd1", "bb1", "cc1", "dd1");
            List<String> row3 = CollUtil.newArrayList("aa2", "bb2", "cc2", "dd2", "bb1", "cc1", "dd1", "cc3", "dd3", "bb1", "cc3", "dd3", "bb1");
            List<String> row4 = CollUtil.newArrayList("cc3", "dd3", "bb1","aa3", "bb3", "cc3", "dd3", "bb1", "cc3", "dd3", "bb1", "cc1", "dd1");
            List<String> row5 = CollUtil.newArrayList("aa4", "bb4", "cc4", "dd4", "bb1", "cc1" , "cc3", "dd3", "bb1", "cc3", "dd3", "bb1", "cc3", "dd3", "bb1","dd1");
            List<List<String>> list = CollUtil.newArrayList(row1, row2, row3, row4, row5);
            List<List<String>> result = new ArrayList<List<String>>();
            descartes(list, result, 0, new ArrayList<String>());
            BigExcelWriter writer= ExcelUtil.getBigWriter("e:/测试数据.xlsx");
            // 一次性写出内容,使用默认样式
            writer.write(result);
            // 关闭writer,释放内存
            writer.close();
            System.out.println("数据写入成功!!");
        }
    
    
        /***
         * 笛卡尔积算法
         * @param dimvalue
         * @param result
         * @param layer
         * @param curList
         */
        private static void descartes(List<List<String>> dimvalue, List<List<String>> result, int layer, List<String> curList) {
            if (layer < dimvalue.size() - 1) {
                if (dimvalue.get(layer).size() == 0) {
                    descartes(dimvalue, result, layer + 1, curList);
                } else {
                    for (int i = 0; i < dimvalue.get(layer).size(); i++) {
                        List<String> list = new ArrayList<String>(curList);
                        list.add(dimvalue.get(layer).get(i));
                        descartes(dimvalue, result, layer + 1, list);
                    }
                }
            } else if (layer == dimvalue.size() - 1) {
                if (dimvalue.get(layer).size() == 0) {
                    result.add(curList);
                } else {
                    for (int i = 0; i < dimvalue.get(layer).size(); i++) {
                        List<String> list = new ArrayList<String>(curList);
                        list.add(dimvalue.get(layer).get(i));
                        result.add(list);
                    }
                }
            }
        }
    
    }
    
    

    生成Excel文件大小如图所示:

    还是不够大,那么我在加工下,这次肯定数据量肯定够大了。

    接下来,我们用poi读取Excel文件,示例代码如下:

      public static void main(String[] args) throws IOException {
            // 获取文件路径和文件
            FileInputStream fis = new FileInputStream("e:/测试数据.xlsx");
            // 将输入流转换为工作簿对象
            XSSFWorkbook workbook = new XSSFWorkbook(fis);
            // 获取第一个工作表
            XSSFSheet sheet = workbook.getSheetAt(0);
            //遍历所有的行
            for (Row row : sheet) {
                System.out.println("开始遍历第" + row.getRowNum() + "行数据:");
                //遍历所有的列
                for (Cell cell : row) {
                    System.out.print(cell.getStringCellValue() + " ");
                }
                System.out.println(" ");
            }
        }
    

    运行结果

    果然不负众望,终于内存溢出了,如下图所示:

    二、解决方法

    使用Excel Streaming Reader,这个第三方工具会把一部分的行(可以设置)缓存到内存中,在迭代时不断加载行到内存中,而不是一次性的加载所有记录到内存,这样就可以不断的读取excel内容并且不影响内存的使用。

    但是这个工具也有一定的限制:只能用于读取excel的内容,写入操作不可用;可以使用getSheetAt()方法获取到对应的Sheet,因为当前只是加载了有限的row在内存中,因此不能随机访问row,即不能使用getRow(int rowNum)方法;由于行数据已经加载到了内存,因此可以随机的访问Cell数据,即可以使用getCell(int cellnum)方法。使用这个工具,建议使用迭代器来进行迭代。具体内容可以参见:https://github.com/monitorjbl/excel-streaming-reader。

    在pom.xml文件中加入依赖:

        <dependency>
                <groupId>com.monitorjbl</groupId>
                <artifactId>xlsx-streamer</artifactId>
                <version>2.0.0</version>
            </dependency>
    

    具体示例代码如下:

    public static void main(String[] args) throws IOException {
            FileInputStream in = new FileInputStream("e:/测试数据.xlsx");
            Workbook wk = StreamingReader.builder()
                    .rowCacheSize(100)  //缓存到内存中的行数,默认是10
                    .bufferSize(8192)  //读取资源时,缓存到内存的字节大小,默认是1024
                    .open(in);  //打开资源,必须,可以是InputStream或者是File,注意:只能打开XLSX格式的文件
            Sheet sheet = wk.getSheetAt(0);
            //遍历所有的行
            for (Row row : sheet) {
                System.out.println("开始遍历第" + row.getRowNum() + "行数据:");
                //遍历所有的列
                for (Cell cell : row) {
                    System.out.print(cell.getStringCellValue() + " ");
                }
                System.out.println(" ");
            }
        }
    
    

    运行结果

    这次就很稳定奔放了,不报错,而且速度很快,如下图所示:

    参考文章

    https://www.cnblogs.com/cksvsaaa/p/7280261.html

    优秀不够,你是否无可替代

    软件测试交流QQ群:721256703,期待你的加入!!

    欢迎关注我的微信公众号:软件测试君


  • 相关阅读:
    [转载]Install Opera 12.16 Web Browser in CentOS/RHEL and Fedora
    [转载]CentOS 6.5 安装五笔输入法
    [转载]Lenovo E431 装Centos7无线驱动安装
    ElasticSearch的按日期排序问题
    [转载]java自带线程池和队列详细讲解
    [转载]Redis后台启动
    [转载]Process工具类,提供设置timeout功能
    [转载]使用java.lang.Process类的简单例子
    [转载]SecureCRT 绝佳配色方案, 保护你的眼睛
    4.二叉搜索树转为有序双向链表(递归算法与非递归算法)
  • 原文地址:https://www.cnblogs.com/longronglang/p/13942394.html
Copyright © 2020-2023  润新知