EasyExcel

  • EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。

依赖:

<!-- EasyExcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.6</version>
</dependency>

常用注解和API

常用类

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

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

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

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

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

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

读取时的注解

@ExcelProperty

使用位置:标准作用在成员变量上

可选属性:

属性名 含义 说明
index 对应Excel表中的列数 默认-1,建议指定时从0开始
value 对应Excel表中的列头
converter 成员变量转换器 自定义转换器需要实Converter接口

使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。

如果不使用@ExcelProperty注解,成员变量从上到下的顺序,对应表格中从左到右的顺序;

使用建议:要么全部不写,要么全部用index,要么全部用名字去匹配,尽量不要三个混着用。

// 1. 修改成员变量顺序读取Excel表格
// 2. 修改index属性值读取Excel表格
// 3. 修改value属性值读取Excel表格

@ExcelIgnore

标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

代码演示:

// 4. 忽略id成员变量值读取Excel表格

@DateTimeFormat

标注在成员变量上,日期转换,代码中用String类型的成员变量去接收excel中日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat

// 5. 按照指定的格式写入Excel内容

@NumberFormat

标注在成员变量上,数字转换,代码中用String类型的成员变量去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat

@ExcelIgnoreUnannotated

标注在类上。

不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty 的注解。

标注该注解后,类中的成员变量如果没有标注@ExcelProperty 注解将不会参与读写。

读取时通用参数

ReadWorkbook(工作簿ExcelReaderBuilder),ReadSheet(工作表ExcelReaderSheetBuilder) 都会有的参数👀👀,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。
  • readListener 监听器,在读取数据的过程中会不断的调用监听器。
  • headRowNumber 指定需要读表格的 列头行数。默认有一行头,也就是认为第二行开始起为数据。
  • headclazz二选一。读取文件头对应的列表,会根据列表匹配数据。建议使用clas,就是文件中每一行数据对应的代码中的实体类型。
  • clazzhead二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。
  • autoTrim 字符串、表头等数据自动trim
  • password 读的时候是否需要使用密码

ReadWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,读取时会自动判断,无需设置。
  • inputStreamfile二选一。建议使用file。
  • fileinputStream二选一。读取文件的文件。
  • autoCloseStream 自动关闭流。
  • readCache 默认小于5M用 内存,超过5M会使用 EhCache,不建议使用这个参数。
  • useDefaultListener @since 2.1.4 默认会加入ModelBuildEventListener 来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到class对象,请调用readListener方法,加入自定义的beforeListenerModelBuildEventListener、 自定义的afterListener即可。

ReadSheet(工作表对象)参数

  • sheetNo 需要读取Sheet的编号,建议使用这个来指定读取哪个Sheet
  • sheetName 根据名字去匹配Sheet,excel 2003不支持根据名字去匹配

写入时的注解

@ExcelProperty

使用位置:标准作用在成员变量上

可选属性:

属性名 含义 说明
index 对应Excel表中的列数 默认-1,指定时建议从0开始
value 对应Excel表中的列头
converter 成员变量转换器 自定义转换器需要实Converter接口

使用效果index 指定写到第几列,如果不指定则根据成员变量位置排序;

value指定写入的列头,如果不指定则使用成员变量的名字作为列头;

​ 如果要设置复杂的头,可以为value指定多个值。

其他注解:

基本和读取时一致

  • @ContentRowHeight() 标注在类上或属性上,指定内容行高
  • @HeadRowHeight() 标注在类上或属性上,指定列头行高
  • @ColumnWidth() 标注在类上或属性上,指定列宽
  • @ExcelIgnore 默认所有字段都会写入excel,这个注解会忽略这个字段
  • @DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat
  • @NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat`
  • @ExcelIgnoreUnannotated默认不加ExcelProperty` 的注解的都会参与读写,加了不会参与

写入时通用参数

ReadWorkbook(工作簿ExcelReaderBuilder),ReadSheet(工作表ExcelReaderSheetBuilder) 都会有的参数👀👀,如果为空,默认使用上级。

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

  • writeHandler 写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用,对使用者透明不可见。

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

  • needHead 是否导出头

  • headclazz二选一。写入文件的头列表,建议使用class。

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

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

WriteWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,默认为xlsx

  • outputStreamfile二选一。写入文件的流

  • fileoutputStream二选一。写入的文件

  • templateInputStream 模板的文件流

  • templateFile 模板文件

  • autoCloseStream 自动关闭流。

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

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

WriteSheet(工作表对象)参数

  • sheetNo 需要写入的编号。默认0

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

读取Excel文件

实体-注意:实体的属性要与表格中的数据位置对应一直

/**
 * Description:
 * Created by 大师兄 on 2021/11/20 16:39
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    /**
     * 学生姓名
     */
    private String name;
    /**
     * 学生性别
     */
    private String gender;

    /**
     * 学生出生日期
     */
    private String birthday;
    /**
     * id
     */
    private String id;
}

调用EasyExcelAPI读取的Excel文件的测试类StudentReadDemo

/**
 * Description:
 * Created by 大师兄 on 2021/11/20 17:01
 */
public class StudentReadDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 读取文件,读取完之后会自动关闭
        /*
            pathName        文件路径;"d:\\test.xls"
            head            每行数据对应的实体;Student.class
            readListener    读监听器(继承AnalysisEventListener),每读一样就会调用一次该监听器的invoke方法
            sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字,不传默认为0
        */
        // 封装工作簿对象
        ExcelReaderBuilder workBook = EasyExcel.read
                ("d:\\test.xlsx", Student.class, new StudentReadListener());
        //workBook.headRowNumber(3); 从索引为3的位置开始读
        // 封装工作表
        ExcelReaderSheetBuilder sheet1 = workBook.sheet();//默认获取第一个工作表
        // 读取
        sheet1.doRead();
    }
}

test.xls:

img点击并拖拽以移动

StudentReadListener:

/**
 * Description:
 * Created by 大师兄 on 2021/11/20 16:53
 */
public class StudentReadListener extends AnalysisEventListener<Student> {
    // 每读一行,会调用该invoke方法一次,在invoke可以操作读取到的数据
    public void invoke(Student data, AnalysisContext context) {
        System.out.println("data = " + data);
    }

    // 全部读完之后,会调用该方法
    public void doAfterAllAnalysed(AnalysisContext context) {
        // TODO......
    }
}

text.xlsx数据:

姓名	 性别	    生日	    ID
TOM	  boy	2021/11/20	1
BOB   boy	2021/11/20	2
SYC	  boy	2021/11/20	3
Sovzn boy	2021/11/20	4
JJK	  boy	2021/11/20	5
ZS	  girl	2021/11/20	6
LS	  girl	2021/11/20	7
WMZ	  girl	2021/11/20	8
ZH	  girl	2021/11/20	9
JJK	  girl	2021/11/20	10

执行结果:

data = Student(name=TOM, gender=boy, birthday=2021-11-20 00:00:00, id=1)
data = Student(name=BOB, gender=boy, birthday=2021-11-20 00:00:00, id=2)
data = Student(name=SYC, gender=boy, birthday=2021-11-20 00:00:00, id=3)
data = Student(name=Sovzn, gender=boy, birthday=2021-11-20 00:00:00, id=4)
data = Student(name=JJK, gender=boy, birthday=2021-11-20 00:00:00, id=5)
data = Student(name=ZS, gender=girl, birthday=2021-11-20 00:00:00, id=6)
data = Student(name=LS, gender=girl, birthday=2021-11-20 00:00:00, id=7)
data = Student(name=WMZ, gender=girl, birthday=2021-11-20 00:00:00, id=8)
data = Student(name=ZH, gender=girl, birthday=2021-11-20 00:00:00, id=9)
data = Student(name=JJK, gender=girl, birthday=2021-11-20 00:00:00, id=10)

Process finished with exit code 0

写操作:

到处数据的实体:

/**
 * Description:
 * Created by 大师兄 on 2021/11/20 17:22
 */
@Data
@AllArgsConstructor
@NoArgsConstructor

@ColumnWidth(20) //栏目宽度
//@HeadRowHeight()
public class Student_write {

