Apache POI Excel入门(从POI提供的API倒推Excel功能)POI EasyExcel的底层框架

993 阅读7分钟

Apache POI Excel入门(从POI提供的API倒推Excel功能)

之前只有简单用过Excel,对Excel的功能不甚了解。而在使用POI时,上面就看到密密麻麻的API,看得眼花缭乱。 本文就从一名Excel小白,通过代码API的角度,去发掘和认识Excel所提供的各项属性与功能。

一、Excel整体概念

名称英文解释
工作簿WorkBook一个Excel文件对应一个WorkBook,可以认为WorkBook就是这整个文档文件。
工作表Sheet一个WorkBook下有很多Sheet
单元格Cell每一个Sheet都由若干Cell组成

名称

  • 名称注意范围可以是WorkBook或者具体的一个Sheet就好了!
  • 名称可指定引用位置来自哪一个单元格,也可以应用于一个单元格!

image.png

Sheet工作表

打开Excel工具,底下显示的就是若干个Sheet。

  • 每次打开Excel文件,会回到上一次打开的Sheet,这个是Excel文件本身存储的信息。(即使换一个Excel工具打开也一样) 而且,每个Sheet还会存储上一次打开这个Sheet停留的单元格(Excel工具中不一定有把这个信息显示出来) 也就是,只是读一个Excel文件,哪怕没有对它进行修改操作,文件本身也会有一些状态属性在发生改变。

  • Sheet工作表可以设置隐藏、保护等属性。

打印区域
  • 每一个Sheet工作表可以单独设置打印区域。

image.png

方案管理器
  • sheet方案:数据>模拟分析>方案管理器 方案提供了一种动态改变单元格数据的手段。(每个方案最多允许32个单元格)

image.png

条件格式

选一个范围,然后设置:条件格式,效果如下:

image.png

二、Excel测试

在编写代码前,先进行以下操作。

注意:这部分添加的内容会与第三部分的代码结果一一对应!

添加名称

名称设置后如下:

image.png

添加图片

image.png

添加批注

image.png

添加分页符

image.png

添加超链接

image.png

添加数据提示与约束

image.png

image.png

三、Excel代码

依赖

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.0.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.0.0</version>
        </dependency>
    </dependencies>

初始化

通过new XSSFWorkbook(文件or文件流)获取到Workbook,如下:

