【小记】高性能能Excel操作工具-EasyExcel

一、初识EasyExcel

首先,说到EasyExcel,有做过报表的导入以及导出的小伙伴一定不陌生。
比如,目前就职的公司所涉及的CRM类的诸多项目,就用到了此类功能。
利用Java来对Excel进行导入以及导出,现在常见的框架主要有两大类。

  1. Apache POI
  2. Alibaba EasyExcecl

1 POI

1.1 占用内存,消耗资源

比如我就是先了解的Excel导出工具,就是POI,但是被POI的一些功能缺点所苦恼,众所周知,POI是一款基于内存的读写模式,在一些的大型项目,高并发场景下就显得吃力。

1.2 学习周期较长,比使用用于快速上手

了解过POI的小伙伴都知道,POI操作Excel分为了两种模式(SAAX、Dom)
且SAX解析Excel较为复杂,且POI针对不同版本的Excel,读取和存储的方式也是不相同。
且代码两十分复杂,虽有一些规律可循,但是长期不巩固,很容易就会生疏

1.3 POI的特点

  1. 功能强大
  2. 代码复杂
  3. 内存消耗大

2 EasyExcel

image.png
这张图,是EasyExcel官方的文档上的一张图,这张图很好的诠释了他高性能的地方,内存消耗低。

一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

