poi解析Excel内容

poi可以将指定目录下的Excel中的内容解析、读取到java程序中。下面是一个Demo:

使用poi需要导下包,如下:
poi解析Excel内容_第1张图片


首先是准备读取的Excel表,存放在“E:\programming\备份数据\工单分析结果_2020年7月.xlsx”下:
其他的sheet页(一共9个可见Sheet页),和下面图中的内容也都类似,只是数据稍微不同。
poi解析Excel内容_第2张图片


为了将Excel表中的数据保存在java程序中,下面创建了一个WorkOrder工单的实体类:
poi解析Excel内容_第3张图片

先大概看下主线程中的代码:
poi解析Excel内容_第4张图片

poi解析Excel内容,是需要先创建一个工作簿对象的。
但是因为Microsoft Excel的版本不同,所以文件后缀名也有所不同,例如.xls和.xlsx。
所以我们需要根据解析的Excel的后缀名,自动创建出对应类型的工作簿对象,代码如下:

/**
   * HSSF读写.xls格式文件
   * XSSF读写.xlsx格式文件
   * SXSSF读写.xlsx格式文件
   * HWPE读写doc格式文件
   * HSLF读写PowerPoint文件
   * HDGF读写visio格式文件
   * HPBF读写oublisher格式文件
   * HSMF读写outlook文件
*/

/**
 * 根据后缀名创建对应的工作簿对象
 * @param fileType
 * @param is
 * @return WorkBook
 * @throws IOException
 */
public static Workbook getWorkBook(String fileType,InputStream is) throws IOException {
    //根据文件后缀名确定需要创建的工作簿对象(这里只解析.xls和.xlsx后缀名的Excel)
    if(fileType.equalsIgnoreCase("xls")){
        workbook = new HSSFWorkbook(is);
    }else if(fileType.equalsIgnoreCase("xlsx")){
        workbook = new XSSFWorkbook(is);
    }
    return workbook;
}

有了自动创建工作簿对象类型的方法之后,我们开始编写读取Excel的方法readExcel(),如下:

/**
 * 读取Excel
 * @param FileUrl
 * @return String
 */
