【Alibaba工具型技术系列】「EasyExcel技术专题」实战技术针对于项目中常用的Excel操作指南

这里写目录标题

      • EasyExcel教程
        • Maven依赖
      • EasyExcel API分析介绍
      • EasyExcel 注解
      • 通用参数
        • ReadWorkbook(理解成excel对象)参数
        • ReadSheet(就是excel的一个Sheet)参数
        • 注解
        • 参数
          • 通用参数
        • WriteWorkbook(理解成excel对象)参数
        • WriteSheet(就是excel的一个Sheet)参数
        • WriteTable(就把excel的一个Sheet,一块区域看一个table)参数
        • EasyExcel用法指南
          • 简单的读取excel文件
          • 简单的写入excel文件
          • Web上传与下载
        • 详解读取Excel
          • 对象模型
          • 监听器
          • 代码
          • 读取多个sheet
          • 自定义格式转换
        • 多行头
        • 读取表头数据
      • 异常处理
        • 导出指定的列
        • 调整指定列顺序
        • 复杂头写入
        • 图片导出
        • 列宽、行高
        • 动态表头
        • 合并单元格
        • web数据写出
        • 模板格式导出
          • 简单的Excel模板
          • 实现模板填充
        • 复杂的填充
      • 总结一个工具类
      • 参考资料

EasyExcel教程

本文使用的技术是Alibaba集团开源的EasyExcel技术,该技术是针对Apache POI技术的封装和优化,主要解决了POI技术的耗内存问题,并且提供了较好的API使用。

  • 使用步骤繁琐
  • 动态写出Excel操作非常麻烦
  • 对于新手来说,很难在短时间内上手
  • 读写时需要占用较大的内容,当数据量大时容器发生OOM
Maven依赖
  <!-- easyexcel 依赖 --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.7</version></dependency>

EasyExcel API分析介绍

  • Ø EasyExcel入口类,用于构建开始各种操作

  • Ø ExcelReaderBuilder ExcelWriterBuilder 构建出一个 ReadWorkbook WriteWorkbook,可以理解成一个excel对象,一个excel只要构建一个

  • Ø ExcelReaderSheetBuilder ExcelWriterSheetBuilder 构建出一个 ReadSheet WriteSheet对象,可以理解成excel里面的一页,每一页都要构建一个

  • Ø ReadListener在每一行读取完毕后都会调用ReadListener来处理数据

  • Ø WriteHandler在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据

  • Ø 所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel…sheet()方法之前作用域是整个sheet,之后针对单个sheet

EasyExcel 注解

  • Ø ExcelProperty 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。

  • Ø ExcelIgnore 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

  • Ø DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat

  • Ø NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat

  • Ø ExcelIgnoreUnannotated 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与

通用参数

  • Ø ReadWorkbook,ReadSheet 都会有的参数,如果为空,默认使用上级。

  • Ø converter 转换器,默认加载了很多转换器。也可以自定义。

  • Ø readListener 监听器,在读取数据的过程中会不断的调用监听器。

  • Ø headRowNumber 需要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。

  • Ø head 与clazz二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用class。

  • Ø clazz 与head二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。

  • Ø autoTrim 字符串、表头等数据自动trim

  • Ø password 读的时候是否需要使用密码

ReadWorkbook(理解成excel对象)参数
  • Ø excelType 当前excel的类型 默认会自动判断

  • Ø inputStream 与file二选一。读取文件的流,如果接收到的是流就只用,不用流建议使用file参数。因为使用了inputStream easyexcel会帮忙创建临时文件,最终还是file

  • Ø file 与inputStream二选一。读取文件的文件。

  • Ø autoCloseStream 自动关闭流。

  • Ø readCache 默认小于5M用 内存,超过5M会使用 EhCache,这里不建议使用这个参数。

ReadSheet(就是excel的一个Sheet)参数
  • Ø sheetNo 需要读取Sheet的编码,建议使用这个来指定读取哪个Sheet

  • Ø sheetName 根据名字去匹配Sheet,excel 2003不支持根据名字去匹配