image.png
官网 :[EasyExcel官网](https://easyexcel.opensource.alibaba.com/)

2.1 EasyExcel特点

  1. 对POI进行封装 使用简单
  2. 内存消耗低
  3. 只能操作Excel

二、环境搭建

2.1 加入依赖

<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>easyexcel</artifactId>
			<version>3.1.1</version>
		</dependency>

2.2 创建数据库

create table `t_info` (
	`nick_name` varchar (765),
	`net_number` varchar (765),
	`sign_time` date ,
	`belong_org` varchar (765),
	`order_num` bigint (20),
	`income` bigint (20),
	`in_month` int (11)
); 

2.3 创建实体类

@Data
public class Information {

	private String netNumber;
	
	private String nickName;

	private Date signTime;

	private String belongOrg;
    
	private Long orderNum;
    
	private Long income;
	
	private int month;

三、简单读写

3.1 简单的读取操作

首先,我们需要认识一下Excel,在Excel里,每一个Excel都可以称作一个WorkBook,在WorkBook里面又可以分为多个sheet。
读取Excel里的文件,其实只需要用到一个方法EasyExcel.read()即可
【read方法的源码】

/**
     * @param pathName
     *            读取文件的路径地址
     * @param head
     *            解析成Java实体类的类
     * @param readListener
     *            读的监听器
     * @return Excel reader builder.
     */
public static ExcelReaderBuilder read(String pathName, Class head, ReadListener readListener) {
        ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
        excelReaderBuilder.file(pathName);
        if (head != null) {
            excelReaderBuilder.head(head);
        }
        if (readListener != null) {
            excelReaderBuilder.registerReadListener(readListener);
        }
        return excelReaderBuilder;
    }

【读的监听器】

public class EasyExcelListener extends AnalysisEventListener<Information> {
	
	private static final Logger log = LoggerFactory.getLogger(EasyExcelListener.class);

	/**
	* invoke方法尾调用read读取文件的时候所执行
	* T 为指定读取实体类型
	*/
	@Override
	public void invoke(Information info, AnalysisContext context) {
		// TODO Auto-generated method stub
		log.info("开始读取数据============>>>");
		log.info("information = " + info);
	}

	@Override
	public void doAfterAllAnalysed(AnalysisContext context) {}

}

【测试案例】

@Test
public void testRead() {
	ExcelReaderBuilder workBook = EasyExcel.read("测试EasyExcel简单的读取.xlsx",Information.class,new EasyExcelListener());
	ExcelReaderSheetBuilder sheet = workBook.sheet();
	sheet.doRead();
}

image.png

3.2 简单的写入操作

EasyExcel也提供了同样简单的写出操作的方法EasyExcel.Write()
【Write源码】

 public static ExcelWriterBuilder write(String pathName, Class head) {
        ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder();
        excelWriterBuilder.file(pathName);
        if (head != null) {
            excelWriterBuilder.head(head);
        }
        return excelWriterBuilder;
    }

【测试案例】

@Test
	public void testEasyWrite() {
		String fileName = "测试EasyExcel简单的写入01.xlsx";
		QueryWrapper<Information> wrapper = new QueryWrapper<>();
//		wrapper.like("belong_org", "小");
		Page<Information> page = new Page<>(1, 10);
		IPage<Information> iPage = inService.page(page, wrapper);
		List<Information> list = iPage.getRecords();
		EasyExcel.write(fileName, Information.class).sheet("某直播平台3、4月份数据信息").doWrite(list);
	}

image.png
这里可以看到,这里的列宽,和表头都有点不太合适。为了可以更加可视化,方便阅读的展示数据,easyExcel提供了更多的注解

四、常用注解

4.1 @ExcelProperty

用于将实体类的名称与Excel导出的列名称对应,用于在属性字段上
【ExcelProperty源码】

public @interface ExcelProperty {

    /**
     * sheet表头的名称
     * <p>
     * 写操作:可以自动地将表头合并,也可以单独显示
     * <p>
     * 读操作: 当有多个表头,可以拿到最后面的
     */
    String[] value() default {""};

    /**
     * 列的索引
     *
     * 读或写的时候,可以改便列的顺序,如果index = -1 则会按照order排序
     *
     * 优先级 index >= sort >= default sort
     */
    int index() default -1;

    /**
     * Defines the sort order for an column.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Order of column
     */
    int order() default Integer.MAX_VALUE;

    /**
     * Force the current field to use this converter.
     *
     * @return Converter
     */
    Class<? extends Converter<?>> converter() default AutoConverter.class;

    /**
     *
     * default @see com.alibaba.excel.util.TypeUtil if default is not meet you can set format
     *
     * @return Format string
     * @deprecated please use {@link com.alibaba.excel.annotation.format.DateTimeFormat}
     */
    @Deprecated
    String format() default "";
}

4.1.1 参数说明

属性功能备注
valuesheet的标头名称是一个字符数组,如果写多个,相同的为主标头,其余为附表头
indexsheet列的索引(从0开始)控制列的顺序,通常不与value同用。如果不连续,则会出现空列
convert转化器通常用于性别的值映射(1–男;0–女)

【测试案例】

@TableName("t_info")
public class Information {

	@ExcelProperty({"网站号","网站号"})
	private String netNumber;
	
	@ExcelProperty({"大神昵称","大神昵称"})
	private String nickName;
	
	@ExcelProperty({"进站时间","进站时间"})
	private Date signTime;

	@ExcelProperty({"所属大神团","所属大神团"})
	private String belongOrg;

	@ExcelProperty({"营收信息","订单数量"})
	private Long orderNum;
	
	@ExcelProperty({"营收信息","收入"})
	private Long income;
	
	@ExcelProperty({"月份","月份"})
	@TableField("in_month")
	private int month;
}

说明:如果是在里面写一个数组的形式,则会把相同的表头合并表头

image.png

4.2 @ColumnWidth

设置列宽度,只有一个参数value,取值范围为0-255。因为Excel一个单元格的取值范围就为0-255个字符。
【测试案例】

public class Information {

	@ExcelProperty({"网站号","网站号"})
	@ColumnWidth(20)
	private String netNumber;
	
	@ExcelProperty({"大神昵称","大神昵称"})
	@ColumnWidth(20)
	private String nickName;
	
	@ExcelProperty({"进站时间","进站时间"})
	@ColumnWidth(20)
	private Date signTime;

	@ColumnWidth(20)
	@ExcelProperty({"所属大神团","所属大神团"})
	private String belongOrg;

	@ExcelProperty({"营收信息","订单数量"})
	@ColumnWidth(20)
	private Long orderNum;
	
	@ExcelProperty({"营收信息","收入"})
	@ColumnWidth(20)
	private Long income;
	
	@ExcelProperty({"月份","月份"})
	@TableField("in_month")
	private int month;
}

image.png

4.3 @ContentFontStyle | @HeadFontStyle

ContentFontStyle:用于设置单元格内容字体格式的注解。
HeadFontStyle:用于设置表头单元格字体格式的注解

4.3.1参数说明

属性功能备注
fontName字体名称
fontHeightInPoints字体高度
italic是否斜体
color颜色
typeOffset偏移量
blod是否加粗
charset编码格式
underline下划线

@ContentFontStyle 用于设置单元格内容字体格式的注解
【测试案例】

@TableName("t_info")
@ContentFontStyle(color = 14,fontHeightInPoints = 18,fontName = "宋体")
@HeadFontStyle(fontName = "华文中宋",color = 10,fontHeightInPoints = 24)
public class Information {

	@ExcelProperty({"网站号","网站号"})
	@ColumnWidth(20)
	private String netNumber;
	
	@ExcelProperty({"大神昵称","大神昵称"})
	@ColumnWidth(20)
	private String nickName;
	
	@ExcelProperty({"进站时间","进站时间"})
	@ColumnWidth(20)
	private Date signTime;

	@ColumnWidth(20)
	@ExcelProperty({"所属大神团","所属大神团"})
	private String belongOrg;

	@ExcelProperty({"营收信息","订单数量"})
	@ColumnWidth(20)
	private Long orderNum;
	
	@ExcelProperty({"营收信息","收入"})
	@ColumnWidth(20)
	private Long income;
	
	@ExcelProperty({"月份","月份"})
	@TableField("in_month")
	private int month;
}

image.png

4.4 ContentLoopMerge

用于设置单元格合并

4.4.1 参数说明

属性功能备注
eachRow
columnExtend

4.5 contentRowHeight|HeadRowHeight

全局设置行高,以及设置表头的行高。 通常用于类上

4.5.1 参数说明

属性功能备注
value行高-1表示自动行高

4.6 ContentStyle | HeadStyle

ContentStyle:设置内容的样式,通常用于字段值上,表示设置某一列单元格的样式。
HeadStyle:设置表的表头的样式,通常用于类上。

4.6.1 参数说明

属性功能备注
dateFormat日期格式
hidden设置单元格使用此样式隐藏
locked设置单元格使用此样式锁定
quotePrefix在单元格前面增加`符号,数字或者公式将以文符串的形式展示
horizontalAlignment是否水平居中
wrapped是否换行为true是,表示多行上显示内容
fillFocegroundColor设置单元格填充的前景色值为short类型的值

这里需要注意一下,Excel里的所有颜色,对应到代码里,都是由ICellStyle这个接口里的FillFoceGroundColor属性所设置。如果需要颜色的取值,可以参考:https://www.cnblogs.com/byxxw/p/5265127.html,这边博客。

4.7 DateTimeFormat

用于设置时间类型的格式化操作

4.7.1 参数说明

常用的参数就一个value,指定格式化时间的格式

@ExcelProperty({"进站时间","进站时间"})
@DateTimeFormat("yyyy-MM-dd")
@ColumnWidth(20)
private Date signTime;

image.png

4.8 NumberFormat

用于设置数值类型的数据单元格格式化

4.8.1 参数说明

同上,也是一个value参数

@ExcelProperty({"营收信息","收入"})
@ColumnWidth(20)
@NumberFormat("#.0")
private Long income;

image.png

说明:数字、时间格式和Excel里面设置单元格格式一样,#表示任意数字,没有不会填充;0表示一个数字,没有补0。

五、实现文件上传下载

5.1 文件上传

首先由于我们使用的是SpringBoot的方式完成,所以,我们需要导入相关的文件上传下载依赖文件fileupload

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.3</version>
		</dependency>

其次,需要使用springmvc的相关控制层,完成文件上传的测试,springmvc配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/mvc
	http://www.springframework.org/schema/mvc/spring-mvc.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 包扫描web层 -->
	<context:component-scan
		base-package="com.wei.controller" />
	<!-- 配置文件上传解析器,将上传的文件封装为CommonsMultipartFile -->
	<bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<property name="defaultEncoding" value="UTF-8" />
	</bean>

</beans>

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
	<!-- 注册监听器ContextLoaderListener -->
	<context-param>
		<!--contextConfigLocation:表示配置文件位置路径的属性 -->
		<param-name>contextConfigLocation</param-name>
		<!--指定配置文件路径 -->
		<param-value>classpath:spring-mvc.xml</param-value>
	</context-param>

	<!--注册声明过滤器,解决post请求乱码问题 -->
	<filter>
		<filter-name>characterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<!--指定字符集 -->
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<!--强制request使用字符集encoding -->
		<init-param>
			<param-name>forceRequestEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
		<!--强制response使用字符集encoding -->
		<init-param>
			<param-name>forceResponseEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>characterEncodingFilter</filter-name>
		<!--/*:表示强制所有请求先通过过滤器处理 -->
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!--项目启动后,实例化spring容器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!--配置前端控制器 -->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 加载springmvc的容器核心文件 -->
		<init-param>
			<!--springmvc配置文件的位置路径的属性 -->
			<param-name>contextConfigLocation</param-name>
			<!--指定文件路径 -->
			<param-value>classpath:spring-mvc.xml</param-value>
		</init-param>
		<!--load-on-startup:表示tomcat启动是创建对象的顺序,数值越小,越早创建 -->
		<load-on-startup>1</load-on-startup>
	</servlet>
	<!-- 配置映射的路径 -->
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<!--使用斜杠"/" 原理:代替tomcat中的default,导致所有静态资源都交给DispatcherServlet处理, 默认情况下DispatcherServlet没有处理静态资源的能力,没有控制器对象能处理静态资源的访问, 
			所以访问静态资源(html,js,图片,css)请求都是404 -->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

Service层创建ReadExcel()方法完成文件上传时候数据读取操作

private static final Logger log = LoggerFactory.getLogger(ImformationServiceImpl.class);

@Override
public void readExcelFile(List<Information> infos) {
	// TODO Auto-generated method stub
	log.info("Information = " + infos);
}

controller层完成读取调用

@RestController
@RequestMapping("/file")
public class InfomationController {
	
	@Autowired
	private InformationListener listener;
	
	 @RequestMapping("/fileUpload")
	 public String testFileUpload(MultipartFile file, HttpSession session) {
	       
	     try {
	    	// 获取到工作普
			ExcelReaderBuilder workbook = EasyExcel.read(file.getInputStream(),
														 Information.class,
														 listener);
			// 工作表
			ExcelReaderSheetBuilder sheet = workbook.sheet();
			// 执行监听器里的invoke方法读取数据
			sheet.doRead();
			return "success";
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	     return "fail";
	}
}

配置监听器,完成数据的读取

@Component
@Scope("prototype")
public class InformationListener extends AnalysisEventListener<Information>{

	@Autowired
	private InformationService inService;
	
	private ArrayList<Information> list = new ArrayList<>();
	
	@Override
	public void invoke(Information data, AnalysisContext context) {
		list.add(data);
		if (list.size() % 5 == 0) {
			inService.readExcelFile(list);
			list.clear();
		}
	}

	@Override
	public void doAfterAllAnalysed(AnalysisContext context) {}

}

image.png

5.1 文件下载

@RequestMapping("/fileDownload")
	 public void download(HttpServletResponse response) throws Exception{
		 response.setContentType("application/vnd.ms-excel");
		 response.setCharacterEncoding("UTF-8");
		 String filename = URLEncoder.encode("测试文件下载","UTF-8");
		 response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"+filename+".xlsx");
		 ServletOutputStream outputStream = response.getOutputStream();
		 ExcelWriterBuilder workbook = EasyExcel.write(outputStream, Information.class);
		 ExcelWriterSheetBuilder sheet = workbook.sheet("测试写入数据");
		 List<Information> infos = createData();
		 sheet.doWrite(infos);
	 }

	private List<Information> createData() {
		ArrayList<Information> list = new ArrayList<>();
		Information info = null;
		for (int i = 0; i <= 10; i++) {
			info = new Information();
			info.setBelongOrg("测试大神团"+i);
			Random random = new Random();
			int s = random.nextInt(1000000)%(1000000-10000+1) + 10000;
			info.setIncome(Integer.toUnsignedLong(s));
			info.setMonth(3);
			info.setNetNumber("测试哈哈哈哈");
			info.setNetNumber("46565465");
			info.setOrderNum(45763L);
			info.setSignTime(new Date());
			list.add(info);
		}
		return list;
	}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@WAT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值