真香!Java 导出 Excel 表格竟变得如此简单优雅

技术选型

能够实现「导入/导出 Excel」的第三方常用类库有 Apache poi、Java Excel(JXL)和阿里巴巴开源的 Easyexcel 等。这么多类库该怎么选呢?在这里小编给大家推荐阿里巴巴开源的「Easyexcel」。

  • 性能对比
    poi 和 jxl 对内存的消耗很大,在处理大批量的数据时,容易造成内存溢出。比如处理一个 3M 的 Excel,poi 和 jxl 可能需要上百兆的内存,但 easyexcel 可能只需要几百或几千 KB(内存消耗对比有些夸张)。在性能这一块,Excel 完全是低于 poi 和 jxl。

  • 学习复杂度对比
    我最开始使用的是 poi。在学习它的时候,理解起来不难,就是操作的时候太特么的难了。因为 poi 需要自己处理数据,还有复杂的表格样式,就光是处理数据这一款就很头疼了。等你写好所有的代码,没有几百行,你是实现不了的。反观 easyexcel。它能自己处理数据,表格格式也简单,即使是小白也很容易上手。在学习成本远低于poi。

EasyExcel

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。 64M内存1分钟内读取75M(46W行25列)的Excel,当然还有急速模式能更快,但是内存占用会在100M多一点

真没想到!Java 导出 Excel 表格会变得如此简单优雅

添加依赖

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>1.1.2-beat1</version>
</dependency>

 

基础用法

只需要在 Controller 层返回 List 并增加 @ResponseExcel注解即可

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseExcel {
	String name() default "";
	ExcelTypeEnum suffix() default ExcelTypeEnum.XLSX;
	String password() default "";
	String[] sheet() default {};
	boolean inMemory() default false;
	String template() default "";
	String[] include() default {};
	String[] exclude() default {};
	Class<? extends WriteHandler>[] writeHandler() default {};
	Class<? extends Converter>[] converter() default {};
}

 

  • 返回单 sheet, 全部字段导出
@ResponseExcel(name = "lengleng", sheet = "demoList")
@GetMapping("/e1")
public List<DemoData> e1() {
    List<DemoData> dataList = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        DemoData data = new DemoData();
        data.setUsername("tr1" + i);
        data.setPassword("tr2" + i);
        dataList.add(data);
    }
    return dataList;
}

// 实体对象
@Data
public class DemoData {
	private String username;
	private String password;
}

真没想到!Java 导出 Excel 表格会变得如此简单优雅

  • 自定义字段属性
@Data
public class DemoData {
    @ColumnWidth(50)  // 定义宽度
	@ExcelProperty("用户名") // 定义列名称
    @ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40)
	private String username;
	@ExcelProperty("密码")
	private String password;
}

真没想到!Java 导出 Excel 表格会变得如此简单优雅

  • 忽略部分字段
@Data
public class DemoData {
    @ColumnWidth(50)  // 定义宽度
	@ExcelProperty("用户名") // 定义列名称
    @ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40)
	private String username;
	@ExcelProperty("密码")
	private String password;

真没想到!Java 导出 Excel 表格会变得如此简单优雅

导出多sheet

@ResponseExcel(name = "lengleng", sheet = {"第一个sheet","第二个sheet"})
@GetMapping("/e1")
public List<List<DemoData>> e1() {
    List<List<DemoData>> lists = new ArrayList<>();
    lists.add(list());
    lists.add(list());
    return lists;
}

真没想到!Java 导出 Excel 表格会变得如此简单优雅

设置导出加密码

	@ResponseExcel(name = "lengleng", sheet = "sheetName",password = "lengleng")
	@GetMapping("/e1")
	public List<List<DemoData>> e1() {
		List<List<DemoData>> lists = new ArrayList<>();
		lists.add(list());
		lists.add(list());
		return lists;
	}

真没想到!Java 导出 Excel 表格会变得如此简单优雅

进阶用法

模板导出

@ResponseExcel(name = "模板测试excel", sheet = "sheetName",template = "example.xlsx")
@GetMapping("/e1")
public List<DemoData> e1() {
    return list();
}

读取技巧