@Slf4j
public class ExcelTest {
    public static void main(String[] args) throws Exception {
        //读取文件
        String userDir = System.getProperty("user.dir");
        String excelPath = userDir + "/live-test/src/main/resources/test.xlsx";
        File file = new File(excelPath);
        log.info("文件路径:{} , 存在:{},大小:{}", excelPath, file.exists(), file.length());
        //流
        FileInputStream inputStream = new FileInputStream(file);
        Workbook workbook = new XSSFWorkbook(inputStream);
}

WorkBook

通过WorkBook可以获取的信息

集合属性相关的方法获取到的内容关联的方法
getNumberOfSheets()sheet的数量getSheet(String)/getSheetAt(int)
getNumberOfNames()范围为WorkBook级别的名称数量(不清楚名称是什么?详细见上)getAllNames()
getNumCellStyles()单元格样式数量getCellStypeAt(int)
getNumberOfFonts()字体样式数量getFontAt(int)
getAllNames()名称集合(范围为WorkBook级别的)
getAllPictures()图片集合,与Names不同,所有插入图片都归WorkBook管
基础属性相关的方法获取到的内容
getSpreadsheetVersion()excel文件的版本
getActiveSheetIndex()最后打开sheet的索引(可以切换一下Sheet,然后跑程序,看看是不是发生了变化)
getMissingCellPolicy()找不到Cell单元格的策略,默认:RETURN_NULL_AND_BLANK
getForceFormulaRecalculation()公式是否重新计算,或者沿用之前运算的结果
getFirstVisibleTab()第一个可见Sheet的索引(每个Sheet是有一个隐藏属性的)
getPrintArea(int)获取Sheet索引对应的Sheet的打印区域
字体信息
        int numberOfFonts = workbook.getNumberOfFonts();
        for (int i = 0; i < numberOfFonts; i++) {
            Font fontAt = workbook.getFontAt(i);
            log.info("""  
                            索引:{}
                            字体信息:{},颜色:{},字符集:{},类型偏移:{},
                            是否加粗:{},是否斜体:{},是否下划线:{},是否删除线:{},
                            字体高度:{},字体高度(磅):{}
                            """,
                    fontAt.getIndex(),
                    fontAt.getFontName(), fontAt.getColor(), fontAt.getCharSet(), fontAt.getTypeOffset(),
                    fontAt.getBold(), fontAt.getItalic(), fontAt.getUnderline(), fontAt.getStrikeout(),
                    fontAt.getFontHeight(), fontAt.getFontHeightInPoints());
        }

image.png

名字信息
        workbook.getAllNames().forEach((name -> {
            log.info("name:{}", name);
            log.info("""
                    名字:{},备注:{},
                    所在sheet索引:{},所在sheet名称:{},
                    引用公式:{}
                    """, name.getNameName(), name.getComment(), name.getSheetIndex(), name.getSheetName(), name.getRefersToFormula());
        }));

image.png

单元格风格信息
    int styleLength = workbook.getNumCellStyles();
        for (int i = 0; i < styleLength; i++) {
            CellStyle cellStyle = workbook.getCellStyleAt(i);
            log.info("""
                   索引:{},字体索引:{},
                   对齐方式:{},垂直对齐方式:{},
                   填充模式:{},前景色:{},背景色:{},
                   边框左:{},右边:{},上边:{},下边:{},
                   边框颜色左:{},右边:{},上边:{},下边:{},
                   痕入:{},旋转:{},
                   是否换行:{},是否收缩:{},是否锁定:{},是否隐藏:{},是否引用前缀:{},
                   日期格式:{},日期格式字符串:{} 
                    """,
                    cellStyle.getIndex(), cellStyle.getFontIndex(),
                    cellStyle.getAlignment(), cellStyle.getVerticalAlignment(),
                    cellStyle.getFillPattern(),  cellStyle.getFillForegroundColor(), cellStyle.getFillBackgroundColor(),
                    cellStyle.getBorderLeft(), cellStyle.getBorderRight(), cellStyle.getBorderTop(), cellStyle.getBorderBottom(),
                    cellStyle.getLeftBorderColor(), cellStyle.getRightBorderColor(), cellStyle.getTopBorderColor(), cellStyle.getBottomBorderColor(),
                    cellStyle.getIndention(), cellStyle.getRotation(),
                    cellStyle.getWrapText(), cellStyle.getShrinkToFit(), cellStyle.getLocked(), cellStyle.getHidden(), cellStyle.getQuotePrefixed(),
                    cellStyle.getDataFormat(),cellStyle.getDataFormatString()
            );
        }

image.png

图片信息
        workbook.getAllPictures().forEach((pictureData -> {
            log.info("pictureData:{}", pictureData);
            log.info("""
                            图片类型:{},Mime类型:{},图片扩展名:{},
                            图片数据:{}
                            """,
                    pictureData.getPictureType(),
                    pictureData.getMimeType(),
                    pictureData.suggestFileExtension(),
                    pictureData.getData()
            );
        }));

image.png

Sheet

我们可以使用workBook.getSheetAt(int)或者workBook.getSheet(String)方法通过索引或者名称获取到一个sheet,然后就可以获取到以下属性和方法。

基础属性内容
LeftCol,TopRow值默认为0,表示可视区域列、行的索引从0开始
PhysicalNumberOfRows实际拥有数据的行数
DefaultColumnWidth,DefaultRowHeight默认列宽,行高
NumMergedRegions合并单元格的数量
ActiveCell激活的单元,Excel文件除了记录上一次打开的sheet索引外,还会记录每一个sheet最后停留的cell
ForceFormulaRecalculation是否重新记录本sheet中涉及的公式重新运算(workbook的这项属性范围更大)
Protect是否对工作表进行保护
ScenarioProtect是否对本工作表的方案进行保护,不清楚什么是方案,见上文描述!
Header、Footer页眉、页脚

基本属性省略了很多不影响对sheet本身工作机制理解的属性,如水平居中属性等样式以及是否展示水平线之类的布尔值等。 上面仅记录一些比较重要或者需要理解的属性。

集合属性相关的方法内容
getSheetConditionalFormatting()条件格式集合,不清楚的话,见上文描述!
getCellComments()单元格批注集合
getHyperLinkList()超链接集合
getDataValidations()数据验证
getDrawingPatriarch()图形绘制集合,与workbook的pictures集合相关
getMergedRegions()单元格合并区域集合
getColumnBreaks(),getRowBreaks()设置分页的行/列信息集合
getRepeatingRows(),getRepeatingColumns()用于打印的行/列集合,多页的每一页数据打印都包含此行/列
基本属性

