开发过程当中常常会遇到 Excel 导出的状况,尤为是在企业开发中,涉及到客户信息、财务报表、市场分析等,情景很是多。日常开发过程当中大多都会针对每一个导出单独写一套代码,随着导出愈来愈多,内心便想:有没有一个足够通用东西可让咱们不用写这么多代码来实现 Excel 导出?前端
带着这个问题便开始了本身的“ExcelUtil”之路,在这过程当中主要接触过 easypoi,但仍是不太知足。由于 easypoi 和大多数 Java 库同样:基于字段写配置。固然不是说这个很差,有不少库都这样,好比 fastjson、Jackson 等都是在字段上写注解,描述这个字段有些什么信息或做用等。但对于 Excel 导出,我总以为还有更加通用的方式。vue
通过一段时间的摸索和发掘,在前端的 table 标签上找到了灵感,认为这个方式很好、很是好。table 标签自己包含了不少描述信息,像行、列、合并行、合并列这些与 excel 的 sheet 页“惊人的类似”,再加上近几年前端三大框架的大力发展,尤为是 angular 和 vue 这两个框架在标签上自定义属性的方式进一步让我在写 ExcelUtil 过程当中获得了很多启发。java
ExcelUtil 和 RunnerUtil(GitHub) 同样,大概是在今年 5 到 6 月写的,最近又从新整理了一下,已上传 GitHub # ExcelUtil (记得 star 哈!)。git
ExcelUtil 根据 excel 文件、sheet 页、row 行、cell 单元格这样的层次结构分别定义了本身的做用域,每一个做用域内能够必定程度上自定义变量等,做用域之间互不影响,同名变量下层做用域等声明优先于上层做用域等这些与 java、JavaScript 等语言的做用域结构一致。github
不一样的是 ExcelUtil 使用频率比 RunnerUtil 频率高不少,写 RunnerUtil 的初衷也是为了这个 ExcelUtil 导出,最开始想到了 Java 内置脚本引擎(ScriptEngine),但内置脚本引擎的效率实在过低,数据量稍微大一点(不用太大)状况下直接卡死(不应这么吐槽的,但的确不适合这个场景)。可是 RunnerUtil 的功能独立且完善,性能良好,能够运行各类复杂的 Java 字符串公式,彻底能够单独使用。面试
使用 ExcelUtil 的以前首先要准备的就是数据,数据并无特殊的格式要求,能够是任意 Java 类型数据,如 Collection、Iterable、Iterator(迭代器模式可,这是在一次面试时获得的启发,可用于超大 Excel 导出,虽而后来没经过,但仍然很感谢那位面试官!)、Map、数组、POJO、Number等。编程
第二步是生成 Workbook 位置的方法上进行“注解编程” —— 对的,Java 的注解功能很强大,能够在 Java 内部又单独做为 Java 内的“编程语言”(其实就是写了个简单的解析器而言,捂脸一笑)。json
// 在什么地方导出,就在那个方法上进行声明式“注解编程”
// 首先要声明这是一个 Excel,用 type 指定是 xls 或者 xlsx
@TableExcel(type = TableExcel.Type.XLS, value = {
/* * value 包含的是全部 sheet 页的信息 * 自 sheet 向下,每一个标签能够判断、循环等 * 用 sheetName 指定 sheet 名 * 为何要用单引号再多包裹一层呢?详见 RunnerUtil * 由于这里面的全部内容都是用 RunnerUtil 解析的,须要符合它的格式 */
@TableSheet(sheetName = "'人员信息'", value = {
/* * 在这儿声明了一个名为 names 的数组,用做标题 */
@TableRow(var = "names = {'序号','姓名','性别','年龄','电话','家庭住址', '备注'}", value = {
/* * 这儿用了迭代,迭代 row 上声明的 names * 这个迭代将按 names 的内容生成对应数量和内容的 cell 单元格 */
@TableCell(var = "name:names", value = name)
}),
/* * 上面 cell 的迭代用的是冒号,这儿用了 in,两者意义彻底同样 * 支持 in 彻底是为了向灵感的来源(前端)致敬 * 可是 in 并非关键字,仍可做为普通变量 * 不一样的是 in 的两端至少各有一个空格 * 可迭代的数据类型一下子详细介绍 */
@TableRow(var = "($rowData, index) in collect", value = {
@TableCell("index + 1"), // 序号
@TableCell("$rowData.name"), // 姓名
@TableCell("$rowData.sex"), // 性别
@TableCell("$rowData.age"), // 年龄
@TableCell("$rowData.mobile"), // 电话
@TableCell("$rowData.address"), // 家庭住址
// 最后这个对于上面的备注,这儿有个 when,只有 index == 0 才建立这个单元格
// 同时这儿还用到了并合并行,另外 colspan 是合并列
@TableCell(when = "index == 0", rowspan = "data.size()")
})
})
})
public Workbook exportExcel(Object data){
/* * 写好注解后只须要调用这个方法即可获得一个 Workbook * 在哪儿调用 render 方法就在哪儿写上面那些注解 */
return ExcelUtil.render(data);
}
复制代码
贴一个本工具导出的 10 列 Excel 的性能测试表(本机环境 i7-8700K 16G Win10)数组
行数(万行) | 生成数据耗时(ms) | write到文件耗时(ms) | 总耗时(ms) |
---|---|---|---|
100 | 6,182 | 5,565 | 11,747 |
300 | 14,800 | 16,693 | 31,493 |
500 | 25,876 | 27,317 | 53,193 |
700 | 36,121 | 42,171 | 78,292 |
999 | 53,532 | 54,745 | 108,277 |
4000 | 240,453 | 271,832 | 512,285 |
6000 | 366,987 | 423,351 | 790,338 |
8000 | 528,654 | 498,490 | 1,027,144 |
从这个数据能够看出,随着数据量增长,时间与数据的关系呈正相关性,接近线性关系,100 万行数据生成 Workbook 耗时 6s,总耗时 12s,在正常业务场景下能知足时间的要求。框架