Excel读取多页

  • 以上都是最基础的单页读写,在我们调用sheet()方法时,实际上都是默认第1页,那么如何读取多页?

 /**
 * 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
 * <p>
 * 1. 创建excel对应的实体对象 参照{@link DemoData}
 * <p>
 * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
 * <p>
 * 3. 直接读即可
 */
 @Test
 public void repeatedRead() {
 String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
 // 读取全部sheet
 // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写
 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();
 // 读取部分sheet
 fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
 ExcelReader excelReader = EasyExcel.read(fileName).build();
 // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
 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();
 }
  • 可以看到doReadAll方法可以读取所有sheet页面

  • 若要读取单独的页面,用第二种方式readSheet(index),index为页面位置,从0开始计数

自定义字段转换

  • 在读取写入的时候,我们可能会有这样的需求:比如日期格式转换,字符串添加固定前缀后缀等等,此时我们可以进行自定义编写

@Data
public class ConverterData {
 /**
 * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
 */
 @ExcelProperty(converter = CustomStringStringConverter.class)
 private String string;
 /**
 * 这里用string 去接日期才能格式化。我想接收年月日格式
 */
 @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
 private String date;
 /**
 * 我想接收百分比的数字
 */
 @NumberFormat("#.##%")
 private String doubleData;
}
  • 如上面的CustomStringStringConverter类为自定义转换器,可以对字符串进行一定修改,而日期数字的格式化,它已经有提供注解了DateTimeFormat和NumberFormat

  • 转换器如下,实现Converter接口后即可使用supportExcelTypeKey这是判断单元格类型,convertToJavaData这是读取转换,convertToExcelData这是写入转换

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
public class CustomStringStringConverter implements Converter<String> {
 @Override
 public Class supportJavaTypeKey() {
 return String.class;
 }
 @Override
 public CellDataTypeEnum supportExcelTypeKey() {
 return CellDataTypeEnum.STRING;
 }
 /**
 * 这里读的时候会调用
 */
 @Override
 public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
 GlobalConfiguration globalConfiguration) {
 return "自定义:" + cellData.getStringValue();
 }
 /**
 * 这里是写的时候会调用 不用管
 */
 @Override
 public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
 GlobalConfiguration globalConfiguration) {
 return new CellData(value);
 }
}
  • 这里解析结果截取部分如下,原数据是字符串0 2020/1/1 1:01 1

  • 解析到一条数据:{"date":"2020年01月01日01时01分01秒","doubleData":"100%","string":"自定义:字符串0"}
     

指定表头行数
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()
 // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行
 .headRowNumber(1).doRead();

读取表头数据

  • 只要在实现了AnalysisEventListener接口的监听器中,重写invokeHeadMap方法即可

 /**
 * 这里会一行行的返回头
 *
 * @param headMap
 * @param context
 */
 @Override
 public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
 LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
 }

 转换异常处理

  • 只要在实现了AnalysisEventListener接口的监听器中,重写onException方法即可

@Override
 public void onException(Exception exception, AnalysisContext context) {
 LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
 if (exception instanceof ExcelDataConvertException) {
 ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
 LOGGER.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(),
 excelDataConvertException.getColumnIndex());
 }
 }

 读取单元格参数和类型

  • 将类属性用CellData封装起来

@Data
public class CellDataReadDemoData {
 private CellData<String> string;
 // 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是number
 private CellData<Date> date;
 private CellData<Double> doubleData;
 // 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复
 private CellData<String> formulaValue;
}
  • 这样读取到的数据如下,会包含单元格数据类型

解析到一条数据:{"date":{"data":1577811661000,"dataFormat":22,"dataFormatString":"m/d/yy h:mm","formula":false,"numberValue":43831.0423726852,"type":"NUMBER"},"doubleData":{"data":1.0,"formula":false,"numberValue":1,"type":"NUMBER"},"formulaValue":{"data":"字符串01","formula":true,"formulaValue":"_xlfn.CONCAT(A2,C2)","stringValue":"字符串01","type":"STRING"},"string":{"data":"字符串0","dataFormat":0,"dataFormatString":"General","formula":false,"stringValue":"字符串0","type":"STRING"}}

同步返回

  • 不推荐使用,但如果特定情况一定要用,可以如下,主要为doReadSync方法,直接返回List

 /**
 * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
 */
 @Test
 public void synchronousRead() {
 String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
 // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
 List<Object> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();
 for (Object obj : list) {
 DemoData data = (DemoData)obj;
 LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
 }
 // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
 list = EasyExcel.read(fileName).sheet().doReadSync();
 for (Object obj : list) {
 // 返回每条数据的键值对 表示所在的列 和所在列的值
 Map<Integer, String> data = (Map<Integer, String>)obj;
 LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
 }
 }