注解
  • Ø ExcelProperty index 指定写到第几列,默认根据成员变量排序。value指定写入的名称,默认成员变量的名字,多个value可以参照快速开始中的复杂头

  • Ø ExcelIgnore 默认所有字段都会写入excel,这个注解会忽略这个字段

  • Ø DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat

  • Ø NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat

  • Ø ExcelIgnoreUnannotated 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与

参数
通用参数
  • Ø WriteWorkbook,WriteSheet ,WriteTable都会有的参数,如果为空,默认使用上级。

  • Ø converter 转换器,默认加载了很多转换器。也可以自定义。

  • Ø writeHandler 写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用

  • Ø relativeHeadRowIndex 距离多少行后开始。也就是开头空几行

  • Ø needHead 是否导出头

  • Ø head 与clazz二选一。写入文件的头列表,建议使用class。

  • Ø clazz 与head二选一。写入文件的头对应的class,也可以使用注解。

  • Ø autoTrim 字符串、表头等数据自动trim

WriteWorkbook(理解成excel对象)参数
  • Ø excelType 当前excel的类型 默认xlsx

  • Ø outputStream 与file二选一。写入文件的流

  • Ø file 与outputStream二选一。写入的文件

  • Ø templateInputStream 模板的文件流

  • Ø templateFile 模板文件

  • Ø autoCloseStream 自动关闭流。

  • Ø password 写的时候是否需要使用密码

  • Ø useDefaultStyle 写的时候是否是使用默认头

WriteSheet(就是excel的一个Sheet)参数
  • Ø sheetNo 需要写入的编码。默认0

  • Ø sheetName 需要些的Sheet名称,默认同sheetNo

WriteTable(就把excel的一个Sheet,一块区域看一个table)参数
  • Ø tableNo 需要写入的编码。默认0
