原创 一安 一安未来 2022-08-12 08:41 发表于北京
收录于合集#干货分享集78个
大家好,我是一安,今天聊一下日常开发经常用到的文件导入导出功能,本篇采用阿里巴巴开源EasyExcel实现
前言
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。
EasyExcel采用一行一行的解析模式,从磁盘上一行行读取数据,逐个解析,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
github地址: https://github.com/alibaba/easyexcel
官方文档: https://easyexcel.opensource.alibaba.com/
示例代码
引入easyexcel,为了简便代码,一并引入了lombok
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
<!--EasyExcel相关依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
读文件
注意:方便测试,暂未使用spring管理,统一用main方法测试验证
文件监听器:
package com.capitek.excel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
/**
有个很重要的点 ReadExcelListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
*/
public class ReadExcelListener extends AnalysisEventListener<Map<Integer, String>> {
private static final Logger LOGGER = LoggerFactory.getLogger(ReadExcelListener.class);
private DemoDAO demoDAO;
public ReadExcelListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/** 这里会一行行的返回头
* @param headMap
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
}
/**
* 这个每一条数据解析都会来调用
* @param data
* @param context
*/
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了都会来调用
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", cachedDataList.size());
demoDAO.save(cachedDataList);
LOGGER.info("存储数据库成功!");
}
}
持久层:
package com.capitek.excel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
/**
* 这个类让spring管理,小编偷个懒未使用。
**/
public class DemoDAO {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoDAO.class);
public void save(List<Map<Integer, String>> cachedDataList) {
LOGGER.info("入库条数:"+cachedDataList.size());
}
}
测试验证:
注意:读的时候可以通过设置excelType(ExcelTypeEnum.XLSX)指定写入文件类型,在easyexcel 3.0以后支持写入csv
package com.capitek.excel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.IoUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class TestReadMain {
/**
* @param stream excel文件流
* @param parseRowNumber 指定读取行
*/
public static void parseExcelToView(byte[] stream, Integer parseRowNumber) {
DemoDAO demoDAO = new DemoDAO();
ReadExcelListener readListener = new ReadExcelListener(demoDAO);
EasyExcelFactory.read(new ByteArrayInputStream(stream)).excelType(ExcelTypeEnum.XLSX).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet(0).doRead();
}
/**
* 文件导入测试
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
FileInputStream inputStream = new FileInputStream(new File("easyexcel-export-user.xlsx"));
byte[] stream = IoUtils.toByteArray(inputStream);
parseExcelToView(stream, 2);
inputStream.close();
}
}
日志输出:
16:25:05.492 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条头数据:{0:"班级",1:"学生信息",2:"学生信息"}
16:25:05.496 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条头数据:{0:"班级",1:"姓名",2:"性别"}
16:25:05.506 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三0",2:"男"}
16:25:05.507 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三1",2:"男"}
16:25:05.513 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三2",2:"男"}
16:25:05.514 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三3",2:"男"}
16:25:05.515 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三4",2:"男"}
16:25:05.516 [main] INFO com.capitek.excel.ReadExcelListener - 5条数据,开始存储数据库!
16:25:05.516 [main] INFO com.capitek.excel.DemoDAO - 入库条数:5
16:25:05.516 [main] INFO com.capitek.excel.ReadExcelListener - 存储数据库成功!
16:25:05.516 [main] INFO com.capitek.excel.ReadExcelListener - 所有数据解析完成!
写文件
注意:写的时候可以通过设置excelType(ExcelTypeEnum.XLSX)指定写入文件类型,在easyexcel 3.0以后支持写入csv
package com.capitek.excel;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.fastjson.JSONArray;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
public class TestWriteMain {
private static final Logger LOGGER = LoggerFactory.getLogger(TestWriteMain.class);
/**
* 动态导出文件(通过map方式计算)
* @param headColumnMap 有序列头部
* @param dataList 数据体
* @return
*/
public static byte[] exportExcelFile(LinkedHashMap<String, String> headColumnMap, List<Map<String, Object>> dataList){
//获取列名称
List<List<String>> excelHead = new ArrayList<>();
if(MapUtils.isNotEmpty(headColumnMap)){
//key为匹配符,value为列名,如果多级列名用逗号隔开
headColumnMap.entrySet().forEach(entry -> {
excelHead.add(CollectionUtil.newArrayList(entry.getValue().split(",")));
});
}
List<List<Object>> excelRows = new ArrayList<>();
if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){
for (Map<String, Object> dataMap : dataList) {
List<Object> rows = new ArrayList<>();
headColumnMap.entrySet().forEach(headColumnEntry -> {
if(dataMap.containsKey(headColumnEntry.getKey())){
Object data = dataMap.get(headColumnEntry.getKey());
rows.add(data);
}
});
excelRows.add(rows);
}
}
byte[] stream = createExcelFile(excelHead, excelRows);
return stream;
}
/**
* 生成文件
* @param excelHead
* @param excelRows
* @return
*/
private static byte[] createExcelFile(List<List<String>> excelHead, List<List<Object>> excelRows){
try {
if(CollectionUtils.isNotEmpty(excelHead)){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
EasyExcel.write(outputStream).excelType(ExcelTypeEnum.XLSX).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.head(excelHead)
.sheet("sheet1")
.doWrite(excelRows);
return outputStream.toByteArray();
}
} catch (Exception e) {
LOGGER.error("动态生成excel文件失败,headColumns:" + JSONArray.toJSONString(excelHead) + ",excelRows:" + JSONArray.toJSONString(excelRows), e);
}
return null;
}
public static void main(String[] args) throws IOException {
//导出包含数据内容的文件(方式一)
LinkedHashMap<String, String> headColumnMap = new LinkedHashMap<>();
headColumnMap.put("className","班级");
headColumnMap.put("name","学生信息,姓名");
headColumnMap.put("sex","学生信息,性别");
List<Map<String, Object>> dataList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("className", "一年级");
dataMap.put("name", "张三" + i);
dataMap.put("sex", "男");
dataList.add(dataMap);
}
byte[] stream1 = exportExcelFile(headColumnMap, dataList);
FileOutputStream outputStream1 = new FileOutputStream(new File("easyexcel-export-user.xlsx"));
outputStream1.write(stream1);
outputStream1.close();
}
}
文件内容:
号外!号外!
如果这篇文章对你有所帮助,或者有所启发的话,帮忙点赞、在看、转发、收藏,你的支持就是我坚持下去的最大动力!
一安未来
致力于Java,大数据;心得交流,技术分享;
71篇原创内容
公众号