public static List readExcel(String FileUrl){
    //Workbook workbook = null;
    FileInputStream fis = null;

    try {
        String fileType = FileUrl.substring(FileUrl.lastIndexOf(".")+1,FileUrl.length()); //获取文件后缀名
        File file = new File(FileUrl); //获取Excel对象
        if(!file.exists()){ //判断路径是否正确
            System.out.println("文件不存在!");
            return null;
        }

        fis = new FileInputStream(file); //将文件对象写入流
        workbook = getWorkBook(fileType,fis); //根据文件后缀名,自动创建对应类型的工作簿对象

        List resulet = parseExcel(workbook); //解析Excel,获取工作簿中的值

        return resulet;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(null != fis){
            try {
                fis.close();    //关闭流对象
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return null;
}

解析Ecxel的方法需要我们自己编写,如下:

/**
 * 解析Excel中的内容
 * @param workbook
 * @return List
 */
public static List parseExcel(Workbook workbook){
    List resultList = new ArrayList<>(); //创建返回值接收对象

    //解析Sheet数量,确定循环解析次数(这里不做Sheet的隐蔽性校验)
    for (int sheetNum=0; sheetNum

Excel的单元格中,填写的内容可能是日期、数字、字符串、布尔值等,也有可能是其他的意想不到的类型。
为了能够顺利解析内容,我们需要自定义一个单元格内容类型转换为String类型的方法——convertRowToData(),如下:

/**
 * 类型转换(无论什么类型的值都将转换为String类型)
 * @param cell
 * @return String
 */
public static String convertCellValueToString(Cell cell){
    FormulaEvaluator formulaEvaluator = workbook.getCreationHelper().createFormulaEvaluator();  //用于解析和处理Cell公式的接口对象
    if(cell==null){ //判断单元格对象是否为空(这里其实可以不做判断,因为poi源码中有对null进行处理的代码)
        return null;
    }
    String returnValue = null;
    switch (cell.getCellType()){
        case Cell.CELL_TYPE_BLANK:   //空值
            break;
        case Cell.CELL_TYPE_BOOLEAN: //布尔
            returnValue = String.valueOf(cell.getBooleanCellValue());
            break;
        case Cell.CELL_TYPE_ERROR:   //异常
            returnValue = "非法字符";
            break;
        case Cell.CELL_TYPE_NUMERIC: //数值和日期
            if(HSSFDateUtil.isCellDateFormatted(cell)){      //处理日期、时间格式
                SimpleDateFormat sdf = null;
                if(cell.getCellStyle().getDataFormat()==14){
                    sdf = new SimpleDateFormat("yyyy/MM/dd");
                }else if(cell.getCellStyle().getDataFormat()==21){
                    sdf = new SimpleDateFormat("HH:mm:ss");
                }else if(cell.getCellStyle().getDataFormat()==22){
                    sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                }else{
                    throw new RuntimeException("日期格式错误!");
                }
                Date date = cell.getDateCellValue();
                returnValue = sdf.format(date);
            }else if(cell.getCellStyle().getDataFormat()==0){ //处理数值格式
                cell.setCellType(Cell.CELL_TYPE_STRING);
                returnValue = String.valueOf(cell.getRichStringCellValue().getString());
            }
            break;
        case Cell.CELL_TYPE_FORMULA: //公式————这个公式的处理方式需要特别注意一下,文末我会特意再说明一下这个公式的特殊
            if(cell.getCachedFormulaResultType()==Cell.CELL_TYPE_NUMERIC){
                returnValue = String.valueOf(cell.getNumericCellValue());
            }else if(cell.getCachedFormulaResultType()==Cell.CELL_TYPE_STRING){
                returnValue = String.valueOf(cell.getStringCellValue());
            }
            break;
        case Cell.CELL_TYPE_STRING: //字符串
            returnValue = String.valueOf(cell.getStringCellValue());
            break;
        default:    //其他
            returnValue="未知类型";
            break;
    }
    return returnValue;
}

最后就是用来封装每行数据为一个WorkOrder对象的方法了,这个很简单,如下:

/**
 * 将单元格中的内容转换为String并封装为WorkOrder对象
 * @param row
 * @return WorkOrder
 */
public static WorkOrder convertRowToData(Row row){
    WorkOrder workOrder = new WorkOrder();
    int cellNum = 0;
    Cell cell = null;

    workOrder.setSheetName(sheet.getSheetName());                           //获取Sheet名字
    workOrder.setMonth(convertCellValueToString(row.getCell(cellNum++)));   //获取月份信息——cellNum++是因为Excel是固定的模板,模板的前两行内容是固定格式,没必要读取
    workOrder.setMonth1(convertCellValueToString(row.getCell(cellNum++)));  //1月
    workOrder.setMonth2(convertCellValueToString(row.getCell(cellNum++)));  //2月
    workOrder.setMonth3(convertCellValueToString(row.getCell(cellNum++)));  //3月
    workOrder.setMonth4(convertCellValueToString(row.getCell(cellNum++)));  //4月
    workOrder.setMonth5(convertCellValueToString(row.getCell(cellNum++)));  //5月
    workOrder.setMonth6(convertCellValueToString(row.getCell(cellNum++)));  //6月
    workOrder.setMonth7(convertCellValueToString(row.getCell(cellNum++)));  //7月
    workOrder.setMonth8(convertCellValueToString(row.getCell(cellNum++)));  //8月
    workOrder.setMonth9(convertCellValueToString(row.getCell(cellNum++)));  //9月
    workOrder.setMonth10(convertCellValueToString(row.getCell(cellNum++))); //10月
    workOrder.setMonth11(convertCellValueToString(row.getCell(cellNum++))); //11月
    workOrder.setMonth12(convertCellValueToString(row.getCell(cellNum++))); //12月

    return workOrder;
}

程序编写到这里,已经差不多了,运行一下,我们看下结果:
poi解析Excel内容_第5张图片


一开始看到运行结果,我很蒙,前9个Sheet页的数据很正常。
但是明明没有什么“基础数据”的Sheet,那这些数据到底是从哪儿解析出来的(当初没考office计算机证,现在觉得脑壳痛...)?
难道是解析过程出错了?于是开始了漫长的问题排查过程...
最后在需要解析的Ecxel表本身中找到了答案:Sheet可以被隐藏,隐藏后是看不到的(内心毫无波澜...),如下:
poi解析Excel内容_第6张图片
poi解析Excel内容_第7张图片


根据Excel的Sheet页可以被隐藏的特性,加上刚刚程序运行出来的结果来一起推理,我们可以知道:
poi读取Excel内容的时候,即便是Sheet页被隐藏,内容依旧是可以被读取出来的!
但是我们不妨想一下,既然Sheet页被隐藏,那说明隐藏者认为该Sheet页的数据不再使用、或不希望被读取,
那么,我们就需要在程序中做处理,即被隐藏的Sheet页的数据不再进行解析,这部分处理代码我写在parseExcel()中,如下:

/**
 * 解析Excel中的内容
 * @param workbook
 * @return List
 */
public static List parseExcel(Workbook workbook){
    List resultList = new ArrayList<>(); //创建返回值接收对象

    //解析Sheet数量,确定循环解析次数(这里不做Sheet的隐蔽性校验)
    for (int sheetNum=0; sheetNum

这里用到的isSheetHidden()和isSheetVeryHidden()是我在poi源码中找到的,
主要是用于判断Sheet页是否是Hidden(隐蔽)或VeryHidden(非常隐蔽),它们的返回值都是Boolean类型的,
具体可参见Excel中对于Sheet页的Visible设置,更能帮助理解,如下:
poi解析Excel内容_第8张图片

我在百度这部分内容的时候,发现很多人都在寻找poi中对于Sheet页隐蔽性的判断和设置的方法,
设置的方法很容易百度到,但是判断隐蔽性的方法基本上没有人去回答,所以这里我做个总结:

方法名 作用 调用对象 补充说明
isSheetHidden(int) 判断Sheet是否隐蔽 Wookbook 返回值为Boolean类型
isSheetVeryHidden(int) 判断Sheet是否非常隐蔽 Wookbook 设置后需要重新生成Excel文件覆盖原来的文件才能生效
setSheetHidden(int,boolean) 设置Sheet隐蔽性 Wookbook 设置后需要重新生成Excel文件覆盖原来的文件才能生效
setSheetHidden(int,int) 设置Sheet隐蔽性 Wookbook 设置后需要重新生成Excel文件覆盖原来的文件才能生效
removeSheetAt(int) 删除指定Sheet页 Wookbook 设置后需要重新生成Excel文件覆盖原来的文件才能生效

增加对于隐蔽性处理的代码后,再来运行代码,看下结果:
poi解析Excel内容_第9张图片


最后补充一下关于Excel中的公式的相关内容:
如果对于Excel用的比较粗浅的人(比如我自己...),一开始听到公式,你是不是以为是这个?
poi解析Excel内容_第10张图片
或者是这个?
poi解析Excel内容_第11张图片
如果你觉得是这样的,那咱俩就是同志了.......
其实Excel中单元格的公式,更多是指的用公式计算出来结果的单元格内容
这么一听是不是很绕?那举个例子,比如大家都会用的选中几个单元格,然后点下求和按钮自动计算出来和,这就是公式处理。
那怎么判断哪个单元格中的值是用公式计算出来的呢?很简单(对我这个Excel渣渣来说,可能一辈子也不会注意到这点,简单个锤子...),如下:
poi解析Excel内容_第12张图片
poi解析Excel内容_第13张图片


如果只看Excel中的内容,其实单元格是否使用公式计算值,我们看的并没什么差别,
But,放到Java中用poi读取后,处理与不处理的差别可就大了去了(刚开始的时候被这个公式坑惨了...),看以下运行结果:
poi解析Excel内容_第14张图片
poi解析Excel内容_第15张图片

刚开始对poi不熟悉,各种百度poi对于公式的处理,找了很多,也走了很多弯路,最终确定这个方式靠谱,
这个方式的好处在于,处理公式的结果很稳~就算是原本用于公式计算的单元格被删掉,它也可以正常获取单元格的值!主要代码如下:

FormulaEvaluator formulaEvaluator = workbook.getCreationHelper().createFormulaEvaluator();  //用于解析和处理Cell公式的接口对象

case Cell.CELL_TYPE_FORMULA: //公式
    if(cell.getCachedFormulaResultType()==Cell.CELL_TYPE_NUMERIC){
        returnValue = String.valueOf(cell.getNumericCellValue());
    }else if(cell.getCachedFormulaResultType()==Cell.CELL_TYPE_STRING){
        returnValue = String.valueOf(cell.getStringCellValue());
    }
    break;

顺带补充一下,我用的是poi-3.9版本,目前已经更新到4.X版本了,但是很多使用者反应不稳定,还是建议使用比较老的,稳定~
以上就是目前我对poi读取Excel内容的理解和实践,如果以后还有更多的理解,再回来补充!
有问题欢迎留言,看到必回!


------------------------------------时间分割线 2020/8/26------------------------------------

你可能感兴趣的:(poi解析Excel内容)