        log.info("""
                        TopRow:{},LeftCol:{},
                        physicalNumberOfRows:{},numMergedRegions:{},
                        defaultColumnWidth:{},defaultRowHeight:{},defaultRowHeightInPoints:{},
                        firstRowNum:{},lastRowNum:{},
                        rowSumsBelow:{},rowSumsRight:{},
                        verticallyCenter:{},horizontallyCenter:{},fitToPage:{},
                        autobreaks:{},displayGuts:{},
                        forceFormulaRecalculation:{},
                        protect:{},scenarioProtect:{},
                        isDisplayZeros:{},isDisplayFormulas:{},isDisplayGridlines:{},
                        isPrintGridlines:{},isPrintRowAndColumnHeadings:{},
                        isSelected:{},isDisplayRowColHeadings:{},
                        isRightToLeft:{}
                          """,
                sheet.getTopRow(), sheet.getLeftCol(),
                sheet.getPhysicalNumberOfRows(), sheet.getNumMergedRegions(),
                sheet.getDefaultColumnWidth(), sheet.getDefaultRowHeight(), sheet.getDefaultRowHeightInPoints(),
                sheet.getFirstRowNum(), sheet.getLastRowNum(),
                sheet.getRowSumsBelow(), sheet.getRowSumsRight(),
                sheet.getVerticallyCenter(), sheet.getHorizontallyCenter(), sheet.getFitToPage(),
                sheet.getAutobreaks(), sheet.getDisplayGuts(),
                sheet.getForceFormulaRecalculation(),
                sheet.getProtect(), sheet.getScenarioProtect(),
                sheet.isDisplayZeros(),sheet.isDisplayFormulas(),sheet.isDisplayGridlines(),
                sheet.isPrintGridlines(),sheet.isPrintRowAndColumnHeadings(),
                sheet.isSelected(),sheet.isDisplayRowColHeadings(),
                sheet.isRightToLeft()
        );

image.png

列分页符ColumnBreaks
        for (int i = 0; i < sheet.getColumnBreaks().length; i++) {
            int columnBreak = sheet.getColumnBreaks()[i];
            log.info("columnBreak:{}", columnBreak);
        }

image.png

单元格评论CellComments
       sheet.getCellComments().forEach(((cellAddress, comment) -> {
            log.info("""
                            cellAddress:{},
                            批注内容:{},
                            批注作者:{},{}
                            """,
                    cellAddress,
                    comment.getString(),
                    comment.getAuthor(), comment.getClientAnchor()
            );
        }));

image.png

超链接HyperLinkList
        sheet.getHyperlinkList().forEach((hyperlink -> {
            log.info("hyperlink:{}", hyperlink);
            log.info("""
                            地址:{}, 地址类型:{},
                            标签:{},
                            起始行:{},结束行:{},
                            起始列:{},结束列:{}
                            """,
                    hyperlink.getAddress(), hyperlink.getType(),
                    hyperlink.getLabel(),
                    hyperlink.getFirstRow(), hyperlink.getLastRow(),
                    hyperlink.getFirstColumn(), hyperlink.getLastColumn()
            );
        }));

image.png

数据验证DataValidations
  sheet.getDataValidations().forEach((dataValidation -> {
            log.info("dataValidation:{}", dataValidation);
            log.info("""
                            promptBoxTitle:{},promptBoxText:{},showPromptBox:{},
                            errorBoxTitle:{},errorBoxText:{},showErrorBox:{},errorStyle:{},
                            suppressDropDownArrow:{},emptyCellAllowed:{}
                            """,
                    dataValidation.getPromptBoxTitle(), dataValidation.getPromptBoxText(), dataValidation.getShowPromptBox(),
                    dataValidation.getErrorBoxTitle(), dataValidation.getErrorBoxText(), dataValidation.getShowErrorBox(), dataValidation.getErrorStyle(),
                    dataValidation.getSuppressDropDownArrow(), dataValidation.getEmptyCellAllowed()
            );
            CellRangeAddressList regions = dataValidation.getRegions();
            regions.getGenericChildren().forEach((region -> {
                log.info(
                        """
                                起始行:{},结束行:{},
                                起始列:{},结束列:{},
                                单元格数:{},格式文本:{}                       
                                   """,
                        region.getFirstRow(), region.getLastRow(),
                        region.getFirstColumn(), region.getLastColumn(),
                        region.getNumberOfCells(), region.formatAsString()
                );
            }));
            DataValidationConstraint constraint = dataValidation.getValidationConstraint();
            log.info("""
                    operator:{},validationType:{},
                    formula1:{},formula2:{},
                    explicitListValues:{}
                    """,
                    constraint.getOperator(), constraint.getValidationType(),
                    constraint.getFormula1(),constraint.getFormula2(),
                    constraint.getExplicitListValues()
                   );
        }));

image.png

图片绘制DrawingPatriarch
    Drawing<?> drawingPatriarch = sheet.getDrawingPatriarch();
        drawingPatriarch.forEach((shape -> {
            log.info("shape:{}", shape);
            log.info("""
                            父:{},
                            锚点:{},
                            文本:{}
                            """,
                    shape.getParent(),
                    shape.getAnchor(),
                    shape.getShapeName()
            );
        }));

image.png

PrintSetup打印设置
    PrintSetup printSetup = sheet.getPrintSetup();
        log.info("printSetup:{}", printSetup);
        log.info("""
                        水平分辨率:{},缩放比例:{},
                        高度:{},宽度:{},
                        页脚边距:{},页眉边距:{},
                        打印份数:{},纸张大小:{},
                        起始页:{},使用页:{},草稿:{},
                        横向:{},备注:{},
                        有效设置:{},无颜色:{},无方向:{},
                        从左到右:{}
                """,
                printSetup.getHResolution(),printSetup.getScale(),
                printSetup.getFitHeight(),printSetup.getFitWidth(),
                printSetup.getFooterMargin(),printSetup.getHeaderMargin(),
                printSetup.getCopies(),
                printSetup.getPaperSize(),printSetup.getPageStart(),
                printSetup.getUsePage(),printSetup.getDraft(),printSetup.getLandscape(),printSetup.getNotes(),
                printSetup.getValidSettings(),printSetup.getNoColor(), printSetup.getNoOrientation(),printSetup.getLeftToRight()
        );

image.png

Cell

获取到一个Cell单元格:sheet.getRow(int)->row.getCell(int)

