目录

  • 1. 写在最前
    • 1.1 EasyExcel版本
    • 1.2 初探源码
  • 2. 表头实体类 MyUser
  • 3. 最简单的导出Excel文件
  • 4. 源码demo:
    • 4.1 读Excel
    • 1. 最简单的读
    • 2. 指定列的下标或者列名
    • 4.2 写Excel
    • 1. 最简单的写
    • 2. 根据参数只导出指定列
    • 3. 指定写入的列
    • 4. 复杂头写入
    • 5. 重复多次写入
    • 6. 日期、数字或者自定义格式转换
    • 7. 图片导出(五种方式写入图片)
    • 8. 根据模板写入
    • 9. 列宽、行高(使用注解控制)
    • 10. 注解形式自定义样式
    • 11. 拦截器形式自定义样式
    • 12. 合并单元格
    • 13. 使用table去写入
    • 14. 动态头,实时生成头写入
    • 15. 自动列宽(不太精确)
    • 16. 对单元格进行操作:下拉,超链接等自定义拦截器
    • 17. 可变标题处理(包括标题国际化等)
    • 18. 不创建对象的写
  • 5. 照葫芦画瓢-自定义
    • 5.1 写Excel工具类:
    • 5.2 公共读工具类:
    • 问题记录:

1. 写在最前

不吹不黑,这玩意是阿里开源的项目,相比原生的poi来说,用起来确实方便。github地址:https://github.com/alibaba/easyexcel

这里简单记录下自己的使用过程,以及翻阅源码,学习下阿里大佬是怎么写代码的。我是从1.0.4版本过来的,此版本的源码相对简单些,如果感觉新版本源码过多的话,可以先从1.0.4撸起。

1.1 EasyExcel版本

为了避免由于版本不一致导致,后面的demo中某些类无法引入的问题,我这里使用的是easyExcel 2.2.6版本的,也是写这篇博客时最新的版本。

     <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version><!-- <version>1.0.4</version> --></dependency>

1.2 初探源码

目前easyExcel的源码包结构如图:

在包的最外面,独立出来了四个类,可见这四个货举足轻重。

真正开箱即用的读写类就三个文件,因为EasyExcel.java 就是空的,加上它的理由是为了名称看起来更好。哈哈,这个理由有点意思。

/*** This is actually {@link EasyExcelFactory}, and short names look better.** @author jipengfei*/
public class EasyExcel extends EasyExcelFactory {}

可以看到其继承了EasyExcelFactory,里面包含了读和写的各种方法。

2. 表头实体类 MyUser

这里加上一个BaseModel 类,由于从低版本的easyExcel过来的,在低版本中需要表头的类继承BaseRowModel,高版本中舍弃了此类,无需继承,可直接定义表头类。为了构造通用的工具类,这里我仍然使用了一个BaseModel 类,为了在后面的通用工具类里面使用泛型。

public class BaseModel {}

MyUser 此类作为demo中的实体类,对应读写excel文件的表头类。

import com.alibaba.excel.annotation.ExcelProperty;
import com.lin.test.excel.write.BaseModel;public class MyUser extends  BaseModel {@ExcelProperty(value ={"82班记录表","姓名"},index = 0)private String name;@ExcelProperty(value ={"82班记录表","年龄"},index = 1)private Integer age;@ExcelProperty(value ={"82班记录表","学号"},index = 2)private String idNum;public MyUser() {super();}public MyUser(String name, Integer age, String idNum) {super();this.name = name;this.age = age;this.idNum = idNum;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getIdNum() {return idNum;}public void setIdNum(String idNum) {this.idNum = idNum;}@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("{\"name\":\"");builder.append(name);builder.append("\", \"age\":\"");builder.append(age);builder.append("\", \"idNum\":\"");builder.append(idNum);builder.append("\"}");return builder.toString();}
}
 public static List<MyUser> getUserAll(){List<MyUser> myUsers = new ArrayList<MyUser>();MyUser myUser = null;for (int i = 0; i < 5; i++) {myUser = new MyUser("name" + i, i+1, "idNum" + i);myUsers.add(myUser);}return myUsers;}

3. 最简单的导出Excel文件

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteWorkbook;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.lin.test.excel.entity.MyUser;
import com.lin.test.excel.write.BaseExcelWriter;
import com.lin.test.excel.write.CustomEasyExcel;/*** 使用目前最新版本2.2.6版本的easyExcel* @author linmengmeng* @date 2020年8月13日 下午9:19:55*/
public class NewTestWriteExcel {// 文件输出位置private static String outPath_xlsx = "C:\\Users\\lmm\\Desktop\\testWrite.xlsx";private static String outPath_xls = "C:\\Users\\lmm\\Desktop\\testWrite.xls";public static void main(String[] args) {customMyResourceCode();System.out.println("-----0k-----");}public static void customMyResourceCode() {FileOutputStream outputStream = null;try {outputStream = new FileOutputStream(outPath_xls);} catch (FileNotFoundException e) {e.printStackTrace();}List<MyUser> userList = OldTestWriteExcel.getUserAll();WriteWorkbook writeWorkbook = new WriteWorkbook();writeWorkbook.setClazz(MyUser.class);writeWorkbook.setExcelType(ExcelTypeEnum.XLS);writeWorkbook.setOutputStream(outputStream);WriteSheet writeSheet = new WriteSheet();writeSheet.setSheetNo(0);writeSheet.setSheetName("手动设置sheetName");ExcelWriter excelWriter = new ExcelWriter(writeWorkbook);excelWriter.write(userList, writeSheet);excelWriter.finish();try {outputStream.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}

4. 源码demo:

4.1 读Excel

读取时需要先定义模板的数据读取类,可以是总的工具类,又或者是某个具体的功能对应的实体类。

另外一个就是监听器,监听器可以在读取时检测异常或者数据格式校验,校验通过的数据,可以在监听器里面暂存读取的内容,内容也即是我们定义的数据读取类。最简单的就是讲读取的内容暂存在List里面,如果我们需要校验excel里面的内容的话,可以将当前读取的行号给存起来,然后在校验失败时,可以在业务层外面,拿到行号和错误的异常信息。

下面先看下源码的demo是怎么处理读取事件的,后面贴上我自己用到的读取工具类。

1. 最简单的读