    /**
     * id
     */
    //@ExcelProperty(value = "编号",index = 3)
    @ExcelIgnore 
    private String id;
    /**
     * 学生姓名
     */
    @ExcelProperty(value = {"信息表","学生姓名"} index = 0)
    //@ColumnWidth(30)
    private String name;
    /**
     * 学生性别
     */
    @ExcelProperty(value = "学生性别", index = 2)
    private String gender;

    /**
     * 学生出生日期
     */
    @ExcelProperty(value = "学生出生日期", index = 1)
    @DateTimeFormat("yyyy-MM-dd")
    //@ColumnWidth(20)
    private Date birthday;
}
/**
 * Description:
 * Created by 大师兄 on 2021/11/20 17:26
 */
public class StudentWriteDemo {
    public static void main(String[] args) {

        List<Student_write> Student_writes = initData();
        /*
            String pathName 写入文件的路径
            Class head      写入文件的对象类型
            默认写入到07的xlsx中,如果想要写入xls,可以指定类型(待验证)
         */
        ExcelWriterBuilder workBook = EasyExcel.write("d:\\测试表.xlsx", Student_write.class);

        // sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字
        workBook.sheet().doWrite(Student_writes);
    }

    private static List<Student_write> initData() {
        ArrayList<Student_write> Student_writes = new ArrayList<Student_write>();
        Student_write data = new Student_write();
        for (int i = 0; i < 10; i++) {
            data.setName("Sovzn" + i);
            data.setBirthday(new Date());
            data.setGender("男");
            Student_writes.add(data);
        }
        return Student_writes;
    }
}

填充

填充一组数据

Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{}左右大括号,需要在括号前面使用斜杠转义\{\}

​ 代码中用来填充数据的实体对象的成员变量名或用来填充map集合的key需要和Excel中被{}包裹的变量名称一致。

fill_data_template1.xlsx:img点击并拖拽以移动

/**
 * 使用实体类封装填充数据
 *
 *  实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
 */
@Data
public class FillData {

    private String name;
    private int age;
}
/**
 * Description:
 * Created by 大师兄 on 2021/11/20 17:26
 */
public class StudentFillDemo {
    public static void main(String[] args) {
        // 准备模板
        String templateFile = "d:\\JavaFileIOTest\\fill_data_template1.xlsx";
        //String templateFile = "fill_data_template1.xlsx";
        // 写入文件:当前项目下
        String targetFileName = "d:\\JavaFileIOTest\\单组数据填充.xlsx";
        // 准备对象数据填充
        FillData fillData = new FillData();
        fillData.setName("shiyaochang");
        fillData.setAge(10);
        // 创建一个工作簿对象
        ExcelWriterBuilder workBookWriter = EasyExcel.write(targetFileName).withTemplate(templateFile);
        // 创建一个工作表
        ExcelWriterSheetBuilder sheet = workBookWriter.sheet();
        // 获取工作表并填充
        sheet.doFill(fillData);
        // 使用Map数据填充
        //HashMap<String, String> mapFillData = new HashMap<>();
        //mapFillData.put("name", "Map");
        //mapFillData.put("age", "11");
        // 获取第一个工作表填充并自动关闭流
        //workBookWriter.sheet().doFill(mapFillData);

    }
}

填充多组数据

​ Excel表格中用{.} 来表示包裹要填充的变量,如果单元格文本中本来就有{.}左右大括号,需要在括号前面使用斜杠转义\{ .\}

​ 代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。

fill_data_template2.xlsx:img点击并拖拽以移动

/**
 * Description:
 * Created by 大师兄 on 2021/11/20 17:26
 */
public class StudentMutiFillDemo {
    public static void main(String[] args) {
        // 准备模板
        String templateFile = "d:\\JavaFileIOTest\\fill_data_template2.xlsx";
        //String templateFile = "fill_data_template1.xlsx";
        // 写入文件:当前项目下
        String targetFileName = "d:\\JavaFileIOTest\\多组数据填充.xlsx";
        // 准备对象数据填充
        FillData fillData = new FillData();
        fillData.setName("shiyaochang");
        fillData.setAge(10);
        // 创建一个工作簿对象
        ExcelWriterBuilder workBookWriter = EasyExcel.write(targetFileName).withTemplate(templateFile);
        // 创建一个工作表
        ExcelWriterSheetBuilder sheet = workBookWriter.sheet();
        List<FillData> fillDatas = initData();
        // 获取工作表并填充
        sheet.doFill(fillDatas);

    }

