• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

java实现excel的导出:使用easyExcel

武飞扬头像
荒帝
帮助1

前言

在我们的项目需求中,经常会遇到导出的需求,其中excel的导出最为常见。生成Excel比较有名的框架有Apache poi,jxl等,但他们都存在一个严重的问题就是非常的耗内存,如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc.

一、EasyExcel特点

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,
64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)。
EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

不支持的功能

1、单个文件的并发写入、
2、读取读取图片
3、宏
4、csv读取(这个后续可能会考虑)

三、常见问题

1、读取文件务必使用2.0.5 (现在项目中用的是2.2.10)
2、读写反射对象用到了Cglib动态代理,所以成员变量必须符合驼峰规范,而且使用@Data不能使用@Accessors(chain = true)。后续会考虑支持非驼峰。
3、出现 NoSuchMethodException, ClassNotFoundException, NoClassDefFoundError。极大概率是jar冲突,建议clean项目,或者统一poi 的版本,理论上来说easyexcel兼容poi的3.17,4.0.1,4.1.0所有较新版本
4、用String去接收数字,出现小数点等情况这个是BUG,但是很难修复,后续版本会修复这个问题。目前请使用@NumberFormat注解,里面的参数就是调用了java自带的NumberFormat.format方法,不知道怎么入参的可以自己网上查询。
easyExcel的官方文档地址:https://alibaba-easyexcel.github.io/index.html

四、常用注解

4-1、读

ExcelProperty 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。

@Getter
@Setter
@EqualsAndHashCode 
public class IndexOrNameData{

//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*
@ExcelProperty(index = 2)
private Double doubleData

/*用名字去匹配,这里需要注意,如果名字重复会导致只有一个字殷读取到数据@ExcelProperty ("字符串标题”) */
private Stringstring;

@ExcelProperty ("日期标题") 
private Date date;
}

ExcelIgnore 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*
@ExcelIgnore
private Double doubleData

DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat。

@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private String date;

NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat。

@NumberFormat("#.##%") 
private String doubleData; //接收百比的数字

4-2、写

ExcelProperty index 指定写到第几列,默认根据成员变量排序。value指定写入的名称,默认成员变量的名字,多个value可以参照快速开始中的复杂头
ExcelIgnore 默认所有字段都会写入excel,这个注解会忽略这个字段
DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat
NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat
ExcelIgnoreUnannotated 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与

五、EasyExcel的使用

1、依赖

<dependency>    
<groupId>com.alibaba</groupId>   
 <artifactId>easyexcel</artifactId>    
<version>2.2.10</version>
</dependency>

2、读excel

学新通

2.1最简单的

对象

@Data
public class DemoData {    
private String string;    
private Date date;    
private Double doubleData;
}

controller类

@PostMapping("/outstoragcExce1")
@Apioperation("读取出库excel表")
public DeviceResponse storageservice(@RequestBody MultipartFile file) {
	try{
		storageservice.storageservice(file);
		} catch (Exception e){
	return new DeviceResponse(Constant.FAIL CODE,"出库导失败");
	}
	return new DeviceResponse(Constant.SUCCESS CODE,"出库导入成功");
}

service实现类

@Autowired
private StorageService storageServicel
@override
publil void slorageservice(MulliparlFile file) {
  Tnnutstream is = null:
  try{
     is=file.getInputstream();
  } catch (IDException e){
     e.printstackTrace();
  }
  //1.进行读取数数据,slorageReLrieval是我的puju类,
  //2.new Soragelistenpr(storagpServire)这个是监听器,主要用来i取数据的,别急后面会讲
  //3.特别注意的是storageservice这个service,我上面有注入进去 @Autowired,切记不要new会报错
  EasyExcel.read(is,StorageRetrieval.class, new Soragelisterer(storageService))sheet().doRead();
}

SorageListener监听器

@Component
public class SorageListener extends AnalysisEventListener<pojo类> {

    private static final Logger LOGGER = LoggerFactory.getLogger(SorageListener.class);
    //读取数据初始化值
    private static final int BATCH_COUNT = 50;
    List<pojo类> list = new ArrayList<pojo类>();

    private StorageService storageService;

    public SorageListener() {
        storageService=new StorageServiceImpl();
    }
 
    public SorageListener(StorageService storageService) {
        this.storageService = storageService;
    }

    /**
     * 这个每一条数据解析都会来调用,数据是一条一条进行解析的
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(StorageRetrieval data, AnalysisContext context) {
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }
/**
     * 所有excel表中数据解析完成了 都会来调用这个
     * 解释为什么要保存数据?
     *初始化读取数量为50,表中信息已经加载完毕,,假设excel表中最后只剩下30行遗留数据,所以为了防止存在遗留数据 尽量判断下集合是否为空,不为空在进行存储(这是我的逻辑需要判断,如果不需要也可进行不判断)
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
      if(list.size()==0){
         return;
      }
        saveData();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    public void saveData() {
        storageService.save(list); //代码实现类层保存数据
        LOGGER.info("存储数据库成功!");
    }
}
学新通

2.2、指定列的下标或者列名

@Data
public class IndexOrNameData {
    /**
     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
     */
    @ExcelProperty(index = 2)
    private Double doubleData;
    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
}
/**
 * 指定列的下标或者列名
 *
 * <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
 * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
 * <p>3. 直接读即可
 */
@Test
public void indexOrNameRead() {
    String fileName = TestFileUtil.getPath()   "demo"   File.separator   "demo.xlsx";
    // 这里默认读取第一个sheet
    EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}
学新通

3、写excel

3.1最简单的

学新通
实体对象

