技术标签: excel spring java web Spring boot poi Spring
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
@Data
public class User{
@ExcelProperty(value = "名称", index = 0)
@ColumnWidth(value = 15)
private String name;//名称
@ExcelProperty(value = "年龄", index = 1)
@ColumnWidth(value = 15)
private String age;//患者姓名
@ExcelProperty(value = "性别", index = 2)
@ColumnWidth(value = 15)
private String sex;//患者id
@ExcelProperty(value = "创建时间", index = 4)
@ColumnWidth(value = 15)
private String confirmDate;//创建时间
}
读取excle文件可以分为两种方式,一种是异步读取,另一种是同步读取
异步读取首先要构建监听器继承AnalysisEventListener
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
public class ExcelListener<T> extends AnalysisEventListener<T> {
private List<T> list=new ArrayList<>();
//读取每一行数据执行一次
@Override
public void invoke(T t, AnalysisContext analysisContext) {
list.add(t);
}
//读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头:"+headMap);
}
//数据全部读取完毕
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
save(list);
System.out.println("所有数据解析完毕.................");
}
public void save(List<T> list){
//逻辑代码
}
}
读取代码
public class ExcelUtils {
/**
* 读取excel文件
* @param file 文件
* @return List
*/
public static void readExcel(File file){
InputStream ins=null;
try {
ins=new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//sheet 设置处理的工作簿 headRowNumber 设置从excel第几行开始读取
EasyExcel.read(ins, User.class ,new ExcelListener<User>()).sheet().headRowNumber(1).doRead();//第0行一般是表头,从第1行开始读取
}
}
public class Test{
public static void main(String[] args) {
ExcelUtils readExcel(new File("D:\\test.xlsx"),User.class);
}
}
同步读取可以不需要上面的监听器了(不过如果需要验证表头的话,还是可以加上监听器来验证表头信息)
其实也就是 一个使用 doRead() 另一个使用 doReadSync()
public class ExcelUtils {
/**
* 读取excel文件
* @param file 文件
* @return List
*/
public static List readExcel(File file,Class clazz){
InputStream ins=null;
try {
ins=new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//sheet 设置处理的工作簿 headRowNumber 设置从excel第几行开始读取
return EasyExcel.read(ins).head(clazz).sheet().headRowNumber(1).doReadSync();//第0行一般是表头,从第1行开始读取
}
1.在exlcel中有明明有数据,但是读取进来后对象属性全是空
有可能是使用了 注解 @Accessors(chain = true) 这个注解会和 EasyExcel 有冲突,导致数据无法读取进来,一些高版本好像已经修复了此BUG
2.在读取日期形式的数据时,可能会造成精度丢失 比如:
这样的 yyyy-MM-dd HH:mm:ss 数据读取进来后可能会 变成 yyyy-MM-dd HH:mm
也就是这样
这种情况可以在实体工具类中增加一个注解,来让EasyExcel 知道读取日期的格式
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "创建时间", index = 4)
@ColumnWidth(value = 15)
private String confirmDate;//创建时间
这样读取进来的日期就会正常了
3.在目前的 easyExcel 中有一些 BUG ,如果 excel 第一行没有内容,是不会触发 invokeHeadMap 的方法的
也就是说如果你传入的 excel 文件第一行没有内容,那么你写在 invokeHeadMap 方法中的表头验证方法是不会被触发的
目前有一个办法是在 invoke 中写一些代码进行判断 如下:
private boolean isModel=true;
@Override
public void invoke(Object o, AnalysisContext analysisContext) {
//非正常表格式验证,第一行没数据
if(isModel){
throw new SystemException("请使用规定模板查询");
}
}
@Override
public void invokeHeadMap(Map headMap, AnalysisContext context) {
if(isModel){
isModel=false;
}
..........//其他验证表头代码
}
public class ExcelUtils {
/**
* 像浏览器输出excel文件
* @param response HttpServletResponse
* @param data 输出的数据
* @param fileName 输出的文件名称 excel的名称
* @param sheetName 输出的excel的sheet的名称 也就是页的名称
* @param clazz 输出数据的模板
*/
public static void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName, String sheetName, Class clazz){
//表头样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置表头居中对齐
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//内容样式
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置内容靠左对齐
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
try {
EasyExcel.write(getOutputStream(fileName, response), clazz).excelType(ExcelTypeEnum.XLSX).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy).doWrite(data);
} catch (Exception e) {
throw new SystemException("输出excel文件失败", e);
}
}
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
return response.getOutputStream();
}
}
@GetMapping("/test/downloadExcel")
public static void test(){
List<User> userList=new Arraylist<>();
//省略添加数据代码
try {
ExcelUtil.writeExcel(response, userList, fileName, sheetName, UserExcel.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public class ExcelUtils {
/*
* @Author: pang.jialei
* @Description: 读取excle模板,将数据输出到模板
* @Date: 2021/6/3 15:04
* @Param: templateInputStream: 模板所在的地址
* @param tempFileName: 生成的excel文件名称
* @param downloadDir: 输出excel文件的地址
* @param parms: list表格数据
* @param mapData: 其他参数
**/
public static void invoke(InputStream templateInputStream, String tempFileName, String downloadDir, List<?> parms, Map<String, Object> mapData) {
File file = new File(downloadDir);
if (!file.exists()) {
file.mkdirs();
}
String tempFile = downloadDir + File.separator + tempFileName;
ExcelWriter excelWriter = EasyExcel.write(tempFile).withTemplate(templateInputStream).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
// 如果数据量大 list不是最后一行 参照下一个
if (parms != null) {
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(parms, fillConfig, writeSheet);
}
if (mapData != null) {
excelWriter.fill(mapData, writeSheet);
}
excelWriter.finish();
}
/*
* @Author: pang.jialei
* @Description: 读取excle模板,将数据输出到多sheet模板
* @Date: 2021/6/3 15:04
* @Param: templateInputStream: 模板所在的地址
* @param tempFileName: 生成的excel文件名称
* @param downloadDir: 输出excel文件的地址
* @param listMapData: <sheet名称,list数据> 的表格数据
* @param sheetNames: sheet名称
* @param mapData: <sheet名称,其他参数<key,value>> 其他参数
**/
public static void invokeListSheet(InputStream templateInputStream, String tempFileName, String downloadDir, Map<String,List<?>> listMapData, List<String> sheetNames, Map<String,Map<String, Object>> mapData) {
File file = new File(downloadDir);
if (!file.exists()) {
file.mkdirs();
}
String tempFile = downloadDir + File.separator + tempFileName;
ExcelWriter excelWriter = EasyExcel.write(tempFile).withTemplate(templateInputStream).build();
for (String sheet:sheetNames) {
WriteSheet writeSheet = EasyExcel.writerSheet(sheet).build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
// 如果数据量大 list不是最后一行 参照下一个
List parms= listMapData.get(sheet);
if (parms != null) {
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(parms, fillConfig, writeSheet);
}
if (mapData != null) {
excelWriter.fill(mapData.get(sheet), writeSheet);
}
}
excelWriter.finish();
}
文章浏览阅读4.7k次,点赞9次,收藏32次。ESP32ADC连续采样的经验分享_esp32 dma
文章浏览阅读637次。学习后端开发,必备Linux环境。本篇通过vagrant+virtualBox的方式来管理创建Linux。vagrant 是一个虚拟机管理工具。 通过vagrant控制virtual创建centos8.提前了解vagrant有助于理解本文。 征服诱人的Vagrant 什么是vagrant下载安装下载安装virtualbox https://www.virtualbox.org/wiki/Downloads下载安装vagrant https://www.vagrantup.com/._vagrant box add添加centos8
文章浏览阅读2.1k次,点赞3次,收藏12次。vue引入字体图标_vue引入字体图标
文章浏览阅读413次。题目链接题目大意:让你猜一个n长度序列里第k大的数,每次操作你可以询问i,j<=n他们的and值或者or值,在操作次数小于2n的情况下求出答案思路:根据a+b=a&b+a|b,a+b=2*(a&b)+(aXORb)可得,两次操作分别求出a&b和a|b从而求出aXORb(这里XOR为异或)设p[i]=a[i]^a[i+1]从而用2n-2次方法求出p[i],此时我们知道序列中任何一项即可求出整个序列,在上面我们已经知道a[1]+a[2],a[2]+a[3],所以我们只需要知道a_deltix round, summer 2021 (open for everyone, rated, div. 1 + div. 2) b. tak
文章浏览阅读505次。LCISThis problem differs from one which was on the online contest.The sequence a1, a2, …, an is called increasing, if ai < ai + 1 for i < n.The sequence s1, s2, …, sk is called the subsequenc..._this problem differs from one which was on the online contest.
文章浏览阅读99次。Vue学习04——Vue3学习尚硅谷在线笔记
文章浏览阅读787次。B-树性质B-树可以看作是对2-3查找树的一种扩展,即他允许每个节点有M-1个子节点。1根节点至少有两个子节点2每个节点有M-1个key,并且以升序排列3位于M-1和M key的子节点的值位于M-1和M key对应的Value之间其它节点至少有M/2个子节点下图是一个M=3 阶的B树这里简单说明下图中的小黑方块表示对应关键字所代表的文件的存储位置,实际上可以看做是一个地址,比如根节点中17旁边的小..._myisam是b-树吗
文章浏览阅读8.6k次,点赞10次,收藏75次。OpenFOAM有两家版本,我安装的是OpenFOAM-v8,后续会安装OpenFOAM 2206,并实现多版本共存。这个通过编译的方式安装OpenFOAM-v8的过程,最早是参考东岳流体网站的,我一共给三台机子组双系统,安装了三遍都是没问题的。..._openfoam安装教程
文章浏览阅读8.1k次,点赞3次,收藏16次。函数的声明1.函数只能定义在函数外,不能定义在函数内2.函数不允许重名,C语言中函数没有重载3.函数只要一经定义,就可以在任意函数中调用注意:如果函数定义在它调用之后,那么必须在调用之前,先声明这个函数声明的语法:返回值类型 函数名(参数列表);tips:声明函数其实只要复制函数头,打个分号就可以了再注意: 1、函数定义的时候函数头是什么样子,那么声明的时候也必须是这个样子的2、如果有参数的函数,..._c 声明
文章浏览阅读179次。1、Spring BootJava 构建 Spring 应用程序已经有很长一段时间了,Spring Boot 是 Spring 的一个特定版本,它通过对配置细节的处理,使微服务构建更加简便。创建 Spring Boot 旨在自启动任何类型的 Spring 项目,而不仅仅是微服务。应用程序完成后,Spring Boot 将在 web 服务器中混合,并输出一个 JAR 文件,JVM 除外。你可以将其视为原始 Docker 容器。这也是许多负责构建微服务的开发者都非常喜欢 Spring Boot 的原因。_java 高大上的服务组件
文章浏览阅读3.4k次。runtime实践_runtime image
文章浏览阅读2.2k次,点赞2次,收藏5次。一个Django项目挂到服务器上的过程那全都是血与泪呀,弄得头都大了,各种问题,那真是百花齐放,百家争鸣,放弃了一阵子,后来又收到鼓舞,重新搞了一下,真的是老天不负有心人啊,终于终于终于ok了。真的是舒服了!!!提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录一个Django项目挂到服务器上的过程前言一、二、使用步骤1.引入库2.读入数据总结前言这次是在CentOS7服务器上搭建的,用到了nginx和 uWSGI,因为这个项目没用到数据库,就没有装mysql,需要的可_nginx uwsgi centos7