读取excel大数据量详解

news/2024/7/5 1:51:03

需求:导入大数据量excel文件到数据库(测试11MB,40w行数据)

首先说结论:都是大概时间,且其中有两个参数需要调,这里统一下参数大小。

  1. 监听器中的缓存list一次性存100000(测试过100000比30000快)
  2. 并行工具类中的size,一个线程一次性处理的数据量1000条数据(底层mabatis在连接sql时到1000,也会自动插入)(测试的1000比2000和100更快)
采用方法耗时
2.x版本+单线程+saveBatch74s
2.x版本+16线程+saveBatch(采用多线程)17s
2.x版本+16线程+手写sql(优化sql)14s
3.2.1+版本+16线程+手写sql(采用新版本)10s

注:这是在本地数据库,如果是远程数据库(耗时会长一点,我测试的远程慢了一倍)

解决方案:

使用库:EasyExcel,相比poi、jxl更快。

官网介绍:EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。他能让你在不用考虑性能、内存的等因素的情况下,快速完成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模式,在上层做了模型转换的封装,让使用者更加简单方便。(来自官网)

EasyExcel官方网址:https://easyexcel.opensource.alibaba.com/

优化:

代码就不在这里写了,可以直接参考官网,将一些优化手段和踩的坑说一下

示例表(student):

字段名字段类型
idint
namestring
ageint

优化方案一:采用多线程

使用 :在导入数据时,通过Java8中的stream流中的并行流来对数据进行多线程导入,工具类如下,使用方法代码如下
踩坑或疑问 :本人电脑是16线程,我将线程参数设置为2,当我一次性用list接收30000时,一个线程插入10000数据时,确实是使用两个线程,但是当我把一个线程一次性插入1000时,线程参数不管设置为多少(大于1)时,16个线程全跑起来了,关于在运行时查看线程可以使用stream流中的peek(),已写入下面的工具类代码中。

// 工具类使用方法:第一个参数为数据集合,第二个为插入数据的方法,可自定义
InsertConsumerUtils.insertData(Student, studentService::saveBatch);
// 工具类代码
public class InsertConsumerUtils {
    /**
     * 每个长 SQL 插入的行数,可以根据数据库性能调整
     */
    private final static int SIZE = 1000;

    // 如果需要调整并发数目,修改下面方法的第二个参数即可
    static {
        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism"
        					, "4");
    }

    /**
     * 插入方法
     *
     * @param list     插入数据集合
     * @param consumer 消费型方法,直接使用 mapper::method 方法引用的方式
     * @param <T>      插入的数据类型
     */
    public static <T> void insertData(List<T> list, Consumer<List<T>> consumer) {
        if (list == null || list.size() < 1) {
            return;
        }
        List<List<T>> streamList = new ArrayList<>();

        for (int i = 0; i < list.size(); i += SIZE) {
            int j = Math.min((i + SIZE), list.size());
            List<T> subList = list.subList(i, j);
            streamList.add(subList);
        }
        // 并行流使用的并发数是 CPU 核心数,不能局部更改。全局更改影响较大,斟酌
        streamList.parallelStream()
                .peek(ts -> System.out.println(Thread.currentThread().getName()))
                .forEach(consumer);
    }
}

优化方案二:sql优化,批量插入

刚开始使用的是MP中的saveBatch以为是批量插入,后面查看资料和源码发现,MP中的saveBatch是将一句一句的sql进行插入,也就是如下示例代码

// 举例说明,不用在意字段是什么
insert into table values (1, "张三", 18)
insert into table values (2, "李四", 19)
insert into table values (3, "王五", 10)

于是我们需要将sql变成真正意义上的批量插入,代码示例如下

