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秒!还是有相当的大的收货的。
如果有更好的解析方式也欢迎来分享!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值