无对象的读

  • 顾名思义,不创建实体对象来读取Excel数据,那么我们就用Map接收,但这种对日期不友好,对于简单字段的读取可以使用

  • 其它都一样,监听器的继承中泛型参数变为Map即可

public class NoModleDataListener extends AnalysisEventListener<Map<Integer, String>> {
 ...
}
  • 结果截取如下

    解析到一条数据:{0:"字符串0",1:"2020-01-01 01:01:01",2:"1"}
 


写入技巧

排除特定字段和只写入特定字段

  • 使用excludeColumnFiledNames来排除特定字段写入,用includeColumnFiledNames表示只写入特定字段

 /**
 * 根据参数只导出指定列
 * <p>
 * 1. 创建excel对应的实体对象 参照{@link DemoData}
 * <p>
 * 2. 根据自己或者排除自己需要的列
 * <p>
 * 3. 直接写即可
 */
 @Test
 public void excludeOrIncludeWrite() {
 String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
 // 根据用户传入字段 假设我们要忽略 date
 Set<String> excludeColumnFiledNames = new HashSet<String>();
 excludeColumnFiledNames.add("date");
 // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板")
 .doWrite(data());
 fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
 // 根据用户传入字段 假设我们只要导出 date
 Set<String> includeColumnFiledNames = new HashSet<String>();
 includeColumnFiledNames.add("date");
 // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板")
 .doWrite(data());
 }

 指定写入列

  • 写入列的顺序可以进行指定,在实体类注解上指定index,从小到大,从左到右排列

@Data
public class IndexData {
 @ExcelProperty(value = "字符串标题", index = 0)
 private String string;
 @ExcelProperty(value = "日期标题", index = 1)
 private Date date;
 /**
 * 这里设置3 会导致第二列空的
 */
 @ExcelProperty(value = "数字标题", index = 3)
 private Double doubleData;
}

复杂头写入

  • 如下图这种复杂头

Excel解析工具easyexcel全面探索

  • 我们可以通过修改实体类注解实现

@Data
public class ComplexHeadData {
 @ExcelProperty({"主标题", "字符串标题"})
 private String string;
 @ExcelProperty({"主标题", "日期标题"})
 private Date date;
 @ExcelProperty({"主标题", "数字标题"})
 private Double doubleData;
}

重复多次写入

  • 分为三种:1. 重复写入同一个sheet;2. 同一个对象写入不同sheet;3. 不同的对象写入不同的sheet

/**
 * 重复多次写入
 * <p>
 * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
 * <p>
 * 2. 使用{@link ExcelProperty}注解指定复杂的头
 * <p>
 * 3. 直接调用二次写入即可
 */
 @Test
 public void repeatedWrite() {
 // 方法1 如果写到同一个sheet
 String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
 // 这里 需要指定写用哪个class去读
 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
 // 这里注意 如果同一个sheet只要创建一次
 WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
 // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
 for (int i = 0; i < 5; i++) {
 // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
 List<DemoData> data = data();
 writeSheet.setSheetName("模板");
 excelWriter.write(data, writeSheet);
 }
 /// 千万别忘记finish 会帮忙关闭流
 excelWriter.finish();
 // 方法2 如果写到不同的sheet 同一个对象
 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
 // 这里 指定文件
 excelWriter = EasyExcel.write(fileName, DemoData.class).build();
 // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
 for (int i = 0; i < 5; i++) {
 // 每次都要创建writeSheet 这里注意必须指定sheetNo
 writeSheet = EasyExcel.writerSheet(i, "模板"+i).build();
 // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
 List<DemoData> data = data();
 excelWriter.write(data, writeSheet);
 }
 /// 千万别忘记finish 会帮忙关闭流
 excelWriter.finish();
 // 方法3 如果写到不同的sheet 不同的对象
 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
 // 这里 指定文件
 excelWriter = EasyExcel.write(fileName).build();
 // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
 for (int i = 0; i < 5; i++) {
 // 每次都要创建writeSheet 这里注意必须指定sheetNo。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class 实际上可以一直变
 writeSheet = EasyExcel.writerSheet(i, "模板"+i).head(DemoData.class).build();
 // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
 List<DemoData> data = data();
 excelWriter.write(data, writeSheet);
 }
 /// 千万别忘记finish 会帮忙关闭流
 excelWriter.finish();
 }

图片导出

  • 对图片的导出,可能会有这样的需求,它提供了四种数据类型的导出,还是很丰富的