EasyExcel用法指南
简单的读取excel文件
public void read() {String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭// 参数一:读取的excel文件路径// 参数二:读取sheet的一行,将参数封装在DemoData实体类中// 参数三:读取每一行的时候会执行DemoDataListener监听器EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
简单的写入excel文件
@Test
public void simpleWrite() {String fileName = "demo.xlsx";// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可// 参数一:写入excel文件路径// 参数二:写入的数据类型是DemoData// data()方法是写入的数据,结果是List<DemoData>集合EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
Web上传与下载
/**excel文件的下载
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");response.setHeader("Content-disposition", "attachment;filename=demo.xlsx");EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
}/**excel文件的上传
*/
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {EasyExcel.read(file.getInputStream(), DemoData.class, new DemoDataListener()).sheet().doRead();return "success";
}
详解读取Excel
对象模型
// 如果没有特殊说明,下面的案例将默认使用这个实体类
public class DemoData {/*** 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配*/@ExcelProperty(index = 2)//我想接收百分比的数字@NumberFormat("#.##%")@ExcelProperty(value="浮点数标题", converter = CustomStringConverter.class)private Double doubleData;/*** 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据*/@ExcelProperty(value="字符串标题", converter = CustomStringConverter.class)// converter属性定义自己的字符串转换器private String string;@DateTimeFormat("yyyy年MM月dd日 HH时mm分ss秒")@ExcelProperty("日期标题")//这里用string 去接日期才能格式化private Date date;
}
监听器
// 如果没有特殊说明,下面的案例将默认使用这个监听器
public class DemoDataListener extends AnalysisEventListener<DemoData> {List<DemoData> list = new ArrayList<DemoData>();/*** 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来*/public DemoDataListener() {}/*** 这个每一条数据解析都会来调用** @param data* @param context*/@Overridepublic void invoke(DemoData data, AnalysisContext context) {System.out.println("解析到一条数据:{}", JSON.toJSONString(data));list.add(data);}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {System.out.println(JSON.toJSONString(list));}
}
代码
@Test
public void simpleRead() {// 写法1:String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();// 写法2:fileName = "demo.xlsx";ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();ReadSheet readSheet = EasyExcel.readSheet(0).build();excelReader.read(readSheet);// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的excelReader.finish();
}
读取多个sheet
@Test
public void repeatedRead() {String fileName = "demo.xlsx";// 读取全部sheet// 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();// 读取部分sheetfileName = "demo.xlsx";ExcelReader excelReader = EasyExcel.read(fileName).build();// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener// readSheet参数设置读取sheet的序号ReadSheet readSheet1 =EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();ReadSheet readSheet2 =EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能excelReader.read(readSheet1, readSheet2);// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的excelReader.finish();
}
自定义格式转换
public class CustomStringStringConverter implements Converter<String> {@Overridepublic Class supportJavaTypeKey() {return String.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 这里读的时候会调用** @param cellData*            NotNull* @param contentProperty*            Nullable* @param globalConfiguration*            NotNull* @return*/@Overridepublic String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) {return "自定义:" + cellData.getStringValue();}/*** 这里是写的时候会调用 不用管** @param value*            NotNull* @param contentProperty*            Nullable* @param globalConfiguration*            NotNull* @return*/@Overridepublic CellData convertToExcelData(String value, ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) {return new CellData(value);}
}
多行头
@Test
public void complexHeaderRead() {String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()// 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入默认1行.headRowNumber(1).doRead();
}
读取表头数据

覆盖监听器invokeHeadMap方法

/*** 这里会一行行的返回头* 监听器只需要重写这个方法就可以读取到头信息* @param headMap* @param context*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}

异常处理

覆盖监听器onException方法

/**
* 监听器实现这个方法就可以在读取数据的时候获取到异常信息
*/
@Override
public void onException(Exception exception, AnalysisContext context) {LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage());// 如果是某一个单元格的转换异常 能获取到具体行号// 如果要获取头的信息 配合invokeHeadMap使用if (exception instanceof ExcelDataConvertException) {ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;LOGGER.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(),excelDataConvertException.getColumnIndex());}
}
导出指定的列
@Test
public void excludeOrIncludeWrite() {String fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";// 忽略 date 不导出Set<String> excludeColumnFiledNames = new HashSet<String>();excludeColumnFiledNames.add("date");// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("忽略date").doWrite(data());fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";// 根据用户传入字段 假设我们只要导出 dateSet<String> includeColumnFiledNames = new HashSet<String>();includeColumnFiledNames.add("date");// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("导出date").doWrite(data());
}
调整指定列顺序
public class IndexData {/*** 导出的excel第二列和第四列将空置*/@ExcelProperty(value = "字符串标题", index = 0)private String string;@ExcelProperty(value = "日期标题", index = 2)private Date date;@ExcelProperty(value = "浮点数标题", index = 4)private Double doubleData;
}
复杂头写入
public class ComplexHeadData {/*** 主标题 将整合为一个单元格效果如下:* —————————————————————————* |          主标题        |* —————————————————————————* |字符串标题|日期标题|数字标题|* —————————————————————————*/@ExcelProperty({"主标题", "字符串标题"})private String string;@ExcelProperty({"主标题", "日期标题"})private Date date;@ExcelProperty({"主标题", "数字标题"})private Double doubleData;
}

前面属于主标题,后面属于副标题

图片导出
@Data
@ContentRowHeight(200)
@ColumnWidth(200 / 8)
public class ImageData {// 图片导出方式有5种private File file;private InputStream inputStream;/*** 如果string类型 必须指定转换器,string默认转换成string,该转换器是官方支持的*/@ExcelProperty(converter = StringImageConverter.class)private String string;private byte[] byteArray;/*** 根据url导出 版本2.1.1才支持该种模式*/private URL url;
}@Test
public void imageWrite() throws Exception {String fileName = "imageWrite" + System.currentTimeMillis() + ".xlsx";// 如果使用流 记得关闭InputStream inputStream = null;try {List<ImageData> list = new ArrayList<ImageData>();ImageData imageData = new ImageData();list.add(imageData);String imagePath = "converter" + File.separator + "img.jpg";// 放入五种类型的图片 根据实际使用只要选一种即可imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));imageData.setFile(new File(imagePath));imageData.setString(imagePath);inputStream = FileUtils.openInputStream(new File(imagePath));imageData.setInputStream(inputStream);imageData.setUrl(new URL("https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg"));EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list);} finally {if (inputStream != null) {inputStream.close();}}
}
列宽、行高
@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;/*** 宽度为50,覆盖上面的宽度25*/@ColumnWidth(50)@ExcelProperty("数字标题")private Double doubleData;
}
  • @HeadRowHeight(value = 35) // 表头行高
  • @ContentRowHeight(value = 25) // 内容行高
  • @ColumnWidth(value = 50) // 列宽

