poi读取大文件Excel

背景

  • 前一段时间遇到一种情况,服务器经常宕机,而且没有规律性,查看GC日志发生了out of memory,是堆溢出导致的,分析了一下堆的dump文件,发现在发生OOM时创建了大量的String对象。最后对照时间点,发现宕机的时候业务人员在上传一个excel文件,但是这个excel文件才28MB大小,感觉应该不会引起内存溢出。后来在本地启动了服务,然后尝试上传这个excel文件,同时使用Java VisualVM监控GC情况,发现在上传的时候,创建了大量的String对象,后来老年代没有可分配空间导致了OOM。最终分析结果是,excel文件中存在几十万的空行数据,表面上看,这些空行数据跟不存在数据的行是一样的,但是POI会把这种空行数据读入到内存中,感觉这也是一个坑。
  • 在网上搜了很长时间,发现国内网站上的解决方案真是没法看,基本上答案都差不多,没有什么有见解性的解决方法,后来在stackoverflow上找到了解决方法。算是给自己做一下备注,也想帮助一些还在坑里的人,就分享一下,只是自己的见解,有不得当的地方也请见谅。
  • 原先获取方法
  /**
     * 获取execle 文件
     *
     * @param fileName
     * @return
     */
    public  static Workbook getWookBook(String fileName) {
        Workbook workbook = null;
        FileInputStream fileInputStream = null;
        try {
            File file = new File(fileName);
            if (!file.exists()) {
                System.out.println("模板文件:" + fileName + "不存在!");
            }
            fileInputStream = new FileInputStream(fileName);
            workbook = WorkbookFactory.create(fileInputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        } finally {
            CloseableUtils.close(fileInputStream);
        }
        return workbook;
    }
  • 使用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文件中引入需要的jar包:

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

@Test
    public void testLoad() throws Exception{
        FileInputStream in = new FileInputStream("e:/2.xlsx");
        Workbook wk = StreamingReader.builder()
                .rowCacheSize(100)  //缓存到内存中的行数,默认是10
                .bufferSize(4096)  //读取资源时,缓存到内存的字节大小,默认是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(" ");
        }
    }
  • 最终写法
 /**
     * 获取execle 文件
     *
     * @param fileName
     * @return
     */
    public static Workbook getWookBook(String fileName) {
        Workbook workbook = null;
        if (VerifyUtil.isEmpty(fileName)) return workbook;
        boolean b = fileName.endsWith(".xlsx");
        if (b) {
            return getXlsx(fileName);
        }
        FileInputStream fileInputStream = null;
        try {
            File file = new File(fileName);
            if (!file.exists()) {
                System.out.println("模板文件:" + fileName + "不存在!");
            }
            fileInputStream = new FileInputStream(fileName);
            workbook = WorkbookFactory.create(fileInputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        } finally {
            CloseableUtils.close(fileInputStream);
        }
        return workbook;
    }

    /**
     * @return
     * @throws
     * @Description 解析xlsx 大文件类型
     * @author liuding
     * @date 2019/4/10 0010 08:57
     */
    public static Workbook getXlsx(String fileName) {
        FileInputStream in = null;
        try {
            System.out.println("文件:" + fileName);
            in = new FileInputStream(fileName);
            System.out.println("文件大小:" + String.valueOf(in.available()) + "--" + in.available() / 1024 / 1024);
            Workbook workbook = StreamingReader.builder()
                    .rowCacheSize(100)  //缓存到内存中的行数,默认是10
                    .bufferSize(4096)  //读取资源时,缓存到内存的字节大小,默认是1024
                    .open(in);  //打开资源,必须,可以是InputStream或者是File,注意:只能打开XLSX格式的文件
            return workbook;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值