@Test
 public void imageWrite() throws Exception {
 String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx";
 // 如果使用流 记得关闭
 InputStream inputStream = null;
 try {
 List<ImageData> list = new ArrayList<ImageData>();
 ImageData imageData = new ImageData();
 list.add(imageData);
 String imagePath = TestFileUtil.getPath() + "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);
 EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list);
 } finally {
 if (inputStream != null) {
 inputStream.close();
 }
 }
 }
  • 图片类为

@Data
@ContentRowHeight(100)
@ColumnWidth(100 / 8)
public class ImageData {
 private File file;
 private InputStream inputStream;
 /**
 * 如果string类型 必须指定转换器,string默认转换成string
 */
 @ExcelProperty(converter = StringImageConverter.class)
 private String string;
 private byte[] byteArray;
}

导出结果:两行四列,每列都对应一张图片,四种导出类型均可

Excel解析工具easyexcel全面探索

  • 其中StringImageConverter自定义转换器为

public class StringImageConverter implements Converter<String> {
 @Override
 public Class supportJavaTypeKey() {
 return String.class;
 }
 @Override
 public CellDataTypeEnum supportExcelTypeKey() {
 return CellDataTypeEnum.IMAGE;
 }
 @Override
 public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
 GlobalConfiguration globalConfiguration) {
 throw new UnsupportedOperationException("Cannot convert images to string");
 }
 @Override
 public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
 GlobalConfiguration globalConfiguration) throws IOException {
 return new CellData(FileUtils.readFileToByteArray(new File(value)));
 }
}

字段宽高设置

  • 设置实体类注解属性即可

@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
 @ExcelProperty("字符串标题")
 private String string;
 @ExcelProperty("日期标题")
 private Date date;
 /**
 * 宽度为50
 */
 @ColumnWidth(50)
 @ExcelProperty("数字标题")
 private Double doubleData;
}

自定义样式

  • 实现会比较复杂,需要做头策略,内容策略,字体大小等

 @Test
 public void styleWrite() {
 String fileName = TestFileUtil.getPath() + "styleWrite" + System.currentTimeMillis() + ".xlsx";
 // 头的策略
 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);
 // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板")
 .doWrite(data());
 }
  • 效果如下

Excel解析工具easyexcel全面探索

单元格合并

 @Test
 public void mergeWrite() {
 String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
 // 每隔2行会合并。当然其他合并策略也可以自己写
 LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
 // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());
 }
  • 效果如下,第一列单元格数据,2,3两行合并

Excel解析工具easyexcel全面探索

自动列宽

  • 根据作者描述,POI对中文的自动列宽适配不友好,easyexcel对数字也不能准确适配列宽,他提供的适配策略可以用,但不能精确适配,可以自己重写

  • 想用就注册处理器LongestMatchColumnWidthStyleStrategy

 @Test
 public void longestMatchColumnWidthWrite() {
 String fileName =
 TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";
 // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 EasyExcel.write(fileName, LongestMatchColumnWidthData.class)
 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());
 }

下拉,超链接

  • 下拉,超链接等功能需要自定义实现

 @Test
 public void customHandlerWrite() {
 String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx";
 // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler())
 .registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data());
 }
  • 其中主要为处理器CustomCellWriteHandler类,其实现CellWriteHandler接口,我们在后处理方法afterCellDispose做处理

public class CustomCellWriteHandler implements CellWriteHandler {
 private static final Logger LOGGER = LoggerFactory.getLogger(CustomCellWriteHandler.class);
 @Override
 public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
 Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
 }
 @Override
 public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
 Head head, Integer relativeRowIndex, Boolean isHead) {
 }
 @Override
 public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
 List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
 // 这里可以对cell进行任何操作
 LOGGER.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex());
 if (isHead && cell.getColumnIndex() == 0) {
 CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
 Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
 hyperlink.setAddress("https://github.com/alibaba/easyexcel");
 cell.setHyperlink(hyperlink);
 }
 }
}

不创建对象的写

  • 在设置write的时候不设置对象类,在head里添加List > 的对象头

@Test
 public void noModleWrite() {
 // 写法1
 String fileName = TestFileUtil.getPath() + "noModleWrite" + System.currentTimeMillis() + ".xlsx";
 // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList());
 }
 
 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;
 }

 总结

  • 不知不觉列出了这么多easyexcel的使用技巧和方式,这里应该囊括了大部分我们工作中常用到的excel读写技巧,欢迎收藏查阅

原创文章 123 获赞 241 访问量 55万+

猜你喜欢

转载自blog.csdn.net/wuzhiwei549/article/details/105874226
今日推荐