    private static List<FillData> initData() {
        ArrayList<FillData> FillDatas = new ArrayList<FillData>();

        for (int i = 0; i < 10; i++) {
            FillData data = new FillData();
            data.setName("Sovzn" + i);
            data.setAge(10);
            FillDatas.add(data);
        }
        return FillDatas;
    }

}

效果:

img点击并拖拽以移动

组合填充

既有单组数据又有多组数据的填充,注:在EasyExcel中执行doXXX方法后会自动关闭流,因此,组合填充中不能再使用doXXX,方式填充完一组数据后关闭流,使后面的数据无法填充。

fill_data_template3.xlsx:img点击并拖拽以移动

/**
 * Description:
 * Created by 大师兄 on 2021/12/06 21:19
 */
public class CombineFillDemo {
    public static void main(String[] args) {
        // 准备模板
        String templateFile = "d:\\JavaFileIOTest\\fill_data_template3.xlsx";
        //String templateFile = "fill_data_template1.xlsx";
        // 写入文件:当前项目下
        String targetFileName = "d:\\JavaFileIOTest\\组合数据填充.xlsx";
        // 创建一个工作簿对象(组合填充时,用build创建工作簿)
        ExcelWriter workbook = EasyExcel.write(targetFileName).withTemplate(templateFile).build();
        // 创建一个工作表
        WriteSheet sheet = EasyExcel.writerSheet().build();
        //数据(单组):
        Map<String, String> DateAndTotal = new HashMap<String, String>() {{
            put("date", "2021-1-1");
            put("total", "目标001");
        }};
        //数据(多组):
        List<FillData> fillDatas = initData();
        // 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行,否则在填充时可能会覆盖下方的单组数据(date和total)
        FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
        //填充:不适用doXXX,直接使用fill
        //1.多组数据填充:
        workbook.fill(fillDatas,fillConfig, sheet);
        //2.单组数据填充:
        workbook.fill(DateAndTotal, sheet);

        //切记:手动关流
        workbook.finish();
    }

    private static List<FillData> initData() {
        ArrayList<FillData> FillDatas = new ArrayList<FillData>();

        for (int i = 0; i < 10; i++) {
            FillData data = new FillData();
            data.setName("Sovzn" + i);
            data.setAge(10);
            FillDatas.add(data);
        }
        return FillDatas;
    }
}

img点击并拖拽以移动

水平填充

填充时需要通过FillConfig对象设置水平填充。

fill_data_template4.xlsx:

img点击并拖拽以移动

/**
 * Description:
 * Created by 大师兄 on 2021/12/06 22:03
 */
public class HORIZONTAL_Fill {
    public static void main(String[] args) {
        // 准备模板
        String templateFile = "d:\\JavaFileIOTest\\fill_data_template4.xlsx";
        //String templateFile = "fill_data_template1.xlsx";
        // 写入文件:当前项目下
        String targetFileName = "d:\\JavaFileIOTest\\水平数据填充.xlsx";
        // 创建一个工作簿对象(组合填充时,用build创建工作簿)
        ExcelWriter workbook = EasyExcel.write(targetFileName).withTemplate(templateFile).build();
        // 创建一个工作表
        WriteSheet sheet = EasyExcel.writerSheet().build();
        //数据:
        List<FillData> fillDatas = initData();
        //-------设置水平填充---------
        FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
        //填充
        workbook.fill(fillDatas,fillConfig, sheet);
        //切记:手动关流
        workbook.finish();
    }

    private static List<FillData> initData() {
        ArrayList<FillData> FillDatas = new ArrayList<FillData>();

        for (int i = 0; i < 10; i++) {
            FillData data = new FillData();
            data.setName("Sovzn" + i);
            data.setAge(10);
            FillDatas.add(data);
        }
        return FillDatas;
    }
}

img点击并拖拽以移动