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一遍,又会出现内存溢出的问题,只能将这个数组进行分批插入的操作,最终才完成。

扫描二维码关注公众号,回复: 3934076 查看本文章

猜你喜欢

转载自blog.csdn.net/ATadpole/article/details/81588562