    /*** 最简单的读* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}* <p>* 3. 直接读即可*/@Testpublic void simpleRead() {// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去// 写法1:String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();// 写法2:fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";ExcelReader excelReader = null;try {excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();ReadSheet readSheet = EasyExcel.readSheet(0).build();excelReader.read(readSheet);} finally {if (excelReader != null) {// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的excelReader.finish();}}}

模板的读取类

import java.util.ArrayList;
import java.util.List;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;/*** 模板的读取类** @author Jiaju Zhuang*/
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);/*** 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收*/private static final int BATCH_COUNT = 5;List<DemoData> list = new ArrayList<DemoData>();/*** 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。*/private DemoDAO demoDAO;public DemoDataListener() {// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数demoDAO = new DemoDAO();}/*** 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来** @param demoDAO*/public DemoDataListener(DemoDAO demoDAO) {this.demoDAO = demoDAO;}/*** 这个每一条数据解析都会来调用** @param data*            one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context*/@Overridepublic void invoke(DemoData data, AnalysisContext context) {LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));list.add(data);// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif (list.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listlist.clear();}}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();LOGGER.info("所有数据解析完成!");}/*** 加上存储数据库*/private void saveData() {LOGGER.info("{}条数据,开始存储数据库!", list.size());demoDAO.save(list);LOGGER.info("存储数据库成功!");}
}
/*** 基础数据类.这里的排序和excel里面的排序一致** @author Jiaju Zhuang**/
@Data
public class DemoData {private String string;private Date date;private Double doubleData;
}

可以看到每次读取一行,都会调用invoke方法,

2. 指定列的下标或者列名

    /*** 指定列的下标或者列名** <p>* 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}* <p>* 3. 直接读即可*/@Testpublic void indexOrNameRead() {String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 这里默认读取第一个sheetEasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();}
import java.util.Date;import com.alibaba.excel.annotation.ExcelProperty;import lombok.Data;/*** 基础数据类** @author Jiaju Zhuang**/
@Data
public class IndexOrNameData {/*** 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配*/@ExcelProperty(index = 2)private Double doubleData;/*** 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据*/@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;
}
import java.util.ArrayList;
import java.util.List;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;/*** 模板的读取类** @author Jiaju Zhuang*/
public class IndexOrNameDataListener extends AnalysisEventListener<IndexOrNameData> {private static final Logger LOGGER = LoggerFactory.getLogger(IndexOrNameDataListener.class);/*** 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收*/private static final int BATCH_COUNT = 5;List<IndexOrNameData> list = new ArrayList<IndexOrNameData>();@Overridepublic void invoke(IndexOrNameData data, AnalysisContext context) {LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));list.add(data);if (list.size() >= BATCH_COUNT) {saveData();list.clear();}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {saveData();LOGGER.info("所有数据解析完成!");}/*** 加上存储数据库*/private void saveData() {LOGGER.info("{}条数据,开始存储数据库!", list.size());LOGGER.info("存储数据库成功!");}
}

4.2 写Excel

测试类:com.alibaba.easyexcel.test.demo.write.WriteTest

/*** 基础数据类** @author Jiaju Zhuang**/
@Data
public class DemoData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;@ExcelProperty("数字标题")private Double doubleData;/*** 忽略这个字段*/@ExcelIgnoreprivate String ignore;
}

1. 最简单的写

    /*** 最简单的写* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 直接写即可*/@Testpublic void simpleWrite() {// 写法1String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());// 写法2fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写ExcelWriter excelWriter = null;try {excelWriter = EasyExcel.write(fileName, DemoData.class).build();WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();excelWriter.write(data(), writeSheet);} finally {// 千万别忘记finish 会帮忙关闭流if (excelWriter != null) {excelWriter.finish();}}}

2. 根据参数只导出指定列

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

3. 指定写入的列

    /*** 指定写入的列* <p>* 1. 创建excel对应的实体对象 参照{@link IndexData}* <p>* 2. 使用{@link ExcelProperty}注解指定写入的列* <p>* 3. 直接写即可*/@Testpublic void indexWrite() {String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());}
/*** 基础数据类** @author Jiaju Zhuang**/
@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;
}

4. 复杂头写入

    /*** 复杂头写入* <p>* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}* <p>* 2. 使用{@link ExcelProperty}注解指定复杂的头* <p>* 3. 直接写即可*/@Testpublic void complexHeadWrite() {String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());}

头部实体类。

/*** 复杂头数据.这里最终效果是第一行就一个主标题,第二行分类** @author Jiaju Zhuang**/
@Data
public class ComplexHeadData {@ExcelProperty({"主标题", "字符串标题"})private String string;@ExcelProperty({"主标题", "日期标题"})private Date date;@ExcelProperty({"主标题", "数字标题"})private Double doubleData;
}

5. 重复多次写入

可分页读取数据,逐次写入,最后使用excelWriter.finish();关闭资源。也可在同一个Excel里面定义不同的Sheet直接一次性生成文件。注意sheetNo和SheetName必须不一样。

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

6. 日期、数字或者自定义格式转换

    /*** 日期、数字或者自定义格式转换* <p>* 1. 创建excel对应的实体对象 参照{@link ConverterData}* <p>* 2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解* <p>* 3. 直接写即可*/@Testpublic void converterWrite() {try {System.out.println(TestFileUtil.getPath());String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data());System.out.println("-----ok------");} catch (Exception e) {e.printStackTrace();}}
/*** 基础数据类.这里的排序和excel里面的排序一致** @author Jiaju Zhuang**/
@Data
public class ConverterData {/*** 我想所有的 字符串起前面加上"自定义:"三个字*/@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)private String string;/*** 我想写到excel 用年月日的格式*/@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")@ExcelProperty("日期标题")private Date date;/*** 我想写到excel 用百分比表示*/@NumberFormat("#.##%")@ExcelProperty(value = "数字标题")private Double doubleData;
}

自定义处理规范

/*** String and string converter** @author Jiaju Zhuang*/
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);}}

7. 图片导出(五种方式写入图片)

    /*** 图片导出* <p>* 1. 创建excel对应的实体对象 参照{@link ImageData}* <p>* 2. 直接写即可*/@Testpublic 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);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();}}}