@Data
@piModel(value = "年龄统计实体类”
public class FToWAgeStatisticalVo implements Serializable {
private static final long serialVersionUID = -7891558029837989473L;
@ApiModelProperty("区间")
@ExcelProperty(value = "区间")
private String ageGap;

@ApiModeLProperty("病例数”)
@ExcelProperty(value ="病例数)
private Integer casesNumber ;

@ApiModeProperty("密接数”)
@ExceProperty(value = "密接数”)
private Integer closeNumber ;
}
学新通

service实现

@Override
public void avestatisticalExcel(Httpservlethesponse resonse,FlowReionStatisticalParam flowReionStatisticalParam) throws Exception{
  //这里文件名如果涉及中文一定要使用URL编码,否则会乱码
  String fileName = URLEncoder.encode( s: "floWAgeStatistical.xlsx" StandardCharsets.UTF_8.toString());
  List<FloWAgeStatisticalVo> data = ageStatistical(flowRegionStatisticalParam);
  response.setContentType("application/force-download");
  response.setcharacterEncoding("utf-8");
  response.setHeader( s: "Content-Disposition", s1: "attachment;filename="   fileName);
  EasyExcel.write(response.getoutputstream(),FLoWAgeStatisticalVo.class)
    .autoclosestream(true)
    .exceType(ExcelTypeEnum.XLSX)
    .sheet( sheetName: "年龄统计表")
    .doWrite(data) ;
}

3.2、列宽、行高

@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    /**
     * 宽度为50
     */
    @ColumnWidth(50)
    @ExcelProperty("数字标题")
    private Double doubleData;
}
学新通

3.3、合并单元格

@Getter
@Setter
@EqualsAndHashCode
// 将第6-7行的2-3列合并成一个单元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {
    // 这一列 每隔2行 合并单元格
    @ContentLoopMerge(eachRow = 2)
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}
/**
  * 合并单元格
  * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
  * <p>2. 创建一个merge策略 并注册
  * <p>3. 直接写即可
  */
 @Test
 public void mergeWrite() {
     String fileName = TestFileUtil.getPath()   "mergeWrite"   System.currentTimeMillis()   ".xlsx";
     // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
     LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
     // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
     EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
         .doWrite(data());
 }

3.4、复杂头写入

学新通

@Data
@ApiModel("学校学生缺勤信息")
public class SchoolAnalyseVo {

    @ApiModelProperty("学校Id")
    @ExcelIgnore()
    private Long schoolId;

    @ExcelProperty("学校")
    private String schoolName;

    @ExcelProperty("学校类型")
    private String schoolType;
     ..........

    @ExcelProperty({"症状", "发热"})
    private String fever;

    @ExcelProperty({"症状", "咳嗽"})
    private String cough;

    @ExcelProperty({"症状", "头痛"})
    private String headache;
    .........

    @ExcelProperty({"疾病","普通感冒", "人数"})
    private String commonColdNumber ;

    @ExcelProperty({"疾病","普通感冒", "因病缺勤率"})
    private String commonColdRate ;

    @ExcelProperty({"疾病","流感", "人数"})
    private String influenzaNumber;
    .........
}
学新通

3.5、日期、数字或者自定义格式转换

学新通

@Data
public class ConverterData {
    /**
     * 我想所有的 字符串起前面加上"自定义:"三个字
     */
    @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
    private String string;
    /**
     * 我想写到excel 用年月日的格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty("日期标题")
    private Date date;
    /**
     * 我想写到excel 用百分比表示
     */
    @NumberFormat("#.##%")
    @ExcelProperty(value = "数字标题")
    private Double doubleData;
}
学新通

自定义转换器

public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里读的时候会调用
     *
     * @param cellData
     * @param contentProperty
     * @param globalConfiguration
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return "自定义:"   cellData.getStringValue();
    }

    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value
     * @param contentProperty
     * @param globalConfiguration
     * @return
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return new CellData(value);
    }
}
学新通

3.6、指定写入列

学新通

@Getter
@Setter
@EqualsAndHashCode
public class IndexData {
    @ExcelProperty(value = "字符串标题", index = 0)
    private String string;
    @ExcelProperty(value = "日期标题", index = 1)
    private Date date;
    /**
     * 这里设置3 会导致第二列空的
     */
    @ExcelProperty(value = "数字标题", index = 3)
    private Double doubleData;
}

3.7、其他读操作

https://www.yuque.com/easyexcel/doc/write

4、填充excel

4.1 最简单的填充

学新通
对象

@Getter
@Setter
@EqualsAndHashCode
public class FillData{
private string name;
private double number;
private Date date;
}

代码

/*最简单的填充
* @since 2.1.1
*/
@Test
public void simpleFill() [
    // 模板注 用]来表示你要用的变量 如果本来就有””,”]”特殊字符 用””]"代替
    String templateFileName =TestFileUtil.getPath()   "demo"   File.separator   "fill"   File.separator   "simple.xlsx";
    // 方案1 根据对象填充
    String fileName = TestFileUtil,getPath()   "simpleFill"   System,currentTimeMillis()   ".xlsx",
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    FillData fillData = new FillData();
    fillData.setName("张一");
    fillData.setNumber(5.2);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);
    // 方案2 根据Map填充
    fileName = TestFileUtil.getPath()   "simpleFill"   System.currentTimeMillis()   ".xlsx"
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    Map<string, Object> map = new HashMap<string, Object>();
    map.put("name”,"张二");
    map .put("number", 5.2) :
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}
学新通

4.2、其他填充

填充列表、复杂的填充、数据量大的复杂填充、横向的填充、多列表组合填充填充
https://www.yuque.com/easyexcel/doc/fill

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfikeie
系列文章
更多 icon
同类精品
更多 icon
继续加载