開發過程當中常常會遇到 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,在正常業務場景下能知足時間的要求。markdown