import java.io.File;
import java.io.InputStream;
import java.net.URL;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.converters.string.StringImageConverter;import lombok.Data;/*** 图片导出类** @author Jiaju Zhuang*/
@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;/*** 根据url导出** @since 2.1.1*/private URL url;
}

8. 根据模板写入

    /*** 根据模板写入* <p>* 1. 创建excel对应的实体对象 参照{@link IndexData}* <p>* 2. 使用{@link ExcelProperty}注解指定写入的列* <p>* 3. 使用withTemplate 写取模板* <p>* 4. 直接写即可*/@Testpublic void templateWrite() {String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());}

9. 列宽、行高(使用注解控制)

    /*** 列宽、行高* <p>* 1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}* <p>* 2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度* <p>* 3. 直接写即可*/@Testpublic void widthAndHeightWrite() {String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());}
import java.util.Date;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;import lombok.Data;/*** 基础数据类** @author Jiaju Zhuang**/
@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;
}

10. 注解形式自定义样式

    /*** 注解形式自定义样式* <p>* 1. 创建excel对应的实体对象 参照{@link DemoStyleData}* <p>* 3. 直接写即可** @since 2.2.0-beta1*/@Testpublic void annotationStyleWrite() {String fileName = TestFileUtil.getPath() + "annotationStyleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, DemoStyleData.class).sheet("模板").doWrite(data());}
import java.util.Date;import org.apache.poi.ss.usermodel.FillPatternType;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ContentFontStyle;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadStyle;import lombok.Data;/*** 样式的数据类** @author Jiaju Zhuang**/
@Data
// 头背景设置成红色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 10)
// 头字体设置成20
@HeadFontStyle(fontHeightInPoints = 20)
// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 17)
// 内容字体设置成20
@ContentFontStyle(fontHeightInPoints = 20)
public class DemoStyleData {// 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14)// 字符串的头字体设置成20@HeadFontStyle(fontHeightInPoints = 30)// 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40)// 字符串的内容字体设置成20@ContentFontStyle(fontHeightInPoints = 30)@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;@ExcelProperty("数字标题")private Double doubleData;
}

11. 拦截器形式自定义样式

    /*** 拦截器形式自定义样式* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 创建一个style策略 并注册* <p>* 3. 直接写即可*/@Testpublic void handlerStyleWrite() {String fileName = TestFileUtil.getPath() + "handlerStyleWrite" + 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());}

12. 合并单元格

   /*** 合并单元格* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData} {@link DemoMergeData}* <p>* 2. 创建一个merge策略 并注册* <p>* 3. 直接写即可** @since 2.2.0-beta1*/@Testpublic void mergeWrite() {// 方法1 注解String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";// 在DemoStyleData里面加上ContentLoopMerge注解// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(data());// 方法2 自定义合并单元格策略fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";// 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());}

等于是将固定的位置进行合并,如果存在不确定的Excel数据内容,硬编码合并单元格不太适用。

/*** 样式的数据类** @author Jiaju Zhuang**/
@Data
// 将第6-7行的2-3列合并成一个单元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {// 这一列 每隔2行 合并单元格@ContentLoopMerge(eachRow = 2)@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;@ExcelProperty("数字标题")private Double doubleData;
}

13. 使用table去写入

    /*** 使用table去写入* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 然后写入table即可*/@Testpublic void tableWrite() {String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";// 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例// 这里 需要指定写用哪个class去写ExcelWriter excelWriter = null;try {excelWriter = EasyExcel.write(fileName, DemoData.class).build();// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();// 第一次写入会创建头excelWriter.write(data(), writeSheet, writeTable0);// 第二次写如也会创建头,然后在第一次的后面写入数据excelWriter.write(data(), writeSheet, writeTable1);} finally {// 千万别忘记finish 会帮忙关闭流if (excelWriter != null) {excelWriter.finish();}}}

14. 动态头,实时生成头写入

即是使用了表头生成策略的实体类以外的另一种方式,使用List去存储表头,写入时根据List的内容生成表头。

    /*** 动态头,实时生成头写入* <p>* 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据** <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 然后写入table即可*/@Testpublic void dynamicHeadWrite() {String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName)// 这里放入动态头.head(head()).sheet("模板")// 当然这里数据也可以用 List<List<String>> 去传入.doWrite(data());}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;}private List<DemoData> data() {List<DemoData> list = new ArrayList<DemoData>();for (int i = 0; i < 10; i++) {DemoData data = new DemoData();data.setString("字符串" + i);data.setDate(new Date());data.setDoubleData(0.56);list.add(data);}return list;}

15. 自动列宽(不太精确)

    /*** 自动列宽(不太精确)* <p>* 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照 {@link LongestMatchColumnWidthStyleStrategy}重新实现.* <p>* poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。** <p>* 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData}* <p>* 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy}* <p>* 3. 直接写即可*/@Testpublic void longestMatchColumnWidthWrite() {String fileName =TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, LongestMatchColumnWidthData.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());}private List<LongestMatchColumnWidthData> dataLong() {List<LongestMatchColumnWidthData> list = new ArrayList<LongestMatchColumnWidthData>();for (int i = 0; i < 10; i++) {LongestMatchColumnWidthData data = new LongestMatchColumnWidthData();data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i);data.setDate(new Date());data.setDoubleData(1000000000000.0);list.add(data);}return list;}
@Data
public class LongestMatchColumnWidthData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题很长日期标题很长日期标题很长很长")private Date date;@ExcelProperty("数字")private Double doubleData;
}
import java.util.HashMap;
import java.util.List;
import java.util.Map;import org.apache.poi.ss.usermodel.Cell;import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;/*** Take the width of the longest column as the width.* <p>* This is not very useful at the moment, for example if you have Numbers it will cause a newline.And the length is not* exactly the same as the actual length.** @author Jiaju Zhuang*/
public class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {private static final int MAX_COLUMN_WIDTH = 255;private Map<Integer, Map<Integer, Integer>> cache = new HashMap<Integer, Map<Integer, Integer>>(8);@Overrideprotected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head,Integer relativeRowIndex, Boolean isHead) {boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);if (!needSetWidth) {return;}Map<Integer, Integer> maxColumnWidthMap = cache.get(writeSheetHolder.getSheetNo());if (maxColumnWidthMap == null) {maxColumnWidthMap = new HashMap<Integer, Integer>(16);cache.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);}Integer columnWidth = dataLength(cellDataList, cell, isHead);if (columnWidth < 0) {return;}if (columnWidth > MAX_COLUMN_WIDTH) {columnWidth = MAX_COLUMN_WIDTH;}Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());if (maxColumnWidth == null || columnWidth > maxColumnWidth) {maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);}}private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {if (isHead) {return cell.getStringCellValue().getBytes().length;}CellData cellData = cellDataList.get(0);CellDataTypeEnum type = cellData.getType();if (type == null) {return -1;}switch (type) {case STRING:return cellData.getStringValue().getBytes().length;case BOOLEAN:return cellData.getBooleanValue().toString().getBytes().length;case NUMBER:return cellData.getNumberValue().toString().getBytes().length;default:return -1;}}
}

