EasyExcel简介
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
easyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。easyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
具体使用
读取文件
读取文件有两种方式:
1、使用readBySax方法进行读取,该方法适用于读取大量数据。
EasyExcelFactory.readBySax(InputStream in, Sheet sheet, AnalysisEventListener listener)
2、使用read方法读取,该方法适用于读取少量数据。
EasyExcelFactory.read(InputStream in, Sheet sheet)
下面就以上两种方式分别进行具体的使用
一、使用readBySax方法进行读取
1、从上述说明可以看出,该方法需要传入一个自定义监听器对象,因此我们自定义一个监听器,需要继承AnalysisEventListener并指明泛型(该泛型可以为一个自定义的类型,如User类,也可以是其他的类型,后面后具体讲述),重写invoke和doAfterAllAnalysed方法。
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xukun
* @date : 2020/9/2
*/
public class AttrDataListener extends AnalysisEventListener<List<Object>> {
//用于接收解析的所有数据
private List<List<Object>> data = new ArrayList<>();
//提供一个对外访问的方法
public List<List<Object>> getData() {
return data;
}
//记录解析的数据总数
int count = 0;
// easyexcel解析方式为一行一行解析,而每解析一行都会调用invoke方法
// 该方法的第个参数位解析这一行的结果
@Override
public void invoke(List<Object> objects, AnalysisContext analysisContext) {
//将这一行的解析结果添加到总数据集中
data.add(objects);
count++;
}
// 所有都解析完毕后要执行的操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("解析完毕,共" + (count - 1) + "条数据");
}
}
2、准备好待解析的excel文件
3、进行解析
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.Table;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xukun
* @date : 2020/9/2
*/
public class ExcelTest {
public static void main(String[] args) {
System.out.println("===读文件===");
//待解析的文件路径
String filePath1 = "C:\\Users\\SLDT\\Desktop\\utf-8''区域.xlsx";
//第一个sheet,从第0行开始读取数据
Sheet sheet1 = new Sheet(1, 0);
//创建自定义的监听对象,后面要用这个对象去获取解析的数据
AttrDataListener attrDataListener = new AttrDataListener();
try {
//传参,进行解析;会自动调用自定义监听器中的invoke方法
EasyExcelFactory.readBySax(new FileInputStream(new File(filePath1)),sheet1,attrDataListener);
//得到解析的数据
List<List<Object>> data = attrDataListener.getData();
//打印
//打印
for (List<Object> d : data){
System.out.println(d);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
4、结果
附:泛型为指定类型:
被读取的文件:
1、该方式与类的属性一一对应,因此,我们首先创建一个User类,并且该类还要继承自BaseRowModel类同时使用@ExcelProperty注解指明表头名称和位置(若不指定,读取到的数据为空)。
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
/**
* @author : xukun
* @date : 2020/9/3
*/
public class User extends BaseRowModel {
//value的值表示该列的表头名称为“序号” index表示该列的位置,要与被读取的文件的表头一一对应
@ExcelProperty(value = "序号",index = 0)
private Integer id;
@ExcelProperty(value = "学号",index = 1)
private String sno;
@ExcelProperty(value = "姓名",index = 2)
private String sname;
@ExcelProperty(value = "年龄",index = 3)
private Integer age;
//省略Getter、Setter和toString方法
}
2、使用监听器
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import org.apache.poi.ss.formula.functions.Count;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xukun
* @date : 2020/9/3
*/
public class UserListener extends AnalysisEventListener<User> {
private List<User> users = new ArrayList<>();
public List<User> getUsers() {
return users;
}
private int count = 0;
@Override
public void invoke(User user, AnalysisContext context) {
users.add(user);
count++;
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("解析完毕,共解析"+count+"条数据");
}
}
3、读取
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.Table;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xukun
* @date : 2020/9/2
*/
public class ExcelTest {
public static void main(String[] args) {
System.out.println("===读文件===");
//待解析的文件路径
String filePath3 = "C:\\Users\\SLDT\\Desktop\\学生.xlsx";
//第一个sheet,从第1行开始读取数据(第0行,也就是表头,与User不匹配,所以从第1行开始读取)
Sheet sheet3 = new Sheet(1, 1,User.class);
//创建自定义的监听对象,后面要用这个对象去获取解析的数据
UserListener userListener = new UserListener();
try {
//传参,进行解析;会自动调用自定义监听器中的invoke方法
EasyExcelFactory.readBySax(new FileInputStream(new File(filePath3)),sheet3,userListener);
//得到解析的数据
List<User> users = userListener.getUsers();
//打印
for (User user : users){
System.out.println(user);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
注意:在new Sheet时使用的时另一个要传入类对象的构造方法
Sheet sheet3 = new Sheet(1, 1,User.class);
4、结果
二、使用read方法进行读取
1、由于不需要使用监听器,所以我们直接准备待解析的文件即可。(其实源码里面自己创建了一个监听器对象)
待解析文件:
2、解析文件
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.Table;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xukun
* @date : 2020/9/2
*/
public class ExcelTest {
public static void main(String[] args) {
System.out.println("===读文件===");
//待解析的文件路径
String filePath1 = "C:\\Users\\SLDT\\Desktop\\utf-8''区域.xlsx";
//第一个sheet,从第0行开始读取数据
Sheet sheet1 = new Sheet(1, 0);
try {
得到解析的数据
List<Object> data = EasyExcelFactory.read(new FileInputStream(new File(filePath1)),sheet1);
//打印
for (Object d : data){
System.out.println(d);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
3、结果
写出文件
写文件有两种方式
第一种:生成动态表头的excel文件
第二种:生成指定表头的excel文件
下面就以上两种方式进行说明:
一、生成动态表头
1、代码如下:
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.Table;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xukun
* @date : 2020/9/2
*/
public class ExcelTest {
public static void main(String[] args) {
System.out.println("===写文件===");
String filePath2 = "C:\\Users\\SLDT\\Desktop\\test.xlsx";
//第一个sheet,从第0行开始写数据
Sheet sheet2 = new Sheet(1,0);
sheet2.setSheetName("test");
//自定义表格
Table table = new Table(1);
//所有表头的集合
List<List<String>> head = new ArrayList<>();
//后续这些表头可根据自己的需求动态生成,比如,可通过循环生成等,可能有一些业务,它的表头需要从数据库中查询得到
ArrayList<String> head0 = new ArrayList<>();
head0.add("姓名");//第一列表头
ArrayList<String> head1 = new ArrayList<>();
head1.add("学号");//第二列表头
ArrayList<String> head2 = new ArrayList<>();
head2.add("性别");//第三列表头
head.add(head0);
head.add(head1);
head.add(head2);
//将表头添加到表中
table.setHead(head);
//写
try {
//传参
ExcelWriter writer = EasyExcelFactory.getWriter(new FileOutputStream(new File(filePath2)));
writer.write0(getExcelData(), sheet2, table);
writer.finish();//记住,一定要关闭资源,否则写出的文件为空
System.out.println("文件写出完毕");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 写入到文件中的数据,为空时相当于一个模板
* 在具体的业务中,这些数据从数据库获取
*/
private static List<List<String>> getExcelData() {
return null;
}
}
2、结果:
3、写出的文件:
二、生成指定表头
1、该方式一般生成的表头与类的属性一一对应,因此,我们首先创建一个User类,并且该类还要继承自BaseRowModel类(后续会说明原因),同时使用@ExcelProperty注解指明表头名称和位置。
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
/**
* @author : xukun
* @date : 2020/9/3
*/
public class User extends BaseRowModel {
//value的值表示该列的表头名称为“序号” index表示该列的位置
@ExcelProperty(value = "序号",index = 0)
private Integer id;
@ExcelProperty(value = "学号",index = 1)
private String sno;
@ExcelProperty(value = "姓名",index = 2)
private String sname;
@ExcelProperty(value = "年龄",index = 3)
private Integer age;
//省略Getter、Setter和toString方法
}
2、写文件
package com.shenlan.plains.excel.listener;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.Table;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xukun
* @date : 2020/9/2
*/
public class ExcelTest {
public static void main(String[] args) {
//写出文件
String filePath4 = "C:\\Users\\SLDT\\Desktop\\学生.xlsx";
try {
//传参
ExcelWriter writer = EasyExcelFactory.getWriter(new FileOutputStream(new File(filePath4)));
Sheet sheet4 = new Sheet(1, 0, User.class);
//自定义表格
Table table4 = new Table(1);
writer.write0(getExcelData(), sheet4, table4);
writer.finish();
System.out.println("写文件完毕");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 写入到文件中的数据,为空时相当于一个模板
* 在具体的业务中,这些数据从数据库获取
*/
private static List<List<String>> getExcelData() {
return null;
}
}
可以看出,在该方式中,我们使用的时Sheet的另一个重载的构造方法,在该方法中会传入一个继承自BaseRowModel的类的类对象,我们也可以从源码传参看到。
所以,前面在定义类的时候就需要让该类继承BaseRowModel类。
同时,该方式中的Table对象我们只是创建出来,并没有设置任何关于表头的属性。这是因为关于表头的信息我们都在自定义类中定义好了。
3、结果
控制台结果:
生成的表格:
至此,使用EasyExcel进行文件的读取与写出就到此结束。