自己挖的坑自己填–jxl進行Excel下載堆內存溢出問題
今天在進行使用 jxl 進行 Excel 下載時,由於數據量大(4萬多條接近5萬條數據的下載),數據結構過於負責,存在大量大對象(雖然在對象每次用完都設置為null,但還是存在內存溢出問題),加上本地電腦內存不大(只有8G),導致下載數據時報堆內存溢出,下載失敗。
Exception data: java.lang.OutOfMemoryError
at jxl.write.biff.MemoryDataOutput.write(MemoryDataOutput.java:72)
at jxl.write.biff.File.write(File.java:149)
at jxl.write.biff.RowRecord.writeCells(RowRecord.java:324)
at jxl.write.biff.SheetWriter.write(SheetWriter.java:479)
at jxl.write.biff.WritableSheetImpl.write(WritableSheetImpl.java:1431)
at jxl.write.biff.WritableWorkbookImpl.write(WritableWorkbookImpl.java:915)
下面是案例復現的簡單模擬代碼:
<dependency> <groupId>net.sourceforge.jexcelapi</groupId > <artifactId>jxl</artifactId > <version>2.6.12</version > </dependency >
public class MyExcel { public static void main(String[] args) throws Exception { File xlsFile = new File("E:\\jxl.xls"); // 創建一個工作簿 WritableWorkbook workbook = Workbook.createWorkbook(xlsFile); // 創建一個工作表 WritableSheet sheet = workbook.createSheet("sheet1", 0); Map<Integer, ArrayList<Map<Integer, Person>>> mapListMap = new HashMap<>(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class); PersonServiceImpl bean = context.getBean(PersonServiceImpl.class); //獲取數據庫數據 List<Person> listBean = bean.getList(); //進行數據處理 for (int row = 0; row < 45000; row++) { ArrayList<Map<Integer, Person>> listMap = new ArrayList<Map<Integer, Person>>(); for (int col = 0; col < 30; col++) { Map<Integer, Person> map = new HashMap<Integer, Person>(); for (int j = 0; j < listBean.size(); j++) { map.put(j, listBean.get(j)); } listMap.add(map); map = null; } mapListMap.put(row, listMap); listMap = null; } //寫數據 for (int row = 0; row < 45000; row++) { ArrayList<Map<Integer, Person>> listMap = mapListMap.get(row); for (int col = 0; col < 30; col++) { Map<Integer, Person> map = listMap.get(col); for (int j = 0; j < listBean.size(); j++) { Person person = map.get(j); sheet.addCell(new Label(col, row, person.getId()+"" + row + col)); sheet.addCell(new Label(col, row, person.getName() + row + col)); sheet.addCell(new Label(col, row, person.getAge()+"" + row + col)); map = null; } } listMap = null; } workbook.write(); workbook.close(); }
運行後結果:
解決辦法:
(1)最簡單的方法是加大內存,本地電腦內存過小,當把代碼部署到公司測試環境(測試環境內存是16G)時該問題不再復現;
(2)採用臨時文件寫入EXCEL功能,設定臨時文件的位置,可以有效的避免內存溢出(jxl版本必須是2.6.12及其以上,2.6版本沒有採用臨時文件的功能);
public static void main(String[] args) throws Exception { File xlsFile = new File("E:\\jxl.xls"); // 創建一個工作簿 WorkbookSettings ws = new WorkbookSettings(); ws.setUseTemporaryFileDuringWrite(true); ws.setTemporaryFileDuringWriteDirectory(new File("E:\\"));//指定一個臨時文件路徑 WritableWorkbook workbook = Workbook.createWorkbook(xlsFile,ws); // 創建一個工作表 WritableSheet sheet = workbook.createSheet("sheet1", 0); Map<Integer, ArrayList<Map<Integer, Person>>> mapListMap = new HashMap<>(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class); PersonServiceImpl bean = context.getBean(PersonServiceImpl.class); //獲取數據庫數據 List<Person> listBean = bean.getList(); //進行數據處理 for (int row = 0; row < 45000; row++) { ArrayList<Map<Integer, Person>> listMap = new ArrayList<Map<Integer, Person>>(); for (int col = 0; col < 30; col++) { Map<Integer, Person> map = new HashMap<Integer, Person>(); for (int j = 0; j < listBean.size(); j++) { map.put(j, listBean.get(j)); } listMap.add(map); map = null; } mapListMap.put(row, listMap); listMap = null; } //寫數據 for (int row = 0; row < 45000; row++) { ArrayList<Map<Integer, Person>> listMap = mapListMap.get(row); for (int col = 0; col < 30; col++) { Map<Integer, Person> map = listMap.get(col); for (int j = 0; j < listBean.size(); j++) { Person person = map.get(j); sheet.addCell(new Label(col, row, person.getId()+"" + row + col)); sheet.addCell(new Label(col, row, person.getName() + row + col)); sheet.addCell(new Label(col, row, person.getAge()+"" + row + col)); }
map = null; } listMap = null; } workbook.write(); workbook.close(); }
當開始執行下載時,會產生一個臨時文件,jxl 會把數據寫入到這個臨時文件中,寫入結束後會把這個臨時文件刪除:
PS:
(1)Excel 表一個 sheet 頁可以存儲6萬條,所以一般超過5萬條的,其他的數據就存儲其他sheet中;或採用分成幾個 Excel 表來實現數據下載;
(2)今天測試下載的 Excel 數據文本有170M(數據有4.8w條),通過MS Office 打開失敗,為空 Excel 表,但通過 WPS 可以正常打開。