16. 对单元格进行操作:下拉,超链接等自定义拦截器

    /*** 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)* <p>* demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 注册拦截器 {@link CustomCellWriteHandler} {@link CustomSheetWriteHandler}* <p>* 2. 直接写即可*/@Testpublic 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());}
import java.util.List;import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Hyperlink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.AbstractCellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;/*** 自定义拦截器。对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel** @author Jiaju Zhuang*/
public class CustomCellWriteHandler extends AbstractCellWriteHandler {private static final Logger LOGGER = LoggerFactory.getLogger(CustomCellWriteHandler.class);@Overridepublic 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);}}}
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;/*** 自定义拦截器.对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2** @author Jiaju Zhuang*/
public class CustomSheetWriteHandler implements SheetWriteHandler {private static final Logger LOGGER = LoggerFactory.getLogger(CustomSheetWriteHandler.class);@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {LOGGER.info("第{}个Sheet写入成功。", writeSheetHolder.getSheetNo());// 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试1", "测试2"});DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);writeSheetHolder.getSheet().addValidationData(dataValidation);}
}

17. 可变标题处理(包括标题国际化等)

    /*** 可变标题处理(包括标题国际化等)* <p>* 简单的说用List<List<String>>的标题 但是还支持注解* <p>* 1. 创建excel对应的实体对象 参照{@link ConverterData}* <p>* 2. 直接写即可*/@Testpublic void variableTitleWrite() {// 写法1String fileName = TestFileUtil.getPath() + "variableTitleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, ConverterData.class).head(variableTitleHead()).sheet("模板").doWrite(data());}private List<List<String>> variableTitleHead() {List<List<String>> list = new ArrayList<List<String>>();List<String> head0 = new ArrayList<String>();head0.add("string" + System.currentTimeMillis());List<String> head1 = new ArrayList<String>();head1.add("number" + System.currentTimeMillis());List<String> head2 = new ArrayList<String>();head2.add("date" + System.currentTimeMillis());list.add(head0);list.add(head1);list.add(head2);return list;}

18. 不创建对象的写

    /*** 不创建对象的写*/@Testpublic void noModelWrite() {// 写法1String fileName = TestFileUtil.getPath() + "noModelWrite" + 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;}private List<List<Object>> dataList() {List<List<Object>> list = new ArrayList<List<Object>>();for (int i = 0; i < 10; i++) {List<Object> data = new ArrayList<Object>();data.add("字符串" + i);data.add(new Date());data.add(0.56);list.add(data);}return list;}

5. 照葫芦画瓢-自定义

5.1 写Excel工具类:

使用2.2.6版本的jar包

         <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version><!-- <version>1.0.4</version> --></dependency>
import java.util.List;import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.exception.ExcelGenerateException;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteWorkbook;/*** 自定义 easyExcel 导出excel的工具类* @author linmengmeng* @date 2020年8月11日 下午2:48:07* @param <T> 表头实体类*/
public class BaseExcelWriter extends ExcelWriter {public BaseExcelWriter(WriteWorkbook writeWorkbook) {super(writeWorkbook);}protected static final String DEFAULT_SHEET_NAME = "Sheet1";protected static final Integer DEFAULT_SHEET_NO = 1;protected static final int DEFAULT_HEAD_LINE_Num = 1;protected WriteSheet writeSheet;/*** custom writeSheet* @return*/protected void setWriteSheet(Integer sheetNo, String sheetName){WriteSheet writeSheet = new WriteSheet();writeSheet.setSheetNo(sheetNo);writeSheet.setSheetName(sheetName); this.writeSheet = writeSheet;}protected WriteSheet getWriteSheet(){if (writeSheet == null) {setWriteSheet(DEFAULT_SHEET_NO, DEFAULT_SHEET_NAME);}return writeSheet;}/*** custom headLine* @return*/protected int getHeadLineNum(){return DEFAULT_HEAD_LINE_Num;}  /*** 由子类自定义实现 处理待输出的内容 *    如:数据库查出来的code 处理成中文* @param objectList* @return*/protected List<? extends BaseModel> formateData(List<? extends BaseModel> objectList){return objectList;}/*** 输出缓冲层  往第一个sheet的第一张表中写入数据* 由于数据库查出来的是LinkedMap,这里加了formateData转换List<Object> objectList至List<T>* 未测试分页查询循环写入* @auther linmengmeng* @Date 2020-08-12 下午5:05:19* @param objectList* @param baseExcelWriter* @return*/public ExcelWriter consumeWrite(List<? extends BaseModel> objectList, BaseExcelWriter baseExcelWriter) {if (objectList == null || objectList.isEmpty()) {return this;}if (baseExcelWriter == null) {throw new IllegalArgumentException("baseExcelWriter can not be null");}List<? extends BaseModel> data = baseExcelWriter.formateData(objectList);if (data == null || data.isEmpty()) {throw new ExcelGenerateException("the output must not be empty");}super.write(data, baseExcelWriter.getWriteSheet());return this;}/*** 分页查询循环写入  * 放弃此方法,可以在外层循环调用上面的consumeWrite即可*/
//  public ExcelWriter repeatedWrite(List<Object> objectList, BaseExcelWriter<T> baseExcelWriter, WriteSheet writeSheet) {//      this = consumeWrite(objectList, baseExcelWriter, writeSheet);
//      return baseExcelWriter;
//  }}

BaseModel.java

/*** 空的类  如果存在多个模块导出数据,可以以此类作为模块总的父类*  继承此类,可以直接使用工具类BaseExcelWriter或者CustomEasyExcel* @author linmengmeng* @date 2020年8月24日 上午9:11:28*/
public class BaseModel {}

MyUser Demo:


import java.util.ArrayList;
import java.util.List;import com.alibaba.excel.write.metadata.WriteWorkbook;
import com.lin.test.excel.entity.MyUser;public class MyUserExcelWrite extends BaseExcelWriter{public MyUserExcelWrite(WriteWorkbook writeWorkbook) {super(writeWorkbook);}@Overrideprotected List<? extends BaseModel> formateData(List<? extends BaseModel> objectList) {List<MyUser> myUsers = new ArrayList<MyUser>();MyUser myUser = null;for (BaseModel baseModel : objectList) {if (baseModel instanceof MyUser) {myUser = (MyUser) baseModel;//这里可以对myUser实体类的字段进行格式化或者code转msgmyUsers.add(myUser);}}return myUsers;}}

又或者使用下面的总的工具类:


import java.util.List;import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.exception.ExcelGenerateException;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;/*** easyexcel 2.0.6版本可用的自定义输出工具类* @author linmengmeng* @date 2020年8月24日 上午9:13:17*/
public class CustomEasyExcel extends EasyExcel {private static String DEFAULT_SHEET_NAME = "Sheet1";private static Integer DEFAULT_SHEET_NO = 0;protected WriteSheet getWriteSheet(){//ExcelWriterSheetBuilder excelWriterSheetBuilder = MyCustomEasyExcel.writerSheet(DEFAULT_SHEET_NO, DEFAULT_SHEET_NAME);WriteSheet writeSheet = new WriteSheet();writeSheet.setSheetNo(DEFAULT_SHEET_NO);writeSheet.setSheetName(DEFAULT_SHEET_NAME);return writeSheet;}/*** 自定义Excel样式内容* @return*/protected HorizontalCellStyleStrategy customCellStyle(){return getDefaultStyle();}/*** 自定义Excel样式 头的策略* @return*/protected WriteCellStyle customHeadWriteCellStyle(WriteCellStyle headWriteCellStyle){if (headWriteCellStyle == null) {headWriteCellStyle = new WriteCellStyle();// 背景色headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());WriteFont headWriteFont = new WriteFont();headWriteFont.setFontHeightInPoints((short) 12);headWriteCellStyle.setWriteFont(headWriteFont);}return headWriteCellStyle;}/*** 自定义Excel样式 内容的策略* @return*/protected WriteCellStyle customContentWriteCellStyle(WriteCellStyle contentWriteCellStyle){if (contentWriteCellStyle == null) {contentWriteCellStyle = new WriteCellStyle();WriteFont contentWriteFont = new WriteFont();// 字体大小contentWriteFont.setFontHeightInPoints((short) 12);contentWriteCellStyle.setWriteFont(contentWriteFont);//设置 自动换行contentWriteCellStyle.setWrapped(true);//设置 垂直居中contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//          //设置 水平居中contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//          //设置边框样式contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);contentWriteCellStyle.setBorderTop(BorderStyle.THIN);contentWriteCellStyle.setBorderRight(BorderStyle.THIN);contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);}return contentWriteCellStyle;}/*** 默认Excel样式内容* 默认灰色背景表头,宋体 12号 表头字体加粗 单线边框* @return*/private HorizontalCellStyleStrategy getDefaultStyle(){// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现,自己实现后将实现类传入writeSheet.registerWriteHandler()return new HorizontalCellStyleStrategy(customHeadWriteCellStyle(null), customContentWriteCellStyle(null));}/*** 单个数据源,写入单个sheet里面* @auther linmengmeng* @Date 2020-08-18 下午3:47:29* @param objectList* @param customEasyExcel*/public void customWriteOneSheet(List<? extends BaseModel> objectList, CustomEasyExcel customEasyExcel, ExcelWriter excelWriter){if (customEasyExcel == null) {throw new IllegalArgumentException("customEasyExcel can not be null");}List<? extends BaseModel> data = customEasyExcel.formateData(objectList);if (data == null || data.isEmpty()) {throw new ExcelGenerateException("the output must not be empty");}WriteSheet writeSheet;writeSheet = EasyExcel.writerSheet(0, "默认sheetName").head(objectList.get(0).getClass())
//                .registerWriteHandler(this.customCellStyle()).registerWriteHandler(customEasyExcel.customCellStyle())//.registerWriteHandler(new CustomCellWriteHandler()).build();// 分页去数据库查询数据 这里可以去数据库查询每一页的数据//List<MyUser> data = OldTestWriteExcel.getUser();excelWriter.write(data, writeSheet);}/*** 由子类自定义实现 处理待输出的内容 *   如:数据库查出来的code 处理成中文* @param objectList* @return*/protected List<? extends BaseModel> formateData(List<? extends BaseModel> objectList){return objectList;}
}

默认使用方式,使用上面默认的样式,如需自定义样式,可以实现上面的CustomEasyExcel 类,重写对应方法即可:

    public static void customBaseTestStyle() {String outPath_xlsx = "C:\\Users\\jadl\\Desktop\\testWrite.xlsx";FileOutputStream outputStream = null;try {outputStream = new FileOutputStream(outPath_xlsx);} catch (FileNotFoundException e) {e.printStackTrace();}//创建ExcelWriter 对象,这里使用的绝对路径,可以看下EasyExcel.write()方法,支持多种参数,web端一般使用response操作输出流ExcelWriter excelWriter = EasyExcel.write(outputStream).build();//这里使用的默认样式,自定义时改成子类实例对象即可。CustomEasyExcel customEasyExcel = new CustomEasyExcel();//模拟待导出数据List<MyUser> userList = getUser();//执行写入数据到Excel流中customEasyExcel.customWriteOneSheet(userList, customEasyExcel, excelWriter);// finish 关闭流excelWriter.finish();}public static List<MyUser> getUser(){List<MyUser> myUsers = new ArrayList<MyUser>();MyUser myUser = null;for (int i = 0; i < 5; i++) {if (i == 2) {myUser = new MyUser();}else if (i == 3) {myUser = new MyUser("name" + i, null, "idNum" + i);}else {myUser = new MyUser("name" + i, i+1, "idNum" + i);}myUsers.add(myUser);}return myUsers;}

5.2 公共读工具类:

easyExcel的读取excel使用的是类似监听器的方式,首先定义读取某个excel的监听器,然后使用监听器去逐个读取excel单元格数据。

定义一个公共的基类监听器:BaseModelExcelListener

package com.jadlsoft.utils.excel;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.read.context.AnalysisContext;
import com.alibaba.excel.read.event.AnalysisEventListener;
import com.alibaba.excel.read.exception.ExcelAnalysisException;
import com.jadlsoft.utils.DateUtils;/*** 带有校验结果的模型 解析监听器 * @author linmengmeng* @date 2020年8月6日 上午9:05:38* @param BaseRowModel*/
public class BaseModelExcelListener extends AnalysisEventListener<BaseRowModel> {protected Logger log = Logger.getLogger(BaseModelExcelListener.class);/*** 暂存读取的数据  <当前行号, data>*/private Map<Integer, BaseRowModel> dataMap = new HashMap<Integer, BaseRowModel>();/*** checkResultInfo为校验结果* * 可以自定义校验结果类型*/protected Object checkResultInfo;/*** 自定义读取位置*/protected Sheet sheet;/*** 默认从excel的第一个表的第一行开始读取* 子类可自定义实现读取位置* @return sheet*/public Sheet getSheet() {if (sheet == null) {return new Sheet(1, 1);}return sheet; }@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {}public Map<Integer, BaseRowModel> getDatas() {return dataMap;}@Overridepublic void invoke(BaseRowModel baseRowModel, AnalysisContext context) {//存在非法数据时直接抛出异常,不用接着往下读取Excel中的数据了if (checkResultInfo != null) {throw new ExcelAnalysisException(checkResultInfo.toString());}Object customCheck = customCheckSheet(baseRowModel, context);if (customCheck != null) {checkResultInfo = customCheck;return;}dataMap.put(context.getCurrentRowNum()+1, baseRowModel);}/*** 交由子类自定义实现校验规则 * @param object* @param currentRowNum* @return*/protected Object customCheckSheet(BaseRowModel object, AnalysisContext context) {return null;}public Object getCheckResultInfo(){return checkResultInfo;}/*** 核验日期格式  可为空,不为空则必须为yyyyMMdd格式* @auther linmengmeng* @Date 2020-08-05 下午5:16:55* @return boolean*/protected boolean checkDateStr(String dateStr){if (StringUtils.isBlank(dateStr)) {return true;}if (dateStr.trim().length() != 8) {return false; }Pattern pattern = Pattern.compile("[0-9]+");Matcher isNum = pattern.matcher(dateStr);if(!isNum.matches()){return false;}if (compareDateStr(dateStr, DateUtils.DATE_PATTERN_NO_SPLIT, new Date()) > 0) {return false;}return true;}/*** 判断时间字符串 时间*     在date之前返回-1   之后返回 1  同一天返回0* @auther linmengmeng* @Date 2020-08-05 下午6:02:10* @param dateStr 时间字符串* @param formatter 时间格式*         yyyyMMdd*       yyyy-MM-dd*         yyyy-MM-dd HH:mm:ss* @param date 对比时间* @return*/protected int compareDateStr(String dateStr, String formatter, Date date){Date createDate = DateUtils.createDate(dateStr, formatter);if (createDate != null) {return createDate.compareTo(date);}return 0;}/*** 是否是非空数字字符串* @auther linmengmeng* @Date 2020-08-11 上午8:58:08* @param numberStr 数字字符串* @param strLength 长度,  null时不校验长度* @return 非空数字字符串:true */protected boolean isNumberStr(String numberStr, Integer strLength){if (StringUtils.isEmpty(numberStr)) {return false;}if (strLength != null) {if (numberStr.length() < strLength) {return false;}}Pattern pattern = Pattern.compile("[0-9]+");Matcher isNum = pattern.matcher(numberStr);if(isNum.matches()){return true;}return false;}
}

可以看到该类继承了AnalysisEventListener类,该类为抽象类,只定义了两个抽象方法,具体可由子类实现:

public abstract class AnalysisEventListener<T> {/*** when read one row trigger invoke function** @param object  one row data* @param context read context*/public abstract void invoke(T object, AnalysisContext context);/*** if have something to do after all  read** @param context context*/public abstract void doAfterAllAnalysed(AnalysisContext context);
}

这两个方法,invoke为读取每一行数据之后,会自动执行invoke方法。读取excel数据结束后会执行doAfterAllAnalysed方法。显而易见,数据校验的话,我们这里只需要实现invoke方法即可。

在自定义的BaseModelExcelListener类中定义了一个dataMap用来暂存读取到的excel行数据。

    /*** 暂存读取的数据  <当前行号, data>*/private Map<Integer, BaseRowModel> dataMap = new HashMap<Integer, BaseRowModel>();

其中BaseRowModel为easyExcel中定义的一个空对象,前面有所提到,新版本该类给删掉了,如果使用的新版本,可以自定义一个基类,或者修改下面工具类方法中用到BaseRowModel的地方,直接声明使用泛型即可。

主要看下invoke方法:

 @Overridepublic void invoke(BaseRowModel baseRowModel, AnalysisContext context) {//存在非法数据时直接抛出异常,不用接着往下读取Excel中的数据了if (checkResultInfo != null) {throw new ExcelAnalysisException(checkResultInfo.toString());}Object customCheck = customCheckSheet(baseRowModel, context);if (customCheck != null) {checkResultInfo = customCheck;return;}dataMap.put(context.getCurrentRowNum()+1, baseRowModel);}

这里在校验时,添加了checkResultInfo属性,用来暂存校验结果,如果一旦发现该属性的值为非空,则证明读取内容中,含有非法数据。该方法调用了customCheckSheet,这里也是定义的抽象方法,毕竟每个excel的内容和校验规则不一样,可以继承该类后,自由实现校验规则。

 /*** 交由子类自定义实现校验规则 * @param object* @param currentRowNum* @return*/protected Object customCheckSheet(BaseRowModel object, AnalysisContext context) {return null;}

子类实现customCheckSheet方法,实现自定义校验规则。

package com.jadlsoft.utils.excel.read;
import org.apache.commons.lang.StringUtils;import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.read.context.AnalysisContext;
import com.jadlsoft.domain.excel.QzclInfo;
import com.jadlsoft.utils.excel.BaseModelExcelListener;/*** 核验导入上传的excel中数据有效性 (必填项是否为空,日期是否非法)* @author linmengmeng* @date 2020年8月5日 下午2:46:19*/
public class QzModelExcelListener extends BaseModelExcelListener {/*** 单次上传附件中最大数据量(加上表头占用行)*/private static final Integer FILE_MAX_ROWS_NUM = 502;public QzModelExcelListener() {//super();this.sheet = new Sheet(1, 2, QzclInfo.class);}@Overrideprotected String customCheckSheet(BaseRowModel baseRowModel, AnalysisContext context) {Integer currentRowNum = context.getCurrentRowNum()+1;String errorMsg = null;if (currentRowNum.compareTo(FILE_MAX_ROWS_NUM) > 0) {errorMsg = "单次上传附件中最多上传" + (FILE_MAX_ROWS_NUM-2) + "条数据";}QzclInfo qzclInfo = null;while (baseRowModel instanceof QzclInfo) {qzclInfo = (QzclInfo) baseRowModel;if (StringUtils.isEmpty(qzclInfo.getQzzl())) {return errorMsg = "第" + currentRowNum + "行种类录入错误,请重新录入";}if (StringUtils.isEmpty(qzclInfo.getQzxh())) {return errorMsg = "第" + currentRowNum + "行型号录入错误,请重新录入";}if (StringUtils.isEmpty(qzclInfo.getCxd())) {return errorMsg = "第" + currentRowNum + "行产销地录入错误,请重新录入";}if (StringUtils.isEmpty(qzclInfo.getQh())) {return errorMsg = "第" + currentRowNum + "行号录入错误,请重新录入";}if (StringUtils.isEmpty(qzclInfo.getSccj())) {return errorMsg = "第" + currentRowNum + "行生产厂家录入错误,请重新录入";}if (!checkDateStr(qzclInfo.getScrqStr())) {return errorMsg = "第" + currentRowNum + "行生产日期录入错误,请重新录入";}if (!checkDateStr(qzclInfo.getGmrqStr())) {return errorMsg = "第" + currentRowNum + "行购买日期录入错误,请重新录入";}baseRowModel = null;}return errorMsg;}
}

QzclInfo.java为定义的映射Excel表头的实体类。

package com.jadlsoft.domain.excel;import java.util.Date;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;public class QzclInfo extends BaseRowModel {@ExcelProperty(value ={"种类"},index = 0)private String qzzl;@ExcelProperty(value ={"型号"},index =1)private String qzxh;@ExcelProperty(value ={"产销地"},index = 2)private String cxd;@ExcelProperty(value ={"号"},index = 3)private String qh;//读取excel中的数据@ExcelProperty(value ={"生产日期"},index = 4)private String scrqStr;//实际时间private Date scrq;@ExcelProperty(value ={"生产厂家"},index = 5)private String sccj;@ExcelProperty(value ={"购买日期"},index = 6)private String gmrqStr;private Date gmrq;public String getQzzl() {return qzzl;}public void setQzzl(String qzzl) {this.qzzl = qzzl;}public String getQzxh() {return qzxh;}public void setQzxh(String qzxh) {this.qzxh = qzxh;}public String getCxd() {return cxd;}public void setCxd(String cxd) {this.cxd = cxd;}public String getQh() {return qh;}public void setQh(String qh) {this.qh = qh;}public String getScrqStr() {return scrqStr;}public void setScrqStr(String scrqStr) {this.scrqStr = scrqStr;}public Date getScrq() {return scrq;}public void setScrq(Date scrq) {this.scrq = scrq;}public String getSccj() {return sccj;}public void setSccj(String sccj) {this.sccj = sccj;}public Date getGmrq() {return gmrq;}public void setGmrq(Date gmrq) {this.gmrq = gmrq;}public String getGmrqStr() {return gmrqStr;}public void setGmrqStr(String gmrqStr) {this.gmrqStr = gmrqStr;}@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("{\"qzzl\":\"");builder.append(qzzl);builder.append("\", \"qzxh\":\"");builder.append(qzxh);builder.append("\", \"cxd\":\"");builder.append(cxd);builder.append("\", \"qh\":\"");builder.append(qh);builder.append("\", \"scrqStr\":\"");builder.append(scrqStr);builder.append("\", \"scrq\":\"");builder.append(scrq);builder.append("\", \"sccj\":\"");builder.append(sccj);builder.append("\", \"gmrqStr\":\"");builder.append(gmrqStr);builder.append("\", \"gmrq\":\"");builder.append(gmrq);builder.append("\"}");return builder.toString();}
}

定义工具类结束了,下面看下如何具体使用:
EasyExcelUtil.java工具类中定义读取Excel的方法,这里传入获取到的数据流InputStream ,如何获取这里就不提了,方式有很多,看自己具体的实现了。

/*** 校验excel中数据* @auther linmengmeng* @Date 2020-08-05 下午3:08:40* @param listener 自定义Excel监听器* @param inputStream* @param clazz Excel接收实体类对象* @param excelTypeEnum excel后缀名* @return*/public static Map<String, Object> checkExcelWithModel(BaseModelExcelListener listener, InputStream inputStream, ExcelTypeEnum excelTypeEnum){ExcelReader excelReader = new ExcelReader(inputStream, excelTypeEnum, null, listener);try {excelReader.read(listener.getSheet());} catch (ExcelAnalysisException e) {logger.error("附件内容校验未通过:" + e.getMessage());}Map<String, Object> resultMap = new HashMap<String, Object>();resultMap.put("checkResultInfo", listener.getCheckResultInfo());resultMap.put("resultData", listener.getDatas());return resultMap;}

可以看到,方法中try-catch代码块中监听了ExcelAnalysisException异常,这里对应invok方法中校验结果非空时抛出此异常,

     //存在非法数据时直接抛出异常,不用接着往下读取Excel中的数据了if (checkResultInfo != null) {throw new ExcelAnalysisException(checkResultInfo.toString());}

业务层使用时,直接调用:

         Map<String, Object> checkExcelWithModel = EasyExcelUtil.checkExcelWithModel(new SjsbModelExcelListener(), inputStream, ExcelTypeEnum.XLSX);if (checkExcelWithModel == null || checkExcelWithModel.isEmpty()) {log.error("读取excel文件异常,checkExcelWithModel:" + checkExcelWithModel);}Object checkResultInfo = checkExcelWithModel.get("checkResultInfo");if (checkResultInfo != null) {log.error("excel中包含非法数据,checkResultInfo:" + checkResultInfo.toString());}Object resultData = checkExcelWithModel.get("resultData");if (resultData == null) {log.error("读取excel文件异常,checkExcelWithModel:" + checkExcelWithModel);}@SuppressWarnings("unchecked")Map<Integer, SjsbMxWriteRowModel> sjsbMxMap = (Map<Integer, SjsbMxWriteRowModel>) resultData;if(sjsbMxMap.isEmpty()){log.error("读取excel文件内容为空:" + checkExcelWithModel);}

问题记录:

  1. 设置导出的文件,只读功能为实现。后面有时间再研究研究。
  2. 在读取excel内容时,如果传入的excel文件类型有误(上传的xls格式的文件,读取传入xlsx,或者反过来),则会出现空指针异常。需要手动校验excel文件格式,传入对应的格式即可。

EasyExcel 实践与源码梳理相关推荐

  1. ed2k 网络中搜索资源并选择资源下载的分析及eMule源码梳理

    上一篇博客中,客户端已连接到ed2k网络及客户端与服务器交互的eMule源码梳理,这里将开始搜索资源并下载及客户端与客户端交互的eMule源码梳理 emule 源码下载地址  http://downl ...

  2. Android 源码梳理

    Android 源码梳理 前言 作为霜枫司机一年学习的总结,附上帅照一张. 目录 1. Android系统启动过程分析 2. Linux内核文件系统 3. Android进程间通信源码梳理 4. An ...

  3. Hybrid App 跨平台热更新方案实践 附带源码

    前言 移动开发的跨平台与快速发布一直是开发者的追求,也是技术的一个发展趋势,现在各大厂开始有了自己的大前端团队,所以我们也开始了自己的探索,目前来说主要有两种思路: Hybrid App 代表:Cor ...

  4. 缓存穿透、缓存击穿和缓存雪崩实践附源码

    xiaolyuh@oschina 读完需要 10 分钟 速读仅需 5 分钟 我们使用缓存的主要目是提升查询速度和保护数据库等稀缺资源不被占满. 而缓存最常见的问题是缓存穿透.击穿和雪崩,在高并发下这三 ...

  5. 【ORB-SLAM2源码梳理5】关于双目帧Frame的构造函数

    文章目录 前言 一.双目图像帧Frame的构造函数 二.计算特征点匹配与成功匹配点对的深度ComputeStereoMatches() 三.具体过程 1. 准备阶段 2. 右目图每行特征点统计 3. ...

  6. 《音视频开发进阶指南:基于Android与iOS平台的实践》源码下载地址

    年前买了这本书,想看下随书源码,一开始从CSDN下载频道下载电子书+源码,但那个源码不是这边书的. 从网上找了一段时间,终于找到了(其实在书的前言/勘误和支持中有给出),作者展晓凯的相关网站如下: 作 ...

  7. vite预构建源码梳理

    对于"为什么要进行依赖预构建?"这个问题vite 文档已经解释的很清楚了,那么预构建大概的流程是什么样的呢? 启动预构建 从文档中我们知道在服务启动前会进行预构建,对应源码位置在s ...

  8. 【ORB-SLAM2源码梳理6】Track()函数的第一步:单目初始化MonocularInitialization()

    文章目录 前言 一.Track()函数 二.单目初始化MonocularInitialization() 1. 判断单目初始化器是否创建,若没有就创建. 2. 已创建初始化器,判断特征点数目 3. 在 ...

  9. Linux动态库加载函数dlopen源码梳理(一)

    下载了libc的源码,现在就开始libc源码的学习,最近了解到了linux动态库的相关知识,那么就从linux动态库加载函数dlopen进行梳理学习吧. 如果还没下载libc源码,可通过 https: ...

最新文章

  1. Android4.0 Design之UI设计易犯的错误2
  2. Linux下多线程查看工具(pstree、ps、pstack),linux命令之-pstree使用说明
  3. 李航老师《统计学习方法》的代码实现、课件、作业等相关资源的最全汇总
  4. 2021 EdgeX 中国挑战赛决赛入围名单公布
  5. 工业交换机是什么?矿用交换机采用的是工业级交换机吗?
  6. ABP+AdminLTE+Bootstrap Table权限管理系统第八节--ABP错误机制及AbpSession相关
  7. centos php5.6 phpize,centos7-install-php5.6.40
  8. ATL中建立消息窗口
  9. Python流程控制的 for、 while、循环和嵌套词汇continue、break、range
  10. 23种设计模式设计原则
  11. Qt音视频开发45-视频传输TCP版
  12. CSDN调整图片大小
  13. 宝宝便秘,这些习惯都是元凶!
  14. python爬虫IP地址解析爬取(IP38.com)
  15. problem 1148
  16. python尼姆游戏_python实现聪明的尼姆游戏(人机对战)
  17. java 画立体图形
  18. 红外图像处理:去竖条
  19. 合宙AIR32F103CBT6刷回CMSIS-DAP固件和DAP升级固件以及刷ST-LINK V2-1固件方法
  20. ps切图(6)——参考线及辅助

热门文章

  1. python 重复图片_删除重复文件或图片(去重)的python代码
  2. 【Hadoop HA】搭建Hadoop HA的详细教程
  3. linux下搭建基本web服务
  4. 信号完整性(概念一览表)
  5. 毒液蛋白质相互作用分析
  6. CADD课程学习(10)-- 模拟不同体系与蛋白-蛋白相互作用(ZDOCK)
  7. clang vectorization
  8. Omnet+Sumo+Veins安装配置总结(win7 win10亲测可行)
  9. w指令中的IDLE是什么意思
  10. 计算机系统ARM64拆除炸弹