此外还有,自适应宽度,但是这个不是特别精确

@Test
void contextLoads() {EasyExcel.write("自适应.xlsx", Student.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet().doWrite(getData());
}
动态表头
@Test
public void dynamicHeadWrite() {String fileName = "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName)// 这里放入动态头.head(head()).sheet("模板")// 当然这里数据也可以用 List<List<String>> 去传入.doWrite(data());
}
// 动态表头的数据格式List<List<String>>
private List<List<String>> head() {List<List<String>> list = new ArrayList<List<String>>();List<String> head0 = new ArrayList<String>();head0.add("字符串" + System.currentTimeMillis());List<String> head1 = new ArrayList<String>();head1.add("数字" + System.currentTimeMillis());List<String> head2 = new ArrayList<String>();head2.add("日期" + System.currentTimeMillis());list.add(head0);list.add(head1);list.add(head2);return list;
}
合并单元格
 @Testpublic void mergeWrite() {String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("合并单元格").doWrite(data());}
  • 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写

  • 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭

web数据写出
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("数据写出", "UTF-8");response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
}
模板格式导出

如果需要横向填充只需要模板设置好就可以。

简单的Excel模板

public class FillData {private String name;private double number;// getting setting
}
实现模板填充
@Test
public void simpleFill() {String templateFileName = "simple.xlsx";// 方案1 根据对象填充String fileName = System.currentTimeMillis() + ".xlsx";// 这里 会填充到第一个sheet, 然后文件流会自动关闭FillData fillData = new FillData();fillData.setName("知春秋");fillData.setNumber(25);EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);// 方案2 根据Map填充fileName = System.currentTimeMillis() + ".xlsx";// 这里 会填充到第一个sheet, 然后文件流会自动关闭Map<String, Object> map = new HashMap<String, Object>();map.put("name", "知春秋");map.put("number", 25);EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}
  • 模板注意 用{} 来表示你要用的变量 如果本来就有"{“,”}" 特殊字符 用"{“,”}"代替
复杂的填充

使用List集合的方法批量写入数据,点表示该参数是集合

@Test
public void complexFill() {String templateFileName = "complex.xlsx";String fileName = System.currentTimeMillis() + ".xlsx";ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();WriteSheet writeSheet = EasyExcel.writerSheet().build();// 如果数据量大 list不是最后一行 参照下一个FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();excelWriter.fill(data(), fillConfig, writeSheet);excelWriter.fill(data(), fillConfig, writeSheet);// 其他参数可以使用Map封装Map<String, Object> map = new HashMap<String, Object>();excelWriter.fill(map, writeSheet);excelWriter.finish();
}
  • 模板注意 用{} 来表示你要用的变量 如果本来就有"{“,”}" 特殊字符 用"{“,”}"代替

  • // {} 代表普通变量 {.} 代表是list的变量
    
  • // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。

  • // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
    
  • // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
    

总结一个工具类


public class ExcelUtil {/*** 写出一个 excel 文件到本地* <br />* 将类型所有加了 @ExcelProperty 注解的属性全部写出** @param fileName  文件名 不要后缀* @param sheetName sheet名* @param data      写出的数据* @param clazz     要写出数据类的Class类型对象* @param <T>       写出的数据类型*/public static <T> void writeExcel(String fileName, String sheetName, List<T> data, Class<T> clazz) {writeExcel(null, fileName, sheetName, data, clazz);}/*** 按照指定的属性名进行写出 一个 excel** @param attrName  指定的属性名 必须与数据类型的属性名一致* @param fileName  文件名 不要后缀* @param sheetName sheet名* @param data      要写出的数据* @param clazz     要写出数据类的Class类型对象* @param <T>       要写出的数据类型*/public static <T> void writeExcel(Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {fileName = StringUtils.isBlank(fileName) ? "学生管理系统" : fileName;sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;try(FileOutputStream fos = new FileOutputStream(fileName)) {write(fos,attrName,sheetName,data,clazz);} catch (Exception exception) {exception.printStackTrace();}}/*** 读取 指定格式的 excel文档** @param fileName 文件名* @param clazz    数据类型的class对象* @param <T>      数据类型* @return*/public static <T> List<T> readExcel(String fileName, Class<T> clazz) {return readExcel(fileName, clazz, null);}/*** 取 指定格式的 excel文档* 注意一旦传入自定义监听器,则返回的list为空,数据需要在自定义监听器里面获取** @param fileName     文件名* @param clazz        数据类型的class对象* @param readListener 自定义监听器* @param <T>          数据类型* @return*/public static <T> List<T> readExcel(String fileName, Class<T> clazz, ReadListener<T> readListener) {try(FileInputStream fis = new FileInputStream(fileName)) {return read(fis,clazz,readListener);} catch (Exception exception) {exception.printStackTrace();}}/*** 导出  一个 excel*         导出excel所有数据* @param response* @param fileName  件名 最好为英文,不要后缀名* @param sheetName sheet名* @param data      要写出的数据* @param clazz     要写出数据类的Class类型对象* @param <T>       要写出的数据类型*/public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> clazz) {export(response, null, fileName, sheetName, data, clazz);}/*** 按照指定的属性名进行写出 一个 excel** @param response* @param attrName  指定的属性名 必须与数据类型的属性名一致* @param fileName  文件名 最好为英文,不要后缀名* @param sheetName sheet名* @param data      要写出的数据* @param clazz     要写出数据类的Class类型对象* @param <T>       要写出的数据类型*/public static <T> void export(HttpServletResponse response, Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {fileName = StringUtils.isBlank(fileName) ? "student-system-manager" : fileName;sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;response.setContentType("application/vnd.ms-excel;charset=utf-8");response.setCharacterEncoding("utf-8");response.addHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());try(OutputStream os = response.getOutputStream()) {write(os,attrName,sheetName,data,clazz);} catch (IOException e) {e.printStackTrace();}}/*** 接收一个excel文件,并且进行解析*  注意一旦传入自定义监听器,则返回的list为空,数据需要在自定义监听器里面获取* @param multipartFile excel文件* @param clazz 数据类型的class对象* @param readListener 监听器* @param <T>* @return*/public static <T> List<T> importExcel(MultipartFile multipartFile,Class<T> clazz,ReadListener<T> readListener) {try(InputStream inputStream = multipartFile.getInputStream()) {return read(inputStream,clazz,readListener);} catch (IOException e) {e.printStackTrace();}}private static <T> void write(OutputStream os, Set<String> attrName, String sheetName, List<T> data, Class<T> clazz) {ExcelWriterBuilder write = EasyExcel.write(os, clazz);// 如果没有指定要写出那些属性数据,则写出全部if (!CollectionUtils.isEmpty(attrName)) {write.includeColumnFiledNames(attrName);}write.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(data);}private static <T> List<T> read(InputStream in,Class<T> clazz, ReadListener<T> readListener) {List<T> list = new ArrayList<>();Optional<ReadListener> optional = Optional.ofNullable(readListener);EasyExcel.read(in, clazz, optional.orElse(new AnalysisEventListener<T>() {@Overridepublic void invoke(T data, AnalysisContext context) {list.add(data);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {System.out.println("解析完成")}})).sheet().doRead();return list;}
}

参考资料

  • https://github.com/alibaba/easyexcel/blob/master/docs/API.md 官方api
  • https://github.com/alibaba/easyexcel easyexcel github 地址
  • https://blog.csdn.net/sinat_32366329/article/details/103109058 easyexcel总结
  • https://alibaba-easyexcel.github.io/index.html 官方示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/632793.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

冒泡排序-BubbleSort

1、基本思路 从数组的左边开始&#xff0c;比较两个元素的大小&#xff0c;当左边大于右边时&#xff0c;更换左右元素位置&#xff0c;否则不改变&#xff1b;接着向右移动一步&#xff0c;比较第二个元素和第三个元素的大小&#xff0c;重复上述操作&#xff0c;直到最后一个…

为什么 Golang Fasthttp 选择使用 slice 而非 map 存储请求数据

文章目录 Slice vs Map&#xff1a;基本概念内存分配和性能Fasthttp 中的 SliceMap性能优化的深层原因HTTP Headers 的特性CPU 预加载特性 结论 Fasthttp 是一个高性能的 Golang HTTP 框架&#xff0c;它在设计上做了许多优化以提高性能。其中一个显著的设计选择是使用 slice 而…

Ubuntu开机停在Grub命令行以及启动后进入emergency模式的解决方法

主机上两个硬盘&#xff0c;本来都是Ubutun系统&#xff0c;然后将其中一个重装为windows系统&#xff0c;结果另外一个硬盘的Ubutun启动不了 首先会卡在grub命令行界面&#xff0c;类似下面这样&#xff1a; 这种情况往往是 GRUB 的系统文件或者配置文件损坏了&#xff0c;但…

基于麻雀优化算法SSA的CEEMDAN-BiLSTM-Attention的预测模型

往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较-CSDN博客 风速预测&#xff08;一&#xff09;数据集介绍和预处理-CSDN博客 风速预测&#xff08;二&#xff09;基于Pytorch的EMD-LSTM模型-CSDN博客 风速预测&#xff…

Android: alarm定时很短时,比如500ms,测试执行mPowerManager.forceSuspend()后,系统不会suspend

参考文档&#xff1a; https://blog.csdn.net/weixin_35691921/article/details/124961404 Android: alarm定时很短时&#xff0c;比如500ms&#xff0c;然后执行mPowerManager.forceSuspend()后&#xff0c;系统不会suspend&#xff0c;原因分析&#xff1a; static int ala…

【iOS】——基于Vision Kit框架实现图片文字识别

文章目录 前言一、文本识别的分类二、实现步骤1.导入Vision Kit框架2.创建请求处理器3.在请求处理器中设置文字识别功能4.将图片添加到请求处理器中5.发起文字识别请求6.处理识别结果 三、运行结果测试1.纯英文环境2.中英文混合环境 前言 根据苹果的官方文档&#xff0c;Visio…

【React】脚手架创建项目

文章目录 创建React项目目录结构分析了解PWA脚手架中的webpack 创建React项目 ◼ 创建React项目的命令如下&#xff1a; ​  注意&#xff1a;项目名称不能包含大写字母 ​  另外还有更多创建项目的方式&#xff0c;可以参考GitHub的readme 命令&#xff1a; create-rea…

NRF52832跟NRF52840性能区别 高端蓝牙智能手环穿戴应用

nRF52840可以说是nRF52832的超强升级版&#xff0c;虽然同样是蓝牙5.0&#xff0c;但是整体稳定性和性能上的提升却相差甚远&#xff0c;下面为大家简单讲解下NRF52832和NRF52840的性能上的区别&#xff01; 先说说共同点&#xff1a;这两款芯片都支持蓝牙5.0&#xff0c;MCU也…

谷粒商城篇章8 ---- P236-P247 ---- 购物车【分布式高级篇五】

目录 1 环境搭建 1.1 新建购物车服务模块gulimall-cart 1.2 购物车服务相关配置 1.2.1 pom.xml 1.2.2 yml配置 1.2.2.1 application.yml配置 1.2.2.2 bootstrap.yml配置 1.2.3 主类 1.3 SwitchHosts增加配置 1.4 网关配置 1.5 整合SpringSession 1.5.1 session数据…

禅道下载安装及基本使用(项目周期管理)实施必会!!!

文章目录 前言&#xff1a;一、为什么要使用禅道&#xff1f;二、禅道的下载与安装 前言&#xff1a; 禅道的使用能使公司提高项目管理效率、促进团队协作、支持敏捷开发&#xff0c;并可根据具体需求进行个性化配置。 本文章博主将以一个项目周期来带你们了解禅道。 一、为什…

SCSI/UFS储存架构/协议/电源管理/命令处理流程

UFS子系统架构 1.UFS协议 无论是ufs host controller部分还是ufs device部分&#xff0c;他们都将遵循统一的UFS规范 UFS Application Layer(UAP)应用层 1.UFS command set (UCS) UCS处理命令集&#xff0c;如读、写命令等&#xff0c;.使用的命令是简化的SCSI命令&#xff08;…

ora-12154无法解析指定的连接标识符

用户反映查询的时候报错ora-12154 这个系统只做历史数据查询使用&#xff0c;使用并不平凡&#xff0c;该数据库曾做过一次服务器间的迁移。 用户描述&#xff0c;所有oracle客户端查询该视图都报tns错误&#xff0c;一般ora-12154会发生在连接数据库时&#xff0c;因为tns配…

[文件IO]文件操作的几个笔试题

目录 1.扫描指定目录&#xff0c;找到要查询的文件名&#xff0c;并询问用户是否要删除 2. 文件的复制 3.扫描指定目录&#xff0c;并找到名称或者内容中包含指定字符的所有普通文件&#xff08;不包含目录&#xff09; 1.扫描指定目录&#xff0c;找到要查询的文件名&#x…

Hardware-Aware-Transformers开源项目笔记

文章目录 Hardware-Aware-Transformers开源项目笔记开源项目背景知识nas进化算法进化算法代码示例 开源项目Evolutionary Search1 生成延迟的数据集2 训练延迟预测器3 使延时约束运行搜索算法4. 训练搜索得到的subTransformer5. 根据重训练后的submodel 得到BLEU精度值 代码结构…

医学图像的图像处理、分割、分类和定位-1

一、说明 本报告全面探讨了应用于医学图像的图像处理和分类技术。开展了四项不同的任务来展示这些方法的多功能性和有效性。任务 1 涉及读取、写入和显示 PNG、JPG 和 DICOM 图像。任务 2 涉及基于定向变化的多类图像分类。此外&#xff0c;我们在任务 3 中包括了胸部 X 光图像…

java数据结构与算法刷题-----LeetCode977. 有序数组的平方

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 时间复杂度 空间复杂度 O(n * l o g 2 n log_2{n} log2​…

PyTorch 中的距离函数深度解析:掌握向量间的距离和相似度计算

目录 Pytorch中Distance functions详解 pairwise_distance 用途 用法 参数 数学理论公式 示例代码 cosine_similarity 用途 用法 参数 数学理论 示例代码 输出结果 pdist 用途 用法 参数 数学理论 示例代码 总结 Pytorch中Distance functions详解 pair…

JVM工作原理与实战(十九):运行时数据区-方法区

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、运行时数据区 二、方法区 1.方法区介绍 2.方法区在Java虚拟机的实现 3.类的元信息 4.运行时常量池 5.字符串常量池 6.静态变量的存储 总结 前言 JVM作为Java程序的运行环境…

App分发测试平台:改变应用开发的游戏规则

App分发测试平台是一个提供应用开发者上传、测试、分享和发布应用的在线服务平台。它为开发者提供了一个高效的测试环境&#xff0c;并为用户提供了一个方便的应用获取渠道。其中&#xff0c;测试环节尤为关键&#xff0c;因为它能够确保应用在上线之前达到预期的功能和性能。 …

问题:Feem无法发送信息OR无法连接(手机端无法发给电脑端)

目录 前言 问题分析 资源、链接 其他问题 前言 需要在小米手机、华为平板、Dell电脑之间传输文件&#xff0c;试过安装破解的华为电脑管家、小米的MIUI文件传输等&#xff0c;均无果。&#xff08;小米“远程管理”ftp传输倒是可以&#xff0c;但速度太慢了&#xff0c;且…