前言
java操作Excel常用的兩種方式,分別爲:jxl和poi。今天記錄一下我在使用poi寫Excel時遇到的內存溢出問題,以及poi是如何提供的解決方法。
先附上各版本poi官網下載鏈接https://archive.apache.org/dist/poi/release/bin/
具體實現
poi提供了兩種創建Excel的類:
一種是2003版本的HSSF(文件擴展名爲xls),一張sheet表允許存2^16=65536次方行數據,2^8 = 256列數據;
另一種是2007版本的XSSH(文件擴展名爲xlsx),一張sheet表允許存2^20 = 1048576行,2^14 = 16384列數據。我在使用XSSH創建一張包含18萬數據的sheet時,程序報錯內存溢出,爲解決這個問題找到官方解決辦法,poi爲解決內存溢出在3.8之後的版本(不包含3.8)中添加一個新類SXSSF,它是2007版本的一個升級,通過限定內存中到達一定行數清空內存的方式解決內存溢出。但這個類只能寫,不能讀。
具體場景是從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行時是效率最好的選擇。
說明
下圖是我用到的包。