1. 程式人生 > >Java 讀取較大資料的excel檔案

Java 讀取較大資料的excel檔案

記錄一下使用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一遍,又會出現記憶體溢位的問題,只能將這個陣列進行分批插入的操作,最終才完成。