        int firstRowNum = sheet.getFirstRowNum();
        int lastRowNum = sheet.getLastRowNum();
        for (int i = firstRowNum; i <= lastRowNum; i++) {
            Row row = sheet.getRow(i);
            log.info("第{}行===================", i);
            int rowNum = row.getRowNum();
            for (int j = 0; j <= rowNum; j++) {
                Cell cell = row.getCell(j);
                log.info("cell[{}]:{}", j, cell);
            }
        }

属性
  • 确定是什么类型的值时就获取什么类型的值,不然就会报错!
              log.info("""
                                类型:{},地址:{},批注:{},
                                """,
//                                    缓存类型:{},
//                                     公式:{},数组公式{}:
//                                     超链接:{},
//                                     字符串:{},富文本:{},
//                                     数字:{},布尔:{},错误:{},
//                                     日期:{},日期时间:{}
//                                     """,
                        cell.getCellType(), cell.getAddress(),
                        cell.getCellComment()
                        //                        ,cell.getCachedFormulaResultType(),
                        //                        cell.getCellFormula(),
                        //                        cell.getArrayFormulaRange(),
//                            cell.getHyperlink(),
//                            cell.getStringCellValue(), cell.getRichStringCellValue(),
//                            cell.getNumericCellValue(), cell.getBooleanCellValue(), cell.getErrorCellValue(),
//                            cell.getDateCellValue(), cell.getLocalDateTimeCellValue()
                );

POI- EasyExcel的底层框架

认识了上面POI的API,我们接下来学EasyExcel也会更加轻松,也能够更加灵活地去使用和拓展EasyExcel。

最后分享一下EasyExcel比较好的文章。

站内大佬@竹子爱熊猫 的个人主页 - 动态 - 掘金详细分析了EasyExcel的功能原理,一共有五篇文章,下面贴两篇。

(一)EasyExcel初相识:万字吃透EasyExcel核心API,从此报表处理不再喊难!POI是报表处理领域的知名框 - 掘金

(四)并发编程实战:多线程+EasyExcel,20ms内极速导入百万级大数据报表!什么?工作好些年了还不会用多线程?接 - 掘金