你要的不固定列Excel导入导出,它来啦!

开发 开发工具
今天,我们就以 easyexcel 框架为例,结合实际开发案例,给大家详细介绍一下 easyexcel 的使用,再下篇文章中,我们再来介绍 easypoi,可能也有理解不到位的地方,欢迎网友们批评指出!

[[394269]]

看完本文,你一定会有所收获

一、介绍

在上篇文章中,我们简单的介绍了 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、添加依赖包

  1. <dependency> 
  2.     <groupId>com.alibaba</groupId> 
  3.     <artifactId>easyexcel</artifactId> 
  4.     <version>2.2.6</version> 
  5. </dependency> 

2.2、导出 excel

easyexcel 的导出支持两种方式,一种是通过实体类注解方式来生成文件,另一种是通过动态参数化生成文件。

2.2.1、实体类注解方式生成文件

实体类注解方式生成文件,操作非常简单,只需要在对应的属性字段上添加@ExcelProperty注解,然后填写列名,配置就完成了,示例代码如下:

  1. public class UserEntity { 
  2.  
  3.     @ExcelProperty(value = "姓名"
  4.     private String name
  5.  
  6.     @ExcelProperty(value = "年龄"
  7.     private int age; 
  8.  
  9.     @DateTimeFormat("yyyy-MM-dd HH:mm:ss"
  10.     @ExcelProperty(value = "操作时间"
  11.     private Date time
  12.   
  13.  //set、get... 
  14. public static void main(String[] args) throws FileNotFoundException { 
  15.     List<UserEntity> dataList = new ArrayList<>(); 
  16.     for (int i = 0; i < 10; i++) { 
  17.         UserEntity userEntity = new UserEntity(); 
  18.         userEntity.setName("张三" + i); 
  19.         userEntity.setAge(20 + i); 
  20.         userEntity.setTime(new Date(System.currentTimeMillis() + i)); 
  21.         dataList.add(userEntity); 
  22.     } 
  23.  //定义文件输出位置 
  24.     FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user1.xlsx")); 
  25.     EasyExcel.write(outputStream, UserEntity.class).sheet("用户信息").doWrite(dataList); 

运行程序,打开文件内容结果!

2.2.2、动态参数化生成文件

动态参数化生成文件,这种方式小编使用的比较多,基于它,我们可以封装一个公共的导出工具类,在后面会单独介绍给大家,示例代码如下:

  1. public static void main(String[] args) throws FileNotFoundException { 
  2.     //定义表头 
  3.     List<List<String>> headList = new ArrayList<>(); 
  4.     headList.add(Lists.newArrayList("姓名")); 
  5.     headList.add(Lists.newArrayList("年龄")); 
  6.     headList.add(Lists.newArrayList("操作时间")); 
  7.  
  8.     //定义数据体 
  9.     List<List<Object>> dataList = new ArrayList<>(); 
  10.     for (int i = 0; i < 10; i++) { 
  11.         List<Object> data = new ArrayList<>(); 
  12.         data.add("张三" + i); 
  13.         data.add(20 + i); 
  14.         data.add(new Date(System.currentTimeMillis() + i)); 
  15.         dataList.add(data); 
  16.     } 
  17.     //定义文件输出位置 
  18.     FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user2.xlsx")); 
  19.     EasyExcel.write(outputStream).head(headList).sheet("用户信息").doWrite(dataList); 

运行程序,打开文件内容,结果与上面一致!

2.2.3、复杂表头的生成

很多时候我们需要导出的文件,表头比较复杂,例如,我们想导出如下图这样一个复杂表头,应该如何实现呢?

如果你是使用在实体类上添加注解方式生成文件,那么可以通过如下方式来实现:

  1. public class UserEntity { 
  2.  
  3.     @ExcelProperty(value = "班级"
  4.     private String className; 
  5.  
  6.     @ExcelProperty({"学生信息""姓名"}) 
  7.     private String name
  8.  
  9.     @ExcelProperty({"学生信息""年龄"}) 
  10.     private int age; 
  11.  
  12.     @DateTimeFormat("yyyy-MM-dd HH:mm:ss"
  13.     @ExcelProperty({"学生信息""入学时间"}) 
  14.     private Date time
  15.   
  16.  //set、get... 

其中{"学生信息", "姓名"}这种表达式,表示在当前列,插入多行数据,第一行插入的是学生信息名称,第二行,插入的是姓名名称,因此形成多级表头!

如果你是使用的动态参数化生成文件,操作也同样类似,示例代码如下:

  1. public static void main(String[] args) throws FileNotFoundException { 
  2.     //定义多级表头 
  3.     List<List<String>> headList = new ArrayList<>(); 
  4.     headList.add(Lists.newArrayList("班级")); 
  5.     headList.add(Lists.newArrayList("学生信息""姓名")); 
  6.     headList.add(Lists.newArrayList("学生信息","年龄")); 
  7.     headList.add(Lists.newArrayList("学生信息","入学时间")); 
  8.  
  9.     //定义数据体 
  10.     List<List<Object>> dataList = new ArrayList<>(); 
  11.     for (int i = 0; i < 10; i++) { 
  12.         List<Object> data = new ArrayList<>(); 
  13.         data.add("一年级~1班"); 
  14.         data.add("张三" + i); 
  15.         data.add(20 + i); 
  16.         data.add(new Date(System.currentTimeMillis() + i)); 
  17.         dataList.add(data); 
  18.     } 
  19.     //定义文件输出位置 
  20.     FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user3.xlsx")); 
  21.     EasyExcel.write(outputStream).head(headList).sheet("用户信息").doWrite(dataList); 

其中Lists.newArrayList("学生信息", "姓名")表达的意思跟上面一样,在当前列下插入多行,类似于:

  1. List<String> list = new ArrayList<>(); 
  2. list.add("学生信息"); 
  3. list.add("姓名"); 

Lists.newArrayList编程来自于guava工具包!

2.2.4、自定义样式

在实际使用过程中,我们可能还需要针对文件做一下样式自定义,例如你想把表头设置为红色,内容设置为绿色,列宽、行宽都加大,应该如何实现呢?

操作也很简单,编写一个自定义样式类,然后在写入的时候注入进去。

  1. /** 
  2.  * 自定义样式 
  3.  * @return 
  4.  */ 
  5. private static HorizontalCellStyleStrategy customerStyle(){ 
  6.     // 头的策略 
  7.     WriteCellStyle headWriteCellStyle = new WriteCellStyle(); 
  8.     // 背景设置为红色 
  9.     headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); 
  10.     WriteFont headWriteFont = new WriteFont(); 
  11.     headWriteFont.setFontHeightInPoints((short)20); 
  12.     headWriteCellStyle.setWriteFont(headWriteFont); 
  13.     // 内容的策略 
  14.     WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); 
  15.     // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 
  16.     contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); 
  17.     // 背景绿色 
  18.     contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); 
  19.     WriteFont contentWriteFont = new WriteFont(); 
  20.     // 字体大小 
  21.     contentWriteFont.setFontHeightInPoints((short)20); 
  22.     contentWriteCellStyle.setWriteFont(contentWriteFont); 
  23.     // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 
  24.     HorizontalCellStyleStrategy horizontalCellStyleStrategy = 
  25.             new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); 
  26.     return horizontalCellStyleStrategy; 

在写入的时候,将其注入,例如下面的动态导出:

  1. //通过registerWriteHandler方法,将自定义的样式类注入进去 
  2. EasyExcel.write(outputStream).registerWriteHandler(customerStyle()).head(headList).sheet("用户信息").doWrite(dataList); 

2.3、导入 excel

easyexcel 的导入同样也支持两种方式,和上面一样,一种是通过实体类注解方式来读取文件,另一种是通过动态监听器读取文件。

2.3.1、实体类注解方式来读取文件

实体类注解方式来读取文件时,要读取的 excel 表头需要与实体类一一对应,以下面的 excel 文件为例!

通过注解方式来读取,既可以指定列的下表,也可以通过列名来映射,但是两者只能取一个。

  1. /** 
  2.  * 读取实体类 
  3.  */ 
  4. public class UserReadEntity { 
  5.  
  6.     @ExcelProperty(value = "姓名"
  7.     private String name
  8.  
  9.     /** 
  10.      * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配 
  11.      */ 
  12.     @ExcelProperty(index = 1) 
  13.     private int age; 
  14.  
  15.     @DateTimeFormat("yyyy-MM-dd HH:mm:ss"
  16.     @ExcelProperty(value = "操作时间"
  17.     private Date time
  18.  
  19.     //set、get... 
  1. public static void main(String[] args) throws FileNotFoundException { 
  2.  //同步读取文件内容 
  3.     FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-user1.xls")); 
  4.     List<UserReadEntity> list = EasyExcel.read(inputStream).head(UserReadEntity.class).sheet().doReadSync(); 
  5.     System.out.println(JSONArray.toJSONString(list)); 

运行程序,输出结果如下:

  1. [{"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 一行一行解析出来的数据,然后将数据封装出来,基于此,我们可以编写一套动态的导入工具类,详细工具类会下面介绍到,示例代码如下:

  1. /** 
  2.  * 创建一个监听器,继承自AnalysisEventListener 
  3.  */ 
  4. public class UserDataListener extends AnalysisEventListener<Map<Integer, String>> { 
  5.  
  6.     private static final Logger LOGGER = LoggerFactory.getLogger(UserDataListener.class); 
  7.  
  8.     /** 
  9.      * 表头数据 
  10.      */ 
  11.     private List<Map<Integer, String>> headList = new ArrayList<>(); 
  12.  
  13.     /** 
  14.      * 数据体 
  15.      */ 
  16.     private List<Map<Integer, String>> dataList = new ArrayList<>(); 
  17.  
  18.     /** 
  19.      * 这里会一行行的返回头 
  20.      * 
  21.      * @param headMap 
  22.      * @param context 
  23.      */ 
  24.     @Override 
  25.     public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { 
  26.         LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); 
  27.         headList.add(headMap); 
  28.     } 
  29.  
  30.     /** 
  31.      * 这个每一条数据解析都会来调用 
  32.      * 
  33.      * @param data 
  34.      *            one row value. Is is same as {@link AnalysisContext#readRowHolder()} 
  35.      * @param context 
  36.      */ 
  37.     @Override 
  38.     public void invoke(Map<Integer, String> data, AnalysisContext context) { 
  39.         LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); 
  40.         dataList.add(data); 
  41.     } 
  42.  
  43.     /** 
  44.      * 所有数据解析完成了 都会来调用 
  45.      * 
  46.      * @param context 
  47.      */ 
  48.     @Override 
  49.     public void doAfterAllAnalysed(AnalysisContext context) { 
  50.         LOGGER.info("所有数据解析完成!"); 
  51.     } 
  52.  
  53.     public List<Map<Integer, String>> getHeadList() { 
  54.         return headList; 
  55.     } 
  56.  
  57.     public List<Map<Integer, String>> getDataList() { 
  58.         return dataList; 
  59.     } 
  1. public static void main(String[] args) throws FileNotFoundException { 
  2.     FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-user1.xls")); 
  3.     //初始化一个监听器 
  4.     UserDataListener userDataListener = new UserDataListener(); 
  5.     //读取文件数据 
  6.     EasyExcel.read(inputStream, userDataListener).sheet().doRead(); 
  7.     System.out.println("表头:" + JSONArray.toJSONString(userDataListener.getHeadList())); 
  8.     System.out.println("数据体:" + JSONArray.toJSONString(userDataListener.getDataList())); 

运行程序,输出结果如下:

  1. 表头:[{0:"姓名",1:"年龄",2:"操作时间"}] 
  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、复杂表头读取

在实际的开发中,我们还会遇到复杂表头的数据读取,以如下表头为例,我们应该如何读取呢?

如果你是采用注解的方式导出的文件,同样也可以通过注解方式来读取,例如上文中,我们是使用如下实体类生成的文件,我们也可通过这个类读取文件!

  1. public class UserEntity { 
  2.  
  3.     @ExcelProperty(value = "班级"
  4.     private String className; 
  5.  
  6.     @ExcelProperty({"学生信息""姓名"}) 
  7.     private String name
  8.  
  9.     @ExcelProperty({"学生信息""年龄"}) 
  10.     private int age; 
  11.  
  12.     @DateTimeFormat("yyyy-MM-dd HH:mm:ss"
  13.     @ExcelProperty({"学生信息""入学时间"}) 
  14.     private Date time
  15.   
  16.  //set、get 
  1. //读取文件 
  2. List<UserEntity> list = EasyExcel.read(filePath).head(UserEntity.class).sheet().doReadSync(); 
  3. System.out.println(JSONArray.toJSONString(list)); 

读取结果如下:

  1. [{"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}] 

如果你是使用动态参数化来生成文件,那么这个时候可以采用动态监听器的方式来读取文件,在读取的时候需要指定数据所在行,示例代码如下:

  1. public static void main(String[] args) throws FileNotFoundException { 
  2.     FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-export-user4.xlsx")); 
  3.     //初始化一个监听器 
  4.     UserDataListener userDataListener = new UserDataListener(); 
  5.     //读取文件数据,指定数据所在行使用headRowNumber方法 
  6.     EasyExcel.read(inputStream, userDataListener).sheet().headRowNumber(2).doRead(); 
  7.     System.out.println("表头:" + JSONArray.toJSONString(userDataListener.getHeadList())); 
  8.     System.out.println("数据体:" + JSONArray.toJSONString(userDataListener.getDataList())); 

读取结果如下:

  1. 表头:[{0:"班级",1:"学生信息",2:"学生信息",3:"学生信息"},{0:"班级",1:"姓名",2:"年龄",3:"入学时间"}] 
  2. 数据体:[{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 导入导出需求,就编写一个方法,而且很多业务需求都是动态导入导出,没办法基于实体类注解的方式来读取文件或者写入文件

因此,基于动态参数化生成文件和动态监听器读取文件方法,我们可以单独封装一套动态导出导出工具类,省的我们每次都需要重新编写大量重复工作,以下就是小编我在实际使用过程,封装出来的工具类,在此分享给大家!

  • 动态导出工具类
  1. public class DynamicEasyExcelExportUtils { 
  2.  
  3.     private static final Logger log = LoggerFactory.getLogger(DynamicEasyExcelExportUtils.class); 
  4.  
  5.     private static final String DEFAULT_SHEET_NAME = "sheet1"
  6.  
  7.     /** 
  8.      * 动态生成导出模版(单表头) 
  9.      * @param headColumns 列名称 
  10.      * @return            excel文件流 
  11.      */ 
  12.     public static byte[] exportTemplateExcelFile(List<String> headColumns){ 
  13.         List<List<String>> excelHead = Lists.newArrayList(); 
  14.         headColumns.forEach(columnName -> { excelHead.add(Lists.newArrayList(columnName)); }); 
  15.         byte[] stream = createExcelFile(excelHead, new ArrayList<>()); 
  16.         return stream; 
  17.     } 
  18.  
  19.     /** 
  20.      * 动态生成模版(复杂表头) 
  21.      * @param excelHead   列名称 
  22.      * @return 
  23.      */ 
  24.     public static byte[] exportTemplateExcelFileCustomHead(List<List<String>> excelHead){ 
  25.         byte[] stream = createExcelFile(excelHead, new ArrayList<>()); 
  26.         return stream; 
  27.     } 
  28.  
  29.     /** 
  30.      * 动态导出文件 
  31.      * @param headColumnMap  有序列头部 
  32.      * @param dataList       数据体 
  33.      * @return 
  34.      */ 
  35.     public static byte[] exportExcelFile(LinkedHashMap<String, String> headColumnMap, List<Map<String, Object>> dataList){ 
  36.         //获取列名称 
  37.         List<List<String>> excelHead = new ArrayList<>(); 
  38.         if(MapUtils.isNotEmpty(headColumnMap)){ 
  39.             //key为匹配符,value为列名,如果多级列名用逗号隔开 
  40.             headColumnMap.entrySet().forEach(entry -> { 
  41.                 excelHead.add(Lists.newArrayList(entry.getValue().split(","))); 
  42.             }); 
  43.         } 
  44.         List<List<Object>> excelRows = new ArrayList<>(); 
  45.         if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){ 
  46.             for (Map<String, Object> dataMap : dataList) { 
  47.                 List<Object> rows = new ArrayList<>(); 
  48.                 headColumnMap.entrySet().forEach(headColumnEntry -> { 
  49.                     if(dataMap.containsKey(headColumnEntry.getKey())){ 
  50.                         Object data = dataMap.get(headColumnEntry.getKey()); 
  51.                         rows.add(data); 
  52.                     } 
  53.                 }); 
  54.                 excelRows.add(rows); 
  55.             } 
  56.         } 
  57.         byte[] stream = createExcelFile(excelHead, excelRows); 
  58.         return stream; 
  59.     } 
  60.  
  61.     /** 
  62.      * 生成文件 
  63.      * @param excelHead 
  64.      * @param excelRows 
  65.      * @return 
  66.      */ 
  67.     private static byte[] createExcelFile(List<List<String>> excelHead, List<List<Object>> excelRows){ 
  68.         try { 
  69.             if(CollectionUtils.isNotEmpty(excelHead)){ 
  70.                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 
  71.                 EasyExcel.write(outputStream).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) 
  72.                         .head(excelHead) 
  73.                         .sheet(DEFAULT_SHEET_NAME) 
  74.                         .doWrite(excelRows); 
  75.                 return outputStream.toByteArray(); 
  76.             } 
  77.         } catch (Exception e) { 
  78.             log.error("动态生成excel文件失败,headColumns:" + JSONArray.toJSONString(excelHead) + ",excelRows:" + JSONArray.toJSONString(excelRows), e); 
  79.         } 
  80.         return null
  81.     } 
  82.  
  83.     /** 
  84.      * 导出文件测试 
  85.      * @param args 
  86.      * @throws IOException 
  87.      */ 
  88.     public static void main(String[] args) throws IOException { 
  89.         //导出包含数据内容的文件 
  90.         LinkedHashMap<String, String> headColumnMap = Maps.newLinkedHashMap(); 
  91.         headColumnMap.put("className","班级"); 
  92.         headColumnMap.put("name","学生信息,姓名"); 
  93.         headColumnMap.put("sex","学生信息,性别"); 
  94.         List<Map<String, Object>> dataList = new ArrayList<>(); 
  95.         for (int i = 0; i < 5; i++) { 
  96.             Map<String, Object> dataMap = Maps.newHashMap(); 
  97.             dataMap.put("className""一年级"); 
  98.             dataMap.put("name""张三" + i); 
  99.             dataMap.put("sex""男"); 
  100.             dataList.add(dataMap); 
  101.         } 
  102.         byte[] stream = exportExcelFile(headColumnMap, dataList); 
  103.         FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx")); 
  104.         outputStream.write(stream); 
  105.         outputStream.close(); 
  106.     } 
  • 动态导入工具类
  1. /** 
  2.  * 创建一个监听器 
  3.  */ 
  4. public class DynamicEasyExcelListener extends AnalysisEventListener<Map<Integer, String>> { 
  5.  
  6.     private static final Logger LOGGER = LoggerFactory.getLogger(UserDataListener.class); 
  7.  
  8.     /** 
  9.      * 表头数据(存储所有的表头数据) 
  10.      */ 
  11.     private List<Map<Integer, String>> headList = new ArrayList<>(); 
  12.  
  13.     /** 
  14.      * 数据体 
  15.      */ 
  16.     private List<Map<Integer, String>> dataList = new ArrayList<>(); 
  17.  
  18.     /** 
  19.      * 这里会一行行的返回头 
  20.      * 
  21.      * @param headMap 
  22.      * @param context 
  23.      */ 
  24.     @Override 
  25.     public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { 
  26.         LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); 
  27.         //存储全部表头数据 
  28.         headList.add(headMap); 
  29.     } 
  30.  
  31.     /** 
  32.      * 这个每一条数据解析都会来调用 
  33.      * 
  34.      * @param data 
  35.      *            one row value. Is is same as {@link AnalysisContext#readRowHolder()} 
  36.      * @param context 
  37.      */ 
  38.     @Override 
  39.     public void invoke(Map<Integer, String> data, AnalysisContext context) { 
  40.         LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); 
  41.         dataList.add(data); 
  42.     } 
  43.  
  44.     /** 
  45.      * 所有数据解析完成了 都会来调用 
  46.      * 
  47.      * @param context 
  48.      */ 
  49.     @Override 
  50.     public void doAfterAllAnalysed(AnalysisContext context) { 
  51.         // 这里也要保存数据,确保最后遗留的数据也存储到数据库 
  52.         LOGGER.info("所有数据解析完成!"); 
  53.     } 
  54.  
  55.     public List<Map<Integer, String>> getHeadList() { 
  56.         return headList; 
  57.     } 
  58.  
  59.     public List<Map<Integer, String>> getDataList() { 
  60.         return dataList; 
  61.     } 
  1. /** 
  2.  * 编写导入工具类 
  3.  */ 
  4. public class DynamicEasyExcelImportUtils { 
  5.  
  6.     /** 
  7.      * 动态获取全部列和数据体,默认从第一行开始解析数据 
  8.      * @param stream 
  9.      * @return 
  10.      */ 
  11.     public static List<Map<String,String>> parseExcelToView(byte[] stream) { 
  12.         return parseExcelToView(stream, 1); 
  13.     } 
  14.  
  15.     /** 
  16.      * 动态获取全部列和数据体 
  17.      * @param stream           excel文件流 
  18.      * @param parseRowNumber   指定读取行 
  19.      * @return 
  20.      */ 
  21.     public static List<Map<String,String>> parseExcelToView(byte[] stream, Integer parseRowNumber) { 
  22.         DynamicEasyExcelListener readListener = new DynamicEasyExcelListener(); 
  23.         EasyExcelFactory.read(new ByteArrayInputStream(stream)).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet(0).doRead(); 
  24.         List<Map<Integer, String>> headList = readListener.getHeadList(); 
  25.         if(CollectionUtils.isEmpty(headList)){ 
  26.             throw new RuntimeException("Excel未包含表头"); 
  27.         } 
  28.         List<Map<Integer, String>> dataList = readListener.getDataList(); 
  29.         if(CollectionUtils.isEmpty(dataList)){ 
  30.             throw new RuntimeException("Excel未包含数据"); 
  31.         } 
  32.         //获取头部,取最后一次解析的列头数据 
  33.         Map<Integer, String> excelHeadIdxNameMap = headList.get(headList.size() -1); 
  34.         //封装数据体 
  35.         List<Map<String,String>> excelDataList = Lists.newArrayList(); 
  36.         for (Map<Integer, String> dataRow : dataList) { 
  37.             Map<String,String> rowData = new LinkedHashMap<>(); 
  38.             excelHeadIdxNameMap.entrySet().forEach(columnHead -> { 
  39.                 rowData.put(columnHead.getValue(), dataRow.get(columnHead.getKey())); 
  40.  
  41.             }); 
  42.             excelDataList.add(rowData); 
  43.         } 
  44.         return excelDataList; 
  45.     } 
  46.  
  47.     /** 
  48.      * 文件导入测试 
  49.      * @param args 
  50.      * @throws IOException 
  51.      */ 
  52.     public static void main(String[] args) throws IOException { 
  53.         FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx")); 
  54.         byte[] stream = IoUtils.toByteArray(inputStream); 
  55.         List<Map<String,String>> dataList = parseExcelToView(stream, 2); 
  56.         System.out.println(JSONArray.toJSONString(dataList)); 
  57.         inputStream.close(); 
  58.     } 
  59.  

为了方便后续的操作流程,在解析数据的时候,会将列名作为key!

四、总结

本文主要以实际使用场景为例,对 easyexcel 的使用做了简单的介绍,尤其是动态导出导出,基于业务的需要,做了一个公共的工具类,方便后续进行快速开发,避免重复的劳动!

当然,easyexcel 的功能还不只上面介绍的那些内容,还有基于模版进行excel的填充,web 端restful的导出导出,使用方法大致都差不多,具体可以参与官方的文档,地址如下:https://www.yuque.com/easyexcel/doc/read#1bfaf593

最后,希望本文对大家有所帮助!

五、参考

1、easyexcel - 接口文档

 

 

责任编辑:武晓燕 来源: Java极客技术
相关推荐

2018-02-08 10:47:19

存储技术列存储

2011-10-18 09:23:47

函数式编程

2011-04-13 10:09:50

Oracle数据泵导入导出

2009-04-11 21:41:00

2023-08-27 21:57:07

技术债语言工具

2014-08-15 13:44:40

mongodb

2020-12-18 10:40:00

ExcelJava代码

2022-12-29 08:49:40

SpringBootExcel

2010-07-21 14:17:36

SQL Server数

2021-11-04 10:45:46

SpringBootExcelJava

2023-09-20 10:04:04

Python工具

2011-01-18 17:05:35

Thunderbird邮件导入导出

2022-01-26 07:42:13

SpringBoot性能系统

2020-12-23 14:18:43

JavaScript模块导出

2022-05-11 09:02:27

Python数据库Excel

2019-08-25 23:30:10

mysql命令mysqldump

2010-10-28 11:55:47

oracle数据导出

2020-09-22 09:41:09

前端

2023-03-28 07:17:25

场景数据业务

2019-04-16 09:15:59

开源技术 趋势
点赞
收藏

51CTO技术栈公众号