本文记录一下用Apache POI操作Excel时可能遇到一些坑。

excel版本错误

首先是excel版本问题,由于office excel 03版和07版的区别,在读取文件时如果版本不匹配会报以下错误。

org.apache.poi.poifs.filesystem.NotOLE2FileException: Invalid header signature; read 0x0010000000060809, expected 0xE11AB1A1E011CFD0 - Your file appears not to be a valid OLE2 document
    at org.apache.poi.poifs.storage.HeaderBlock.<init>(HeaderBlock.java:167)
    at org.apache.poi.poifs.storage.HeaderBlock.<init>(HeaderBlock.java:106)
    at org.apache.poi.poifs.filesystem.POIFSFileSystem.<init>(POIFSFileSystem.java:137)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.<init>(HSSFWorkbook.java:382)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.<init>(HSSFWorkbook.java:364)

最初解决办法是根据文件名采用不同的Workbook来初始化stream,大致代码如下,请忽视判断逻辑不严谨的问题(这里应该取后缀名而不是判断包含)。

if (filePath.indexOf(".xlsx") != -1) {
                XSSFWorkbook wb = new XSSFWorkbook(inputStream);
                return wb.getSheetAt(sheetIndex);
            } else {
                HSSFWorkbook wb = new HSSFWorkbook(inputStream);
                return wb.getSheetAt(sheetIndex);
            }

手动修改文件名后不匹配

通过上面的方法大部分还是可以解决的,但是遇到人为改变后缀名后会出现如下异常。

Caused by: org.apache.poi.openxml4j.exceptions.InvalidFormatException: Package should contain a content type part [M1.13]
    at org.apache.poi.openxml4j.opc.ZipPackage.getPartsImpl(ZipPackage.java:203)
    at org.apache.poi.openxml4j.opc.OPCPackage.getParts(OPCPackage.java:684)
    at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:275)
    at org.apache.poi.util.PackageHelper.open(PackageHelper.java:37)

再次优化一下,使用工厂模式创建Workbook:

Workbook workbook = WorkbookFactory.create(inputStream);

看一下POI的源码:

public static Workbook create(InputStream inp, String password) throws IOException, InvalidFormatException, EncryptedDocumentException {
        InputStream is = FileMagic.prepareToCheckMagic(inp);
        FileMagic fm = FileMagic.valueOf(is);
        switch(fm) {
        case OLE2:
            NPOIFSFileSystem fs = new NPOIFSFileSystem(is);
            return create(fs, password);
        case OOXML:
            return new XSSFWorkbook(OPCPackage.open(is));
        default:
            throw new InvalidFormatException("Your InputStream was neither an OLE2 stream, nor an OOXML stream");
    }

可以看到他是根据stream类型来初始化的,这样即使人为改变文件名也无关紧要。

stream类型错误

通过上面的方式处理后,有时候还是会出现以下异常:

org.apache.poi.openxml4j.exceptions.InvalidFormatException: Your InputStream was neither an OLE2 stream, nor an OOXML stream
    at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:186)
    at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:149)
    at com.github.crab2died.ExcelUtils.readExcel2Objects(ExcelUtils.java:126)
    at com.github.crab2died.ExcelUtils.readExcel2Objects(ExcelUtils.java:206)

这个问题可能是文件传输stream为非可读性的stream,解决方法为将读取的stream转为ByteArrayInputStream再读取:

InputStream is = new ByteArrayInputStream(xlsxInByteArray);

然而笔者是直接读取本地文件,并非此类问题,以下解答给了笔者答案,直接文件另存为后再读取新的文件,问题解决。

https://stackoverflow.com/questions/29894185/your-inputstream-was-neither-an-ole2-stream-nor-an-ooxml-stream-while-reading

如果文件很多,直接在程序中先另写一个文件再读取新文件即可,目前没有找到更好的解决方案。