Java 讀取較大資料的excel檔案
阿新 • • 發佈:2018-11-05
記錄一下使用poi讀取大資料excel檔案踩的坑
介紹
Java 有2個jar包可以操作excel檔案,分別是jxl和poi;
jxl這個jar包只能讀取excel2003年的檔案(檔案字尾為.xls),而poi這個jar包excel2003(檔案字尾為.xls)和excel2007(檔案字尾為.xls)的檔案都可以讀取。
問題
我是用的是poi這個jar包,對excel進行讀取;
下面是上傳一個file檔案時呼叫的方法
public static Workbook getWorkbookByMultipartFile(MultipartFile file){ String fileName=file.getOriginalFilename(); if (!fileName.matches("^.+\\.(?i)(xls)$") && !fileName.matches("^.+\\.(?i)(xlsx)$")) { log.info("檔案格式錯誤,檔名:{}",fileName); return null; } boolean isExcelXlsx2007 = false; if (fileName.matches("^.+\\.(?i)(xlsx)$")) { isExcelXlsx2007 = true; } InputStream is = null; try { is = file.getInputStream(); } catch (IOException e) { log.error("讀取黃頁出錯,file.getInputStream();失敗",e); return null; } Workbook wb = null; if (isExcelXlsx2007) { try { wb = new XSSFWorkbook(is); } catch (IOException e) { log.error("讀取ExcelXlsx2007出錯",e); }finally { if (is!=null){ try { is.close(); } catch (IOException e) { log.error("關閉檔案流出錯",e); } } return wb; } } try { wb = new HSSFWorkbook(is); } catch (IOException e) { log.error("讀取ExcelXlsx2007出錯",e); }finally { if (is!=null){ try { is.close(); } catch (IOException e) { log.error("關閉檔案流出錯",e); } } return wb; } }
呼叫這個方法就能獲取一個Workbook物件,然後獲取sheet物件,對其中的資料進行處理,這個肯定是沒有問題的,但是這個方法是把檔案轉化為inputstream流,然後想通過這個流來獲取一個Workbook物件,當檔案很大的時候(我操作20多萬條資料時,檔案大約15M左右),這個步驟就會出現記憶體溢位的問題。這個問題直接就把後路斷死了,只能另外找辦法解決。
解決
使用poi的另一個模式,poi Sax事件驅動解析excel
/** * Excle xxls 批量讀取大檔案操作類 * */ public abstract class XlsxProcessAbstract { private final Logger logger = LoggerFactory.getLogger(XlsxProcessAbstract.class); //開始讀取行數從第0行開始計算 private int rowIndex = -1; private final int minColumns = 0; /** * Destination for data */ public <T> LinkedList<T> processAllSheet(Integer index, Class<T> clazz) throws Exception { OPCPackage pkg = OPCPackage.open(this.getInputStream()); ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg); XSSFReader xssfReader = new XSSFReader(pkg); StylesTable styles = xssfReader.getStylesTable(); SheetToCSV<T> sheetToCSV = new SheetToCSV<T>(clazz); parserSheetXml(styles, strings, sheetToCSV, xssfReader.getSheet("rId"+index)); return sheetToCSV.getPojoList(); } /** * 解析excel 轉換成xml * * @param styles * @param strings * @param sheetHandler * @param sheetInputStream * @throws IOException * @throws SAXException */ public void parserSheetXml(StylesTable styles, ReadOnlySharedStringsTable strings, SheetContentsHandler sheetHandler, InputStream sheetInputStream) throws IOException, SAXException { DataFormatter formatter = new DataFormatter(); InputSource sheetSource = new InputSource(sheetInputStream); try { XMLReader sheetParser = SAXHelper.newXMLReader(); ContentHandler handler = new XSSFSheetXMLHandler(styles, null, strings, sheetHandler, formatter, false); sheetParser.setContentHandler(handler); sheetParser.parse(sheetSource); } catch (ParserConfigurationException e) { throw new RuntimeException("SAX parser appears to be broken - " + e); } } public abstract InputStream getInputStream() throws IOException; /** * 讀取excel行、列值 * * @author nevin.zhang */ private class SheetToCSV<T> implements SheetContentsHandler { private boolean firstCellOfRow = false; private T pojo; private Class<T> clazz; private int currentRowNumber = -1; private int currentColNumber = -1; private ArrayList<String> keyList = Lists.newArrayList(); private LinkedList<T> pojoList = Lists.newLinkedList(); public LinkedList<T> getPojoList() { return pojoList; } public SheetToCSV(Class<T> clazz) { this.clazz = clazz; } /** * 處理cell中為空值的情況 * @param number */ private void processCellBlankCells(int number) { for (int i = 0; i < number; i++) { for (int j = 0; j < minColumns; j++) { } } } @Override public void startRow(int rowNum) { //logger.info(String.valueOf(rowNum)); processCellBlankCells(rowNum - currentRowNumber - 1); if(rowNum!=0){ try { pojo = clazz.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } firstCellOfRow = true; currentRowNumber = rowNum; currentColNumber = -1; } @Override public void endRow(int rowNum) { if(pojo==null){ return; } System.out.println(pojo); if (currentRowNumber!=0){ pojoList.add(pojo); } } @Override public void cell(String cellReference, String cellValue, XSSFComment comment) { if (firstCellOfRow) { firstCellOfRow = false; } else { } if (cellReference == null) { cellReference = new CellAddress(currentRowNumber, currentColNumber).formatAsString(); } int thisCol = (new CellReference(cellReference)).getCol(); int missedCols = thisCol - currentColNumber - 1; for (int i = 0; i < missedCols; i++) { // excel中為空的值設定為“|@|” } currentColNumber = thisCol; // logger.info("當前行數:{},當前列數:{},當前值cell:{}",currentRowNumber, currentColNumber, cellValue); if (currentRowNumber ==0){ keyList.add(cellValue); return; } if (pojo == null||StringUtils.isBlank(cellValue)) { return; } try { PropertyDescriptor pd = new PropertyDescriptor(keyList.get(currentColNumber), clazz); try { pd.getWriteMethod().invoke(pojo, cellValue); pd=new PropertyDescriptor("createBy",clazz); pd.getWriteMethod().invoke(pojo, RequestHolder.getCurrentUser().getUsername()); pd=new PropertyDescriptor("updateBy",clazz); pd.getWriteMethod().invoke(pojo, RequestHolder.getCurrentUser().getUsername()); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (IntrospectionException e) { e.printStackTrace(); } } @Override public void headerFooter(String text, boolean isHeader, String tagName) { } } }
該方法對excel的資料有規定的要求——excel的第1行全放屬性名,下面的2,3.....行都是放置對應的資料,遍歷每行都會生成一個對應的pojo實體類物件。
物件千萬用LinkedList來存,不能用Arraylist,因為這種方法不能直接取到excel的資料條數,導致不能直接new Arratlist,Arraylist會自動擴充套件,浪費記憶體。
最後將LinkedList傳到service層,呼叫dao層的方法進行資料插入,這裡又會出問題,因為我是用的時Mybatis,他會自動代理mapper中的方法,把傳過來的list會再copy一遍,又會出現記憶體溢位的問題,只能將這個陣列進行分批插入的操作,最終才完成。