Excel文件的深度解析

Excel深度解析历程

故事背景

      最近公司分配了一个任务,里面就有各种各样的文件解析,由于公司之前也找过外包公司开发过同样的代码,
   但是xlsx格式的文件接口响应速度惨不忍睹,下面用的全部都是同一个文件测试,文件大小7.8M,行数2.1w行,
   之前的版本,执行当前文件的计算耗时12秒!!!
     于是乎,我就成了重新开发那个模块的“大冤种”;

技术选型方案

一、 直接采用现成的框架

	 1. 可以使用easypoi框架,简单易用,详情可以查看我的另一篇 
	 博客[链接]  https://blog.csdn.net/q82682810X/article/details/114314333/ ,
 不出意外的话,应该旧版本就采用的是这种懒人模式,代码敲起来是快了,系统得垮了。
	 2. 使用阿里提供的easyExcel工具类,sax模式读取,性能倍感提升!
	 3. 使用apache提供的excel解析基础类,进行解析

二、 自己处理解析

  这里最大的困难就是得去了解excel解析的各种api,操作excel的方式等。还得自己去摸轮子。

遇到的困难

      看了上面的选型,肯定很多人第一反应就是使用阿里的框架,靠谱,性能又好。没错我一开始也是这么决定的,
  但是接着就有了另外一个问题,阿里提供的easyExcel工具类不支持解析csv格式的文件解析。很不凑巧的是easypoi
  他支持,(正可谓鱼和熊掌不可兼得);
     那这时候肯定就有人说了,解析xlsx的时候用阿里的,csv用easypoi的不久ok了么?
  后来实操的时候依赖冲突严重,两则都引入了apache的excel解析模块...而且在单元测试的时候,发现easyExcel(阿里)
  的也渐渐不能满足我的“野心”了。(解析一个7.8M的xlsx文件,纯处理行读取(不做任何解析操作),2.1w行数据,耗时3.2s)

升级的思路

一、大胆的创想

    会不会是因为Java语言天生的特性,根本就发挥性能达不到极致呢?顿时脑光一乍!C语言解析excel!!!
然后想着用java通过调用jni的方式去获取文件的解析值!!!
    于是乎,我就在网上查各种C语言解析excel的例子,能跑通就可以做一个直接的对比。然后我东找西找,发现
一篇颇为神奇的文章:链接: [link](https://blog.csdn.net/skv00d00/article/details/123419791) ,于是乎
奇怪的只是增加了——这篇文章是解压xlsx格式的文件,然后根据里面的xml文件去做数据解析,然后一步步进入文件探究
(这里一定要注意,仅适用于xlsx格式的文档)

解析完的文档

    好家伙,原来他就是一个压缩包文件,剩下的就是怎么从解析好的文件里面去拿到自己想要的数据信息了。
 经过一番简单的摸索,终于定位到了两个文件:分别是 xl/sharedStrings.xml 和 xl/worksheets/sheet1.xml 这两
 个文件, 第一个文件一看名字就很有来头,共享字符串?后面经过验证,发现,所有表格里面的字符数据,
 全部都可以在这个文件里面找得到,剩下的谜题就在sheet1.xml 文件中了,不出意外的话,肯定是利用的数据索引
 的方式去进行字符串关联。结果果然不出我的意料!

sheet.xml 中 row标签代表的是行信息,c标签代表是新的一列,指的是column,c标签中有两个属性,第一个属性代表他属于第几列 例如:A1、BA1列这种(BA1代表BA列,第1行),还有一个s值,这个s就标识着他是不是字符串,如果是,则需要从共享字符串文件中的索引下标去获取目标值,否则他可能是数字类型,直接读取。

二、深度解析实践

第一步

 先创建一个解析Excel的工具类(DeepExcelParse.class) ,如果只需要excel文件File对象,
 NodeList是用作于接收sharedStrings的管理,继承的DefaultHnadler主要是用作于sheet.xml文件的sax模式读取
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.core.util.ZipUtil;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.helpers.DefaultHandler;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public class DeepExcelParse extends DefaultHandler implements Closeable {

    private final File tempFile;

    private final List<Entity> result;

    /**
     * stringSharing
     */
    private final NodeList nodeList;

    public DeepExcelParse(File excelFile) {
        tempFile = new File(excelFile.getParent(), IdUtil.fastSimpleUUID());
        ZipUtil.unzip(excelFile, tempFile);
        Document document = XmlUtil.readXML(
                new File(tempFile, "xl" + File.separator + "sharedStrings.xml"));
        this.nodeList = document.getElementsByTagName("t");
        this.result = new LinkedList<>();
    }

    @Override
    public void close() throws IOException {
        FileUtil.del(tempFile);
    }

}

利用到的实体对象,这里仅做DEMO展示:

    @Data
    private class Entity{
        private String id;
        private String name;
    }

第二步

重写Handler解析的方法,暴露一个start开始解析的入口
	public void start(){
        File target = new File(tempFile, "xl" + File.separator 
                + "worksheets" + File.separator + "sheet1.xml");
        if (!FileUtil.exist(target)) {
            throw new RuntimeException("系统异常");
        }
        XmlUtil.readBySax(target, this);
    }

第三步

接下来就得处理真正的解析流程了,重写DefaultHandler的方法,核心的有:startElement,endElement,characters方法
这里就不具体展示解析流程(如果有需要的话,后续再更新这里的内容),
startElement 标签开始时会调用的方法,这里可以获取到标签类型 例如:<div id='id1'><span attr='abc'>test</span></div>,他会进
入两次这个方法,一次拿到的是div标签,第二次会来到span标签,都是指的是解析开始,这里只能够获取得到id和attr属性的值,
拿不到 span标签里面的 ‘test’值
endElement方法:标签结束的回调,这里也会进入两次,刚好和上面的函数以一对对的方式进行回调
characters 方法: 这里是负责获取span标签内部的内容,方法定义:characters(char[] ch, int start, int length)
如果是字符串的话,则直接通过 new String(ch,start,length);就可以解析到标签内部的内容块。

性能测试

    捣腾了大半天之后,终于要迎来了我的性能测试对比时间了,也是同样的excel文档,2.1W行数据,解析耗时2.3秒!
 小点:优化的点不单止是解析方式不一样,而且我在解析文件的过程中,会跳过很多我不关心的数据列,但是现成的框架
 并不会知道调用者需要的是哪些字段,会逐个字段或者通过反射获取@Excel注解上面的值去决定获取哪些列的数据,但是
 反射终究会有性能问题。目前优化暂告一段落。

性能提升空间

上面展示的例子还是有缺点的,最大的确定就是会直接加载整个共享字符串的内容进来,在某种情况下,他可能会很大,
进而占用了大部分的内存空间,再高并发的背景下,会频繁触发GC,并且会有OOM的风险,这时候就需要更加合适的GC
处理器了,再者就是将JDK的版本升至9或以上,java9中有个新特性是专门针对String.class做出优化,核心是减少了
一些单字节可以展示的字符串的占用空间(会有两种模式,一种是jdk8以前的,用两个字节代表一个字符,现在会考虑
动态切换占用一个字节/两个字节)

小结

在经历了这一波探索之后感觉收货良多,一是把xlsx的老底掀开了解了一遍,有发现新天地的小惊喜,有性能优化上面的提升的喜悦。由最开始的12秒,优化成6秒,到最后的2.8秒!还是有相当的大的收货的。
如果有更好的解析方式也欢迎来分享!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340