Easy Excel

Easy Excel

  • 官网:https://easyexcel.opensource.alibaba.com/docs/current/
  • 为什么要使用Easy Excel

使Java更好的操作excel

官方:

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

img

  • 怎么使用

两种读方式:

1.确定表头:建立对象,和表头形成映射关系

2.不确定表头:每一行数据映射为Map<String,Object>

先导入pom.xml

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

创建一个对象(与excel中的行数据做个映射)

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}

自定义监听器

@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        System.out.println(data);
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        System.out.println("已解析完成");
    }
}

创建excel文件(最好不要又中文路径)

img

读取数据

  • 自定义监听器
public class ImportExcel {
    public static void main(String[] args) {
        String fileName = "D:\\demo.xlsx";
        // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    }
}

img

  • 不创建监听器
public class ImportExcel {
    public static void main(String[] args) {
        String fileName = "D:\\demo.xlsx";
        // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置
//        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        List<DemoData> lists = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();
        for (DemoData list : lists) {
            System.out.println(list);
        }
    }
}

img

  • 监听器:先创建监听器、在读取文件时绑定监听器。单独抽离处理逻辑,代码清晰易于维护;一条一条处理,适用于数据量大的场景。
  • 同步读:无需创建监听器,一次性获取完整数据。方便简单,但是数据量大时会有等待时常,也可能内存溢出。

测试项目导入数据

  • 监听器
package com.xc.usercentor.once;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.xc.usercentor.model.domains.User;
import com.xc.usercentor.service.UserService;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class DemoDataListener implements ReadListener<User> {
    private UserService userService;
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 10000;
    public DemoDataListener(UserService userService) {
        this.userService = userService;
    }

    /**
     * 缓存的数据
     */
//    private List<User> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    List<List<User>> lists = ListUtils.newArrayListWithExpectedSize(100);
    List<User> list = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 这个每一条数据解析都会来调用
     *
     * @param user    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(User user, AnalysisContext context) {

            if (list.size() >= BATCH_COUNT) {
                lists.add(new ArrayList<>(list));
                list = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
            }
            list.add(user);
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData(lists);
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData(List<List<User>> lists) {
        for(int i = 0; i < lists.size(); i++){
            List<User> users = lists.get(i);
            userService.saveBatch(users);
        }
    }
}
  • 测试
    @Test
    void contextLoads() {
        String fileName = "D:\\demo.xlsx";
        // 写法1:JDK8+ ,不用额外写一个DemoDataListener
        // since: 3.0.0-beta1
        // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置
        long start = System.currentTimeMillis();
        EasyExcel.read(fileName, User.class, new DemoDataListener(userService)).sheet().doRead();
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

100w条数据导入数据库时间131秒

img

使用自定义线程池+CompletableFuture并发编程提高了导入数据库性能

  • 监听器
package com.xc.usercentor.once;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.xc.usercentor.model.domains.User;
import com.xc.usercentor.service.UserService;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

@Slf4j
public class DemoDataListener implements ReadListener<User> {
    private UserService userService;
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 10000;
    public DemoDataListener(UserService userService) {
        this.userService = userService;
    }

    /**
     * 缓存的数据
     */
//    private List<User> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    List<List<User>> lists = ListUtils.newArrayListWithExpectedSize(100);
    List<User> list = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 这个每一条数据解析都会来调用
     *
     * @param user    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(User user, AnalysisContext context) {

            if (list.size() >= BATCH_COUNT) {
                lists.add(new ArrayList<>(list));
                list = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
            }
            list.add(user);
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData(lists);
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData(List<List<User>> lists) {
        List<CompletableFuture<Void>> futureList = new ArrayList<>();
        for(int i = 0; i < lists.size(); i++){
            List<User> users = lists.get(i);
            CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
                userService.saveBatch(users,10000);
            });
            futureList.add(future);
        }
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{})).join();
    }
}

最后用时35秒

CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
            userService.saveBatch(users,10000);
        });
        futureList.add(future);
    }
    CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{})).join();
}

}

img

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值