看完本文,你一定會有所收穫
一、介紹
在上篇文章中,我們簡單的介紹了 excel 匯入匯出技術實踐方案,就目前而已,使用最多的開源框架主要有以下三類,分別是:
apache poi:poi是使用最廣的一種匯入匯出框架,但是缺點也很明顯,匯出大資料量的時候,容易oom
easypoi:easypoi 的底層也是基於 apache poi 進行深度開發的,它主要的特點就是將更多重複的工作,全部簡單化,避免編寫重複的程式碼,最顯著的特點就是匯出的支援非常豐富
easyexcel:easyexcel 是阿里巴巴開源的一款 excel 解析工具,底層邏輯也是基於 apache poi 進行二次開發的,目前的應用也非常廣
總的來說,easypoi 和 easyexcel 都是基於apache poi進行二次開發的。
不同點在於:
1、easypoi 在讀寫資料的時候,優先是先將資料寫入記憶體,因此讀寫效能非常高,這種操作平時使用的時候不會出現什麼問題,但是當資料量很大的時候,會出現 oom,當然它也提供了 sax 模式一行一行解析,需要自己根據當前場景來實現。
2、easyexcel 預設基於 sax 模式一行一行解析,明顯降低了記憶體,不會出現 oom 情況,程式有過高併發場景的驗證,因此整體執行比較穩定,相對於 easypoi 來說,讀寫效能稍慢!
3、easypoi 的 api 非常豐富,easyexcel 功能的支援,比較簡單。
就小編的實際使用情況來看,easypoi 相比 easyexcel 而言,有很多的優點,尤其是他的 api 非常豐富,但是在實際使用過程中,發現在匯入幾千條資料的時候,有時容易發生異常,尤其是當老闆使用的時候,突然蹦出這麼一個異常,這個時候是沒辦法容忍的。
但是當改用成 easyexcel 的時候,不會出現這個問題,因此如果你經常要匯入的資料量非常大,那麼我推薦你使用 easyexcel。
今天,我們就以 easyexcel 框架為例,結合實際開發案例,給大家詳細介紹一下 easyexcel 的使用,再下篇文章中,我們再來介紹 easypoi,可能也有理解不到位的地方,歡迎網友們批評指出!
二、程式例項
2。1、新增依賴包
2。2、匯出 excel
easyexcel 的匯出支援兩種方式,一種是透過實體類註解方式來生成檔案,另一種是透過動態引數化生成檔案。
2。2。1、實體類註解方式生成檔案
實體類註解方式生成檔案,操作非常簡單,只需要在對應的屬性欄位上新增
@ExcelProperty
註解,然後填寫列名,配置就完成了,示例程式碼如下:
public class UserEntity { @ExcelProperty(value = “姓名”) private String name; @ExcelProperty(value = “年齡”) private int age; @DateTimeFormat(“yyyy-MM-dd HH:mm:ss”) @ExcelProperty(value = “操作時間”) private Date time; //set、get。。。}
public static void main(String[] args) throws FileNotFoundException { List
執行程式,開啟檔案內容結果!
2。2。2、動態引數化生成檔案
動態引數化生成檔案,這種方式小編使用的比較多,基於它,我們可以封裝一個公共的匯出工具類,在後面會單獨介紹給大家,示例程式碼如下:
public static void main(String[] args) throws FileNotFoundException { //定義表頭 List> headList = new ArrayList<>(); headList。add(Lists。newArrayList(“姓名”)); headList。add(Lists。newArrayList(“年齡”)); headList。add(Lists。newArrayList(“操作時間”)); //定義資料體 List
> dataList = new ArrayList<>(); for (int i = 0; i < 10; i++) { List
執行程式,開啟檔案內容,結果與上面一致!
2。2。3、複雜表頭的生成
很多時候我們需要匯出的檔案,表頭比較複雜,例如,我們想匯出如下圖這樣一個複雜表頭,應該如何實現呢?
如果你是使用在實體類上添加註解方式生成檔案,那麼可以透過如下方式來實現:
public class UserEntity { @ExcelProperty(value = “班級”) private String className; @ExcelProperty({“學生資訊”, “姓名”}) private String name; @ExcelProperty({“學生資訊”, “年齡”}) private int age; @DateTimeFormat(“yyyy-MM-dd HH:mm:ss”) @ExcelProperty({“學生資訊”, “入學時間”}) private Date time; //set、get。。。}
其中
{“學生資訊”, “姓名”}
這種表示式,表示在當前列,插入多行資料,第一行插入的是
學生資訊
名稱,第二行,插入的是
姓名
名稱,因此形成多級表頭!
如果你是使用的動態引數化生成檔案,操作也同樣類似,示例程式碼如下:
public static void main(String[] args) throws FileNotFoundException { //定義多級表頭 List> headList = new ArrayList<>(); headList。add(Lists。newArrayList(“班級”)); headList。add(Lists。newArrayList(“學生資訊”, “姓名”)); headList。add(Lists。newArrayList(“學生資訊”,“年齡”)); headList。add(Lists。newArrayList(“學生資訊”,“入學時間”)); //定義資料體 List
> dataList = new ArrayList<>(); for (int i = 0; i < 10; i++) { List
其中
Lists。newArrayList(“學生資訊”, “姓名”)
表達的意思跟上面一樣,在當前列下插入多行,類似於:
List
Lists。newArrayList
程式設計來自於
guava
工具包!
2。2。4、自定義樣式
在實際使用過程中,我們可能還需要針對檔案做一下樣式自定義,例如你想把表頭設定為紅色,內容設定為綠色,列寬、行寬都加大,應該如何實現呢?
操作也很簡單,編寫一個自定義樣式類,然後在寫入的時候注入進去。
/** * 自定義樣式 * @return */private static HorizontalCellStyleStrategy customerStyle(){ // 頭的策略 WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 背景設定為紅色 headWriteCellStyle。setFillForegroundColor(IndexedColors。RED。getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont。setFontHeightInPoints((short)20); headWriteCellStyle。setWriteFont(headWriteFont); // 內容的策略 WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); // 這裡需要指定 FillPatternType 為FillPatternType。SOLID_FOREGROUND 不然無法顯示背景顏色。頭默認了 FillPatternType所以可以不指定 contentWriteCellStyle。setFillPatternType(FillPatternType。SOLID_FOREGROUND); // 背景綠色 contentWriteCellStyle。setFillForegroundColor(IndexedColors。GREEN。getIndex()); WriteFont contentWriteFont = new WriteFont(); // 字型大小 contentWriteFont。setFontHeightInPoints((short)20); contentWriteCellStyle。setWriteFont(contentWriteFont); // 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現 HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); return horizontalCellStyleStrategy;}
在寫入的時候,將其注入,例如下面的動態匯出:
//透過registerWriteHandler方法,將自定義的樣式類注入進去EasyExcel。write(outputStream)。registerWriteHandler(customerStyle())。head(headList)。sheet(“使用者資訊”)。doWrite(dataList);
2。3、匯入 excel
easyexcel 的匯入同樣也支援兩種方式,和上面一樣,一種是透過實體類註解方式來讀取檔案,另一種是透過動態監聽器讀取檔案。
2。3。1、實體類註解方式來讀取檔案
實體類註解方式來讀取檔案時,要讀取的 excel 表頭需要與實體類一一對應,以下面的 excel 檔案為例!
透過註解方式來讀取,既可以指定列的下表,也可以透過列名來對映,但是兩者只能取一個。
/** * 讀取實體類 */public class UserReadEntity { @ExcelProperty(value = “姓名”) private String name; /** * 強制讀取第三個 這裡不建議 index 和 name 同時用,要麼一個物件只用index,要麼一個物件只用name去匹配 */ @ExcelProperty(index = 1) private int age; @DateTimeFormat(“yyyy-MM-dd HH:mm:ss”) @ExcelProperty(value = “操作時間”) private Date time; //set、get。。。}
public static void main(String[] args) throws FileNotFoundException { //同步讀取檔案內容 FileInputStream inputStream = new FileInputStream(new File(“/Users/panzhi/Documents/easyexcel-user1。xls”)); List
執行程式,輸出結果如下:
[{“age”:20,“name”:“張三0”,“time”:1616920360000},{“age”:21,“name”:“張三1”,“time”:1616920360000},{“age”:22,“name”:“張三2”,“time”:1616920360000},{“age”:23,“name”:“張三3”,“time”:1616920360000},{“age”:24,“name”:“張三4”,“time”:1616920360000},{“age”:25,“name”:“張三5”,“time”:1616920360000},{“age”:26,“name”:“張三6”,“time”:1616920360000},{“age”:27,“name”:“張三7”,“time”:1616920360000},{“age”:28,“name”:“張三8”,“time”:1616920360000},{“age”:29,“name”:“張三9”,“time”:1616920360000}]
2。3。2、動態監聽器讀取檔案
動態監聽器讀取檔案,與上面的方式有一個明顯的區別是,我們需要重新寫一個實現類,來監聽 easyexcel 一行一行解析出來的資料,然後將資料封裝出來,基於此,我們可以編寫一套動態的匯入工具類,詳細工具類會下面介紹到,示例程式碼如下:
/** * 建立一個監聽器,繼承自AnalysisEventListener */public class UserDataListener extends AnalysisEventListener
public static void main(String[] args) throws FileNotFoundException { FileInputStream inputStream = new FileInputStream(new File(“/Users/panzhi/Documents/easyexcel-user1。xls”)); //初始化一個監聽器 UserDataListener userDataListener = new UserDataListener(); //讀取檔案資料 EasyExcel。read(inputStream, userDataListener)。sheet()。doRead(); System。out。println(“表頭:” + JSONArray。toJSONString(userDataListener。getHeadList())); System。out。println(“資料體:” + JSONArray。toJSONString(userDataListener。getDataList()));}
執行程式,輸出結果如下:
表頭:[{0:“姓名”,1:“年齡”,2:“操作時間”}]資料體:[{0:“張三0”,1:“20”,2:“2021-03-28 16:32:40”},{0:“張三1”,1:“21”,2:“2021-03-28 16:32:40”},{0:“張三2”,1:“22”,2:“2021-03-28 16:32:40”},{0:“張三3”,1:“23”,2:“2021-03-28 16:32:40”},{0:“張三4”,1:“24”,2:“2021-03-28 16:32:40”},{0:“張三5”,1:“25”,2:“2021-03-28 16:32:40”},{0:“張三6”,1:“26”,2:“2021-03-28 16:32:40”},{0:“張三7”,1:“27”,2:“2021-03-28 16:32:40”},{0:“張三8”,1:“28”,2:“2021-03-28 16:32:40”},{0:“張三9”,1:“29”,2:“2021-03-28 16:32:40”}]
其中
key
表示列下表!
2。3。3、複雜表頭讀取
在實際的開發中,我們還會遇到複雜表頭的資料讀取,以如下表頭為例,我們應該如何讀取呢?
如果你是採用註解的方式匯出的檔案,同樣也可以透過註解方式來讀取,例如上文中,我們是使用如下實體類生成的檔案,我們也可透過這個類讀取檔案!
public class UserEntity { @ExcelProperty(value = “班級”) private String className; @ExcelProperty({“學生資訊”, “姓名”}) private String name; @ExcelProperty({“學生資訊”, “年齡”}) private int age; @DateTimeFormat(“yyyy-MM-dd HH:mm:ss”) @ExcelProperty({“學生資訊”, “入學時間”}) private Date time; //set、get}
//讀取檔案List
讀取結果如下:
[{“age”:20,“className”:“一年級~1班”,“name”:“張三0”,“time”:1618719961000},{“age”:21,“className”:“一年級~1班”,“name”:“張三1”,“time”:1618719961000},{“age”:22,“className”:“一年級~1班”,“name”:“張三2”,“time”:1618719961000},{“age”:23,“className”:“一年級~1班”,“name”:“張三3”,“time”:1618719961000},{“age”:24,“className”:“一年級~1班”,“name”:“張三4”,“time”:1618719961000},{“age”:25,“className”:“一年級~1班”,“name”:“張三5”,“time”:1618719961000},{“age”:26,“className”:“一年級~1班”,“name”:“張三6”,“time”:1618719961000},{“age”:27,“className”:“一年級~1班”,“name”:“張三7”,“time”:1618719961000},{“age”:28,“className”:“一年級~1班”,“name”:“張三8”,“time”:1618719961000},{“age”:29,“className”:“一年級~1班”,“name”:“張三9”,“time”:1618719961000}]
如果你是使用動態引數化來生成檔案,那麼這個時候可以採用動態監聽器的方式來讀取檔案,在讀取的時候需要指定資料所在行,示例程式碼如下:
public static void main(String[] args) throws FileNotFoundException { FileInputStream inputStream = new FileInputStream(new File(“/Users/panzhi/Documents/easyexcel-export-user4。xlsx”)); //初始化一個監聽器 UserDataListener userDataListener = new UserDataListener(); //讀取檔案資料,指定資料所在行使用headRowNumber方法 EasyExcel。read(inputStream, userDataListener)。sheet()。headRowNumber(2)。doRead(); System。out。println(“表頭:” + JSONArray。toJSONString(userDataListener。getHeadList())); System。out。println(“資料體:” + JSONArray。toJSONString(userDataListener。getDataList()));}
讀取結果如下:
表頭:[{0:“班級”,1:“學生資訊”,2:“學生資訊”,3:“學生資訊”},{0:“班級”,1:“姓名”,2:“年齡”,3:“入學時間”}]資料體:[{0:“一年級~1班”,1:“張三0”,2:“20”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三1”,2:“21”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三2”,2:“22”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三3”,2:“23”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三4”,2:“24”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三5”,2:“25”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三6”,2:“26”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三7”,2:“27”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三8”,2:“28”,3:“2021-04-18 12:26:01”},{0:“一年級~1班”,1:“張三9”,2:“29”,3:“2021-04-18 12:26:01”}]
三、動態匯出匯入工具類封裝
在實際使用開發中,我們不可能每來一個 excel 匯入匯出需求,就編寫一個方法,而且很多業務需求都是動態匯入匯出,沒辦法基於實體類註解的方式來讀取檔案或者寫入檔案
因此,基於動態引數化生成檔案和動態監聽器讀取檔案方法,我們可以單獨封裝一套動態匯出匯出工具類,省的我們每次都需要重新編寫大量重複工作,以下就是小編我在實際使用過程,封裝出來的工具類,在此分享給大家!
動態匯出工具類
public class DynamicEasyExcelExportUtils { private static final Logger log = LoggerFactory。getLogger(DynamicEasyExcelExportUtils。class); private static final String DEFAULT_SHEET_NAME = “sheet1”; /** * 動態生成匯出模版(單表頭) * @param headColumns 列名稱 * @return excel檔案流 */ public static byte[] exportTemplateExcelFile(List> excelHead = Lists。newArrayList(); headColumns。forEach(columnName -> { excelHead。add(Lists。newArrayList(columnName)); }); byte[] stream = createExcelFile(excelHead, new ArrayList<>()); return stream; } /** * 動態生成模版(複雜表頭) * @param excelHead 列名稱 * @return */ public static byte[] exportTemplateExcelFileCustomHead(List
> excelHead){ byte[] stream = createExcelFile(excelHead, new ArrayList<>()); return stream; } /** * 動態匯出檔案 * @param headColumnMap 有序列頭部 * @param dataList 資料體 * @return */ public static byte[] exportExcelFile(LinkedHashMap
> excelHead = new ArrayList<>(); if(MapUtils。isNotEmpty(headColumnMap)){ //key為匹配符,value為列名,如果多級列名用逗號隔開 headColumnMap。entrySet()。forEach(entry -> { excelHead。add(Lists。newArrayList(entry。getValue()。split(“,”))); }); } List
> excelRows = new ArrayList<>(); if(MapUtils。isNotEmpty(headColumnMap) && CollectionUtils。isNotEmpty(dataList)){ for (Map
動態匯入工具類
/** * 建立一個監聽器 */public class DynamicEasyExcelListener extends AnalysisEventListener
/** * 編寫匯入工具類 */public class DynamicEasyExcelImportUtils { /** * 動態獲取全部列和資料體,預設從第一行開始解析資料 * @param stream * @return */ public static List
為了方便後續的操作流程,在解析資料的時候,會將列名作為
key
!
四、總結
本文主要以實際使用場景為例,對 easyexcel 的使用做了簡單的介紹,尤其是動態匯出匯出,基於業務的需要,做了一個公共的工具類,方便後續進行快速開發,避免重複的勞動!
當然,easyexcel 的功能還不只上面介紹的那些內容,還有基於模版進行excel的填充,web 端restful的匯出匯出,使用方法大致都差不多,具體可以參與官方的文件,地址如下:
https://www。yuque。com/easyexcel/doc/read#1bfaf593
最後,希望本文對大家有所幫助!