前言

java操作Excel常用的两种方式,分别为:jxl和poi。今天记录一下我在使用poi写Excel时遇到的内存溢出问题,以及poi是如何提供的解决方法。
先附上各版本poi官网下载链接https://archive.apache.org/dist/poi/release/bin/

具体实现

  1. poi提供了两种创建Excel的类:
    一种是2003版本的HSSF(文件扩展名为xls),一张sheet表允许存2^16=65536次方行数据,2^8 = 256列数据;
    另一种是2007版本的XSSH(文件扩展名为xlsx),一张sheet表允许存2^20 = 1048576行,2^14 = 16384列数据。
  2. 我在使用XSSH创建一张包含18万数据的sheet时,程序报错内存溢出,为解决这个问题找到官方解决办法,poi为解决内存溢出在3.8之后的版本(不包含3.8)中添加一个新类SXSSF,它是2007版本的一个升级,通过限定内存中到达一定行数清空内存的方式解决内存溢出。但这个类只能写,不能读。
  3. 具体场景是从FTP上读取一个txt文件,里面包含18万行的数据,需要读取后保存到Excel中,代码如下
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

public class Test {

    public static void main(String[] args) throws IOException {
         try{
             long curr_time=System.currentTimeMillis();

             FTPClient ftp = new FTPClient();
             int reply;
             ftp.connect("ip地址", 端口);
             ftp.setControlEncoding("GBK");
             ftp.login("用户名","密码");// 登录
             ftp.setFileType(FTPClient.BINARY_FILE_TYPE);// 采用二进制上传
             reply = ftp.getReplyCode();
             if (!FTPReply.isPositiveCompletion(reply)) {
                ftp.disconnect();
             }
            // 设置PassiveMode被动模式-向服务发送传输请求  
             ftp.enterLocalPassiveMode();  

             // 设置以二进制流的方式传输  
             ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
             ftp.changeWorkingDirectory("/student");//转移到FTP服务器目录

             int rowaccess=100;//内存中缓存记录行数

             SXSSFWorkbook wb = new SXSSFWorkbook(rowaccess); //创建的workbook类是SXSSF
             Sheet sh = wb.createSheet();
             Row row = sh.createRow(0);

             Cell cell = row.createCell(0);
             cell.setCellValue("学号");

             cell = row.createCell(1);
             cell.setCellValue("姓名");

             cell = row.createCell(2);
             cell.setCellValue("性别");

             cell = row.createCell(3);
             cell.setCellValue("年龄");

             cell = row.createCell(4);
             cell.setCellValue("出生日期");

             InputStream ins = null;  
             String fileName = "student_2017.txt";
             ins = ftp.retrieveFileStream(fileName); // 从服务器上读取指定的文件     
             BufferedReader reader = new BufferedReader(new InputStreamReader(ins, "GBK"));   
             String line = null;   
             int j = 0;
             while ((line = reader.readLine()) != null) {   
                    List<String> list = Arrays.asList(line.toString().split("\\|"));
                    j++;
                    row = sh.createRow((int) j);
                    for (int i = 0; i < list.size(); i++) {
                        row.createCell(i).setCellValue(list.get(i));
                    }
                    if( j % rowaccess==0){//每当行数达到设置的值就刷新数据到硬盘,以清理内存
                         ((SXSSFSheet)sh).flushRows();//关键就在这
                    }
                }

                /*写数据到文件中*/
                FileOutputStream os = new FileOutputStream("d:/student/student_2017.xlsx");    
                wb.write(os);
                os.close();

                ftp.logout();

                /*计算耗时*/
                System.out.println("耗时:"+(System.currentTimeMillis()-curr_time)/1000);
           } catch(Exception e) {
               e.printStackTrace();
           }
        }
}

代码中的关键点在于首先创建的workbook是SXSSFWorkbook ,并在参数中指定了多少行时清空内存,其次在于写一个if判断,判断是否到达了指定行数,如果到达指定的行数执行((SXSSFSheet)sh).flushRows()用以清空缓存。
4. 效率
可以看到上面代码中缓存行数为100,那么多少行一清是最好的呢,我特意用这18万数据做了如下测试:

缓存行数 —- 执行时间
10000 ——— 196s
1000 ———– 26s
100 ———— 10s
10 ————– 8s
1 ————— 9s

所有总结得出在100行时是效率最好的选择。

说明

下图是我用到的包。

java long 内存溢出 java poi内存溢出_poi