// 举例说明,不用在意字段是什么
insert into student values (1, "张三“, 18),(2, "李四", 19),(3, "王五", 10)

说到这里,肯定有小伙伴有疑问,不知道写这个sql怎么办,mybatis中的代码如下:

<insert id="{写你的函数名}" useGeneratedKeys="true" keyProperty="unid" 
			parameterType="{写你entity类全路径}">
        insert into student(name,age)
        values
        <foreach collection="list" item="item" index="index" separator=",">
            (#{item.name}, #{item.age})
        </foreach>
    </insert>

正常来说代码就是这样,但是有个问题,就是excel导入的数据一般没有id,如果数据库id没有设置自动增长,就需要我们自己插入id,代码如下

<insert id="{写你的函数名}" useGeneratedKeys="true" keyProperty="unid" 
			parameterType="{写你的entity类全路径}">
		<selectKey keyProperty="id" resultType="{写你entity类全路径}" order="BEFORE">
            select if(max(id) is null,1, max(id) + 1 ) as id from student
        </selectKey>
        insert into student(id,name) values
        <foreach collection="list" item="item" index="index" separator=",">
            (#{item.id}, #{item.name})
        </foreach>
    </insert>

但是这样又带来了一个问题,使用了多线程,导致生成的id会重复,所以在多线程下,需要手动生成id,并且手动设置,于是我们又需要一个工具类,利用雪花算法生成的19位id(大部分人用的这种id策略)
由于导致篇幅过长,将生成雪花id工具类查看下面一篇博客,链接如下:
雪花id生成工具类

使用方法如下:

student.setId(snowflakeIdWorker.nextId())

优化方案三:采用新版本

在官网上我们可以看到不同版本的速度描述:可以看到最新的版本在空间和时间上综合是最优的,一代版本一代神嘛,下面是整理官网给出的数据。

版本速度
2.x64M内存1分钟内读取75M(46W行25列)的Excel
3.0.2+版本64M内存20秒读取75M(46W行25列)的Excel
3.2.1+版本16M内存23秒读取75M(46W行25列)的Excel

刚开始由于公司项目中很多依赖和3.x版本有冲突,于是采用了2.x版本,后面学到了一个处理依赖冲突的方法(又get一个新技能),就是下载插件 Maven Helper,如何操作网上有很多文章,就不细说了,处理完依赖冲突后,就可以使用了。

问题:在2.x版本中,监听器是继承AnalysisEventListener,3.x中变成了实现ReadListener,刚开始没有跑成功,如果使用3.x版本,出错时可以考虑还是采用2.x版本中的继承AnalysisEventListener,网上关于ReadListener的博客也非常少,猜测可能是使用的人比较少吧。

// 2.x 监听器
public class DemoDataListener extends AnalysisEventListener<DemoData>
// 3.x 监听器
public class DemoDataListener implements ReadListener<DemoData>

其他:

1、官网有提到一个急速模式,经测试没有效果
开启急速模式:急速模式可以自己开启后测试下内存的占用,如果感觉符合预期可以直接开启。如果最大文件条数也就十几二十万,然后excel也就是十几二十M,而且不会有很高的并发,并且内存也较大。这种情况下可以考虑开启极速模式。

EasyExcel.read().readCache(new MapCache());

2、监听器中的缓存list和并行工具类中的size,一个是缓存一次性存多少,一个是每个线程插入的数量,这两个参数的改变会影响全局的速度,网上有一些说批量插入速度最快为20,我测试了发现也不是,网上很多文章也不是很准确,有兴趣的小伙伴可以自行去调整这两个参数,不同的场景,不同服务器每个人最合适的参数应该是不一样的。

注:本人也是刚刚接触这个easyExcel,如果有误欢迎指正,另外还有疑问的小伙伴可留言


http://lihuaxi.xjx100.cn/news/1021403.html

相关文章

Leetcode.1306 跳跃游戏 III

题目链接 这里是引用 Leetcode.1306 跳跃游戏 III Rating &#xff1a; 1397 题目描述 这里有一个非负整数数组 arr&#xff0c;你最开始位于该数组的起始下标 start处。当你位于下标 i 处时&#xff0c;你可以跳到 i arr[i]或者 i - arr[i]。 请你判断自己是否能够跳到对应…

Windows Subsystem for Android (WSA) 下载:在 Windows 11 上运行 Android 应用 (April 2023)

适用于 Android™️ 的 Windows 子系统&#xff0c;2023 年 4 月更新 (April 2023) 请访问原文链接&#xff1a;https://sysin.cn/blog/wsa/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Windows 11 上适用于 Android™ 的 …

【算法系列之二叉树I】leetcode226.翻转二叉树

非递归实现前序遍历 力扣题目链接 解决思路 前序遍历&#xff0c;中左右。先放右节点&#xff0c;后放左节点。 Java实现 class Solution {public List<Integer> preorderTraversal(TreeNode root) {//中左右Stack<TreeNode> stack new Stack<>();List…

【大数据之Hadoop】十三、MapReduce之WritableComparable排序

MapReduce框架必须进行排序&#xff0c;MapTask和ReduceTask都会对key按字典顺序排序&#xff0c;是默认的行为&#xff08;默认使用快速排序&#xff09;&#xff0c;有利于提高效率。任何程序数据都会进行排序&#xff0c;不管逻辑是否需要。 对于排序而言分为两个阶段&#…

响应式UI部件DevExtreme v22.2.5全新发布

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序。从Angular和Reac&#xff0c…

说走就走的旅行?你需要一个旅行必备清单

可能很多朋友都不用清单这个东西&#xff0c;更别说清单模版了。那清单真的好用吗&#xff1f;说实话&#xff0c;当你真的用清单来整理自己的日常工作&#xff0c;乃至生活琐事后&#xff0c;你就会发现你的时间多了&#xff0c;想要完成的事&#xff0c;大部分都可以按时完成…

MZ深度解读SAP常见财务问题-02-账套在哪里?

发文词&#xff1a;类似于新刊物的“发刊词”&#xff0c;我们也写两句发文词。笔者前些年关于SAP的文字主要包括“SAP那些事”系列文章中了&#xff0c;这些文章的视角主要是从顾问的角度进行描述的&#xff0c;侧重的也是系统功能和顾问职业的描述。 最近&#xff0c;笔者认…

【网络应用开发】实验3——Web组件重用与JavaBeans

目录 Web组件重用与JavaBeans预习报告 一、实验目的 二、实验原理 三、实验预习内容 1. 静态include指令何时执行&#xff1f;主页面和被包含的子页面是否转换为一个转换单元&#xff1f; 2.动作指令何时执行&#xff1f;主页面和被包含的子页面是否转换为一个转换单元&a…