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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【Alibaba工具型技术系列】「EasyExcel技术专题」实战技术针对于项目中常用的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
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        System.out.println("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
    }
    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public 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();
    // 读取部分sheet
    fileName = "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> {
  @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
    /**
     * 这里读的时候会调用
     *
     * @param cellData
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return "自定义:" + cellData.getStringValue();
    }
    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public 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";
    // 根据用户传入字段 假设我们只要导出 date
    Set<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;
}
复制代码



合并单元格

@Test
 public 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模板

image.png

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>() {
            @Override
            public void invoke(T data, AnalysisContext context) {
                list.add(data);
            }
            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                  System.out.println("解析完成");
            }
        })).sheet().doRead();
        return list;
    }
}
复制代码




参考资料





相关文章
|
2月前
|
人工智能 Python
读取excel工具:openpyxl | AI应用开发
`openpyxl` 是一个 Python 库,专门用于读写 Excel 2010 xlsx/xlsm/xltx/xltm 文件。它是处理 Excel 文件的强大工具,可以让你在不需要安装 Excel 软件的情况下,对 Excel 文件进行创建、修改、读取和写入操作【10月更文挑战第3天】
84 0
|
4月前
|
关系型数据库 MySQL Shell
不通过navicat工具怎么把查询数据导出到excel表中
不通过navicat工具怎么把查询数据导出到excel表中
47 0
|
17天前
|
数据可视化 数据挖掘 项目管理
打破协作壁垒,Excel多人协同编辑工具带来翻天覆地的变化!
在现代办公中,团队协作和信息共享至关重要。Excel的多人协同编辑功能显著提升了工作效率,避免了版本冲突和重复劳动。市场上的Google Sheets、Airtable、板栗看板和Zoho Sheet等工具也提供了类似功能。以其清晰的界面和强大的数据分析能力,特别适合项目管理和进度追踪,帮助团队高效协作,达成目标。
|
5月前
|
Java BI 数据处理
如何在Java中实现Excel操作
如何在Java中实现Excel操作
|
29天前
|
SQL 数据可视化 数据挖掘
想让Excel表格设计更美观?试试这几款好用工具!
Excel表格设计在项目管理和数据分析中至关重要。本文推荐四款辅助工具:板栗看板、Excel自动图表助手、Think-Cell Chart 和 Power BI,分别在任务管理、图表生成、数据可视化等方面表现突出,帮助你设计出更专业、美观的表格。
50 2
|
1月前
|
数据处理
在Excel中,通配符是一种强大的工具
【10月更文挑战第23天】在Excel中,通配符是一种强大的工具
22 4
|
2月前
|
easyexcel Java UED
SpringBoot中大量数据导出方案:使用EasyExcel并行导出多个excel文件并压缩zip后下载
在SpringBoot环境中,为了优化大量数据的Excel导出体验,可采用异步方式处理。具体做法是将数据拆分后利用`CompletableFuture`与`ThreadPoolTaskExecutor`并行导出,并使用EasyExcel生成多个Excel文件,最终将其压缩成ZIP文件供下载。此方案提升了导出效率,改善了用户体验。代码示例展示了如何实现这一过程,包括多线程处理、模板导出及资源清理等关键步骤。
|
4月前
|
数据可视化 数据格式 索引
我用Python操作Excel的两种主要工具
我用Python操作Excel的两种主要工具
|
4月前
|
人工智能 自然语言处理 小程序
【工具】Excel竟然也能搞AI,快来玩转chatexcel
ChatExcel是由北京大学团队开发的一款人工智能办公辅助工具,用户可通过自然语言与Excel表格互动,简化数据处理任务,如排序、求和等,无需手动编写公式或函数。本文介绍了ChatExcel的功能特点、使用方法及实操步骤,展示了如何通过简单指令完成复杂操作,提高工作效率。此外,还提供了新手指南帮助快速上手。
184 0
【工具】Excel竟然也能搞AI,快来玩转chatexcel
|
4月前
|
easyexcel Java 关系型数据库
阿里巴巴-EasyExcel 基于Java的简单、省内存的读写Excel
该文章主要介绍了在Java应用中如何使用EasyExcel技术完成对Excel文件的导入和导出操作,包括环境搭建、基本概念、快速入门、进阶操作和综合应用等内容,并提供了相关代码示例和注意事项。
 阿里巴巴-EasyExcel 基于Java的简单、省内存的读写Excel