数据导入:减轻录入工作量
数据导出:统计信息归档
数据传输:异构系统之间数据传输
Java
领域解析、生成Excel
比较有名的框架有Apache poi
、jxl
等。
但它们都存在一个严重的问题就是非常的耗内存。
如果系统并发量不大的话可能还行,但是一旦并发上来后一定会出现OOM
或者JVM
频繁的full gc
。
EasyExcel
是阿里巴巴开源的一个exce
l处理框架,以使用简单、节省内存著称。
EasyExcel
能大大减少占用内存的主要原因是在解析Excel
时没有将文件数据一次性全部加载到内存中。
而是从磁盘上一行行读取数据,逐个解析。
EasyExcel
采用一行一行的解析模式,并将一行的解析结果以观察者模式通知处理(AnalysisEventListener
)。
综上所述:
EasyExcel
是一个基于Java
的简单又省内存的读写Excel
的开源项目。
在尽可能节约内存的情况下支持读写百M
大小的Excel
文件。
https://alibaba-easyexcel.github.io/index.html
https://github.com/alibaba/easyexcel
<dependencies>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.1.2version>
dependency>
dependencies>
// lombok注解,自动生成getter/setter、构造方法、toString等实体类通用方法
@Data
public class Student {
// @ExcelProperty注解 设置当前字段表头名称和所在的列的索引,index为0,代表sno字段会被插入到第一列
@ExcelProperty(value = "学生编号",index = 0)
private int sno;
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
// @ExcelIgnore 导出时忽略这个字段
@ExcelIgnore
private String ignore;
}
//循环设置要添加的数据,最终封装到list集合中
private static List<Student> data() {
List<Student> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Student data = new Student();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
public static void main(String[] args) throws Exception {
// 指定文件位置
String fileName = "D:\\test.xlsx";
// 指定各个参数并向指定位置写出一个Excel文件
EasyExcel.write(fileName, Student.class).sheet(0, "学生列表").doWrite(data());
}
public static void main(String[] args) throws Exception {
// 指定文件位置
String fileName = "D:\\test.xlsx";
EasyExcel.write(fileName, Student.class).sheet("学生列表")
.doWrite(() -> {
// 这个函数中返回需要的数据即可
return data();
});
}
所传参数详解:
write
方法需要指定文件位置和需要指定写用哪个class去写,写入数据的位置默认是第一个sheet
sheet
方法需要指定当前写入数据的那个sheet
的名称(必须)和向第几个sheet
写入数据(非必须)
doWrite
方法可以接收需要写入的数据的集合或者传入一个lambda
表达式,表达式的返回值依旧是数据的集合
public static void main(String[] args) throws Exception {
// 指定文件位置
String fileName = "D:\\test.xlsx";
// 创建Excel写出对象
ExcelWriter excelWriter = EasyExcel.write(fileName, Student.class).build();
// 创建写出的Excel文件的sheet对象 并指定sheet的位置和名字
WriteSheet sheet1 = EasyExcel.writerSheet(0, "学生列表1").build();
WriteSheet sheet2 = EasyExcel.writerSheet(1, "学生列表2").build();
// 向sheet中写入数据
excelWriter.write(data(), sheet1);
excelWriter.write(data(), sheet2);
//关闭流对象(这是和上面写法的区别,上面的写法会自动关,这里需要手动关)
excelWriter.finish();
}
在web
应用中,经常会碰到Excel
导出(下载)的操作。
在EasyExcel
中主要使用HttpServletResponse
对象获取输出流把文件写出到浏览器。
import org.springframework.web.multipart.MultipartFile;
/**
* 在web应用中读写案例
**/
@Controller
public class WebTest {
@Autowired
private UploadDAO uploadDAO;
/**
* 文件下载(失败了会返回一个有部分数据的Excel)
* 1. 创建excel对应的实体对象
* 2. 设置返回的 参数
* 3. 直接写出Excel
* 这里注意,doWrite执行完后会自动关闭流, 主动finish也会自动关闭OutputStream
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
// 这里注意 使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
// 设置文件名称
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
// 写出数据
EasyExcel.write(response.getOutputStream(), Student.class)
.sheet("sheet的名称").doWrite(data());
}
/**
* 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
* @since 2.1.1
*/
@GetMapping("downloadFailedUsingJson")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
try {
response.setContentType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// URLEncoder.encode可以防止中文乱码
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
// 这里需要设置不关闭流 autoCloseStream(Boolean.FALSE)
EasyExcel.write(response.getOutputStream(), Student.class)
.autoCloseStream(Boolean.FALSE)
.sheet("模板")
.doWrite(data());
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = MapUtils.newHashMap();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
//循环设置要添加的数据,最终封装到list集合中
private static List<Student> data() {
List<Student> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Student data = new Student();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
}
// lombok注解,自动生成getter/setter、构造方法、toString等实体类通用方法
@Data
public class Student {
// @ExcelProperty注解 设置当前字段表头名称和所在的列的索引,index为0,代表sno字段会被插入到第一列
@ExcelProperty(value = "学生编号",index = 0)
private int sno;
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
// @ExcelIgnore 导出时忽略这个字段
@ExcelIgnore
private String ignore;
}
1)基础版
public class ExcelListener extends AnalysisEventListener<Student> {
//创建list集合封装最终的数据
List<Student> list = new ArrayList<>();
//读取excel表头信息时执行
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
// 读取excel内容信息时执行
// EasyExcel会会一行一行去读取excle内容,每解析excel文件中的一行数据,都会调用一次invoke方法
@Override
public void invoke(Student stu, AnalysisContext analysisContext) {
System.out.println("***" + stu);
list.add(user);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
注意:
自定义的监听器类不能被spring
管理,每次读取excel
都要new
一个新的对象。
如果里面用到spring
管理的对象,可以使用构造方法传递进来:
// 如果用到了就在监听器中加上类似代码,去使用dao层或者service层中的逻辑把读取到的数据写到数据库中
private DemoDAO demoDAO;
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
2)需要用到spring中保存数据的逻辑时
@Slf4j
public class ExcelListener extends AnalysisEventListener<Student> {
//创建list集合封装从Excel文件中读取的数据
List<Student> list = new ArrayList<>();
// list中每达到10条数据就存储数据库,然后清理list ,方便内存回收
// 实际使用中可以根据服务器性能设置更多条
private static final int BATCH_COUNT = 10;
// 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。
// 如果不用存储从Excel文件中读取的数据,那么这个对象就没用
private DemoDAO demoDAO;
// 无参构造
public DemoDataListener() {
}
// 有参构造 可以在每次创建Listener对象的时候需要把spring管理的类传进来
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
//读取excel表头信息时执行
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
// 读取excel内容信息时执行
// EasyExcel会会一行一行去读取excle内容,每解析excel文件中的一行数据,都会调用一次invoke方法
@Override
public void invoke(Student stu, AnalysisContext analysisContext) {
System.out.println("***" + stu);
list.add(user);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list = new ArrayList<Student>(BATCH_COUNT);
}
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
list = new ArrayList<Student>(BATCH_COUNT);
}
// 把数据存储到数据库中
private void saveData() {
log.info(BATCH_COUNT + "条数据,开始存储数据库!", cachedDataList.size());
demoDAO.save(list);
log.info("存储数据库成功!");
}
}
public class EasyExcelReadDemo {
public static void main(String[] args) {
// 指定要读取文件的位置
String fileName = "D:\\test.xlsx";
// read方法指定文件名名称、使用哪个实体类解析、使用哪个监听器类处理
// sheet方法指定读取哪个sheet的数据
// doRead() 方法发起最终的读取操作
EasyExcel.read(fileName, Student.class, new StudentListener()).sheet(0).doRead();
}
}
public class EasyExcelReadDemo {
public static void main(String[] args) {
// 指定要读取文件的位置
String fileName = "D:\\test.xlsx";
// 创建Excel读对象,需要指定读取哪个Excel
ExcelReader excelReader = EasyExcel.read(fileName).build();
// 创建需要读取的Excel中的sheet对象
ReadSheet sheet1 = EasyExcel.readSheet(0)
.head(Student.class)
.registerReadListener(new StudentListener()).build();
ReadSheet sheet2 = EasyExcel.readSheet(1)
.head(Student.class)
.registerReadListener(new StudentListener()).build();
// 批量读取sheet1对象和sheet2对象中的数据
excelReader.read(sheet1, sheet2);
// 关闭流资源
excelReader.finish();
}
}
import org.springframework.web.multipart.MultipartFile;
/**
* 在web应用中读写案例
**/
@Controller
public class WebTest {
@Autowired
private DemoDAO demoDAO;
0
/**
* 文件上传
* 1. 创建excel对应的实体对象 参照{@link UploadData}
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器
* 3. 直接读即可
*/
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(),
Student.class,
new UploadDataListener(demoDAO))
.sheet()
.doRead();
return "success";
}
//循环设置要添加的数据,最终封装到list集合中
private static List<Student> data() {
List<Student> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Student data = new Student();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
}