POI框架EXCEL解析性能优化

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: POI EXCEL SAX 解析内存优化

背景

在做商品EXCEL的时候,线上发现了Full GC,排查得知是商家搞了一个巨大的excel,单商品发布接口平均耗时400ms(调用sell耗时200ms左右,系统自身处理商品同步耗时150ms左右),对于3000个商品的发布,耗时在20min左右,这20min内该excel的内存一直未能释放。

excel full gc.png

第一时间想到的是POI真坑,真吃内存。 事情发生了就想着怎么处理,

  1. 止血 线上机器分批重启,
  2. 马上加一个excel行数的限制然后发布 线上半个小时左右就没有任何问题了。

思考

为什么poi这么吃内存,poi这么老了,肯定有人踩过这个坑,撸起袖子,搜poi full gc. 很多文档将的都太粗糙了,本质没有说透

原因

  1. excel本质上是xml文件的集合体。从office 2007起开始使用xml来存档和数据交换:https://zh.wikipedia.org/wiki/Office_Open_XML
  2. poi默认是使用dom方式解析excel,因此文件中String的数量越多,其dom树越大。

解法

由于excel商品发布不需要动态的更改excel中的数据,所以并不强依赖dom解析,直接换成sax来解析excel就行

Action

poi中sax用法


/**
 * @author zhengqiang.zq
 * @date 2018/05/04 ,参考链接:https://poi.apache.org/spreadsheet/how-to.html#sxssf
 */
public class MyEventUserModel {
    public static ThreadLocal<List<ParsedRow>> local = new ThreadLocal<>();

    public void processOneSheet(String filename) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader r = new XSSFReader(pkg);
        SharedStringsTable sst = r.getSharedStringsTable();

        XMLReader parser = fetchSheetParser(sst);
        //从workbook.xml.res 中获取所有需要解析的xml文件,rid1 就是第一个sheet,其target就是该sheet所在的相对路径
        //<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        //<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
        // <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
        // <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
        // <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
        // <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
        // <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet2.xml"/>
        //</Relationships>
        //
        InputStream sheet2 = r.getSheet("rId1");
        InputSource sheetSource = new InputSource(sheet2);
        parser.parse(sheetSource);
        sheet2.close();
    }

    public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException {
        XMLReader parser =
            XMLReaderFactory.createXMLReader(
                "org.apache.xerces.parsers.SAXParser"
            );
        ContentHandler handler = new SheetHandler(sst);
        parser.setContentHandler(handler);
        return parser;
    }

    /**
     * See org.xml.sax.helpers.DefaultHandler javadocs
     */
    private class SheetHandler extends DefaultHandler {

        /**
         * excel 常量数据对象,对应的就是sharedStrings.xml文件中的内容,类似excel中的常量池
         */
        private SharedStringsTable sst;
        /**
         * 当前处理的文本值
         */

        private String lastContents;
        /**
         * 下一个文本是不是String类型
         */
        private boolean nextIsString;
        /**
         * 当前单元格的索引值,从0开始,0:第一列
         */
        private Short index;
        /**
         * 自定义数据类型,存储被解析的每一行原始数据
         */
        List<ParsedRow> sheetData = Lists.newArrayList();
        ParsedRow currentRow = new ParsedRow();

        private SheetHandler(SharedStringsTable sst) {
            this.sst = sst;
        }

        @Override
        public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
            //第一行
            if (name.equals("row")) {
                currentRow.setRowNum(new Long(attributes.getValue("r")));
                sheetData.add(currentRow);
            }
            //c => cell 一个单元格,
            if (name.equals("c")) {
                //r属性表示单元格位置,例如A2,C3
                String coordinate = attributes.getValue("r");
                CellReference cellReference = new CellReference(coordinate);
                //根据r属性获取其列下标,从0开始
                index = cellReference.getCol();

                //t:属性代表单元格类型
                String cellType = attributes.getValue("t");
                if (cellType != null && cellType.equals("s")) {
                    //t="s"表示是改单元格是字符串,那么该单元格的实际值值需要去SharedStringsTable中取
                    nextIsString = true;
                } else {
                    nextIsString = false;
                }
            }
            // Clear contents cache
            lastContents = "";
        }

        @Override
        public void endElement(String uri, String localName, String name) throws SAXException {
            if (nextIsString) {
                int idx = Integer.parseInt(lastContents);
                //从SharedStringsTable中取当前单元格的实际值
                lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
                nextIsString = false;
            }

            // v => contents of a cell
            // Output after we've seen the string contents
            if (name.equals("v")) {
                //不管是不是数字还是文本值
                currentRow.getData().put(index, lastContents);
            }
            if (name.equals("row")) {
                currentRow = new ParsedRow();
            }
        }

        @Override
        public void endDocument() throws SAXException {
            local.set(sheetData);
        }

        /**
         * 通知一个元素中的字符,是否处理由自己决定,比如  <v>1</v>,
         *
         * @param ch     The characters. 整个sheet.xml的char[]数组表示
         * @param start  The start position in the character array. 本次处理的元素值的的开始位置
         * @param length The number of characters to use from the ,元素长度
         *               character array.
         * @throws SAXException Any SAX exception, possibly
         * wrapping another exception.
         * @see ContentHandler#characters
         */
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            //对于lastContents是String类型来说,lastContent存放的是其在SharedStringsTable中的索引,
            // 对于是数字类型来说,lastContents存放就是该数字的字符串表示
            lastContents += new String(ch, start, length);
        }
    }

    public static void main(String[] args) throws Exception {
        String fileName = "/Users/thinerzq/alltest/excel/test_big_3300_diffrent_row.xlsx";
        MyEventUserModel example = new MyEventUserModel();
        Stopwatch stopwatch = new Stopwatch();

        stopwatch.start();
        example.processOneSheet(fileName);
        System.out.println("-----------------finish, " + stopwatch.toString());

        System.out.println(local.get());
        Thread.sleep(100000 * 1000);
    }
}

性能对比

dom 3455行

解析时间

-----------------finish, 6.987 s

内存消耗

jmap -histo:live 2646

thinerzq@thinerzq-2:~$ jmap -histo:live 2646

 num     #instances         #bytes  class name
----------------------------------------------
   1:       2574454      247147584  org.apache.xmlbeans.impl.store.Xobj$AttrXobj
   2:       1332126      127884096  org.apache.xmlbeans.impl.store.Xobj$ElementXobj
   3:       1265264       50610560  java.util.TreeMap$Entry
   4:        778421       48667664  [C
   5:           636       37006672  [B
   6:        611910       29371680  java.util.TreeMap
   7:        611886       24475440  org.apache.xmlbeans.impl.values.XmlUnsignedIntImpl
   8:        653334       20906688  org.apache.poi.xssf.usermodel.XSSFCell
   9:        653334       20906688  org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.STCellRefImpl
  10:        775269       18606456  java.lang.String
  11:        653334       15680016  org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTCellImpl
  12:        611866       14684784  org.apache.poi.xssf.usermodel.XSSFRow
  13:        611866       14684784  org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTRowImpl
  14:        622210        9955360  java.lang.Integer
  15:         55239        1767648  org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.STXstringImpl
  16:         34552        1105664  org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.STCellTypeImpl
  17:         18776         600832  java.util.HashMap$Node
  18:         10328         247872  org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTRstImpl
….
 726:             1             16  sun.util.resources.LocaleData$LocaleDataResourceBundleControl
Total      11909576      686173768=81.8MB

sax 3455行

解析时间

-----------------finish, 2.427 s

内存消耗

jmap -histo:live 2646


thinerzq@thinerzq-2:~$ jmap -histo:live 2711

 num     #instances         #bytes  class name
----------------------------------------------
   1:        612060       29378880  java.util.HashMap
   2:        611866       14684784  com.zq.poi.ParsedRow
   3:        611866       14684784  java.lang.Long
   4:          1298        3342120  [Ljava.lang.Object;
   5:         60140        3149488  [C
   6:         51946        1662272  java.util.HashMap$Node
   7:         60096        1442304  java.lang.String
   8:          3610         578024  [Ljava.util.HashMap$Node;
   9:           617         288960  [B
  10:          1626         186544  java.lang.Class
  11:           975         173176  [I
  12:          2911         116440  java.util.LinkedHashMap$Entry
  13:          2648          63552  javax.xml.namespace.QName
  14:          1811          57952  java.util.concurrent.ConcurrentHashMap$Node
  15:          2242          53808  org.apache.xmlbeans.SchemaType$Ref
  16:           423          27072  java.net.URL
  17:          1619          25904  java.lang.Object
  18:           290          20496  [Ljava.lang.String;
 531:             1             16  sun.util.resources.LocaleData$LocaleDataResourceBundleControl
Total       2036715       70309432 =8.4MB

总览

解析类型 数据量 解析时间 内存占用
dom 3455行不同数据 6.587 s 81.8MB
sax 3455行不同数据 2.427 s 8.4MB
dom 10000行数据,2/3重复 6.748s 100.4M
sax 10000行数据,2/3重复 2.827s 9.4MB

可以看到使用sax解析之后内存下降了近10倍之多,再也不用担心full gc了。由于其常量池的缘故,excel文件大小和行数是否有重复的单元格有关系。

其他

参考链接

  1. poi文档

查看excel的xml文件

将excel文件的后缀名改为.zip 然后解压缩里面就全部都是xml文件了。

xml文件快速一览

excel_constants.png
excel_sceen1.png

目录
相关文章
|
5天前
|
前端开发 JavaScript C#
移动应用开发中的跨平台框架解析
【9月更文挑战第5天】在移动应用开发领域,跨平台框架因其“一次编写,处处运行”的便利性而受到开发者的青睐。本文将深入探讨几种流行的跨平台框架,包括React Native、Flutter和Xamarin,并比较它们的优势与局限。我们将通过代码示例揭示这些框架如何简化移动应用的开发过程,同时保持高性能和良好的用户体验。无论你是新手还是有经验的开发者,这篇文章都将成为你了解和选择跨平台框架的宝贵资源。
38 19
|
13天前
|
存储 算法 Java
Java中的集合框架深度解析云上守护:云计算与网络安全的协同进化
【8月更文挑战第29天】在Java的世界中,集合框架是数据结构的代言人。它不仅让数据存储变得优雅而高效,还为程序员提供了一套丰富的工具箱。本文将带你深入理解集合框架的设计哲学,探索其背后的原理,并分享一些实用的使用技巧。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇通往高效编程的大门。
|
23天前
|
JavaScript 前端开发 算法
【Vue秘籍揭秘】:掌握这一个技巧,让你的列表渲染速度飙升!——深度解析`key`属性如何成为性能优化的秘密武器
【8月更文挑战第20天】Vue.js是一款流行前端框架,通过简洁API和高效虚拟DOM更新机制简化响应式Web界面开发。其中,`key`属性在列表渲染中至关重要。本文从`key`基本概念出发,解析其实现原理及最佳实践。使用`key`帮助Vue更准确地识别列表变动,优化DOM更新过程,确保组件状态正确维护,提升应用性能。通过示例展示有无`key`的区别,强调合理使用`key`的重要性。
39 3
|
11天前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
61 0
|
11天前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
44 0
|
11天前
|
开发者 测试技术 Android开发
Xamarin 开发者的五大常见问题及解决方案:从环境搭建到性能优化,全面解析高效跨平台应用开发的技巧与代码实例
【8月更文挑战第31天】Xamarin 开发者常遇问题及解决方案覆盖环境搭建至应用发布全流程,助新手克服技术难关。首先需正确安装配置 Visual Studio 及 Xamarin 支持,设置 iOS/Android 测试环境。利用 Xamarin.Forms 和 XAML 实现高效跨平台开发,共享 UI 和业务逻辑代码。针对性能优化,采取减少 UI 更新、缓存计算结果等措施,复杂问题则借助 Xamarin Profiler 分析。
24 0
|
11天前
|
Web App开发 IDE 测试技术
自动化测试的利器:Selenium 框架深度解析
【8月更文挑战第31天】在软件开发的世界中,自动化测试是提高产品质量和开发效率不可或缺的一环。本文将深入探讨Selenium这一强大的自动化测试工具,从其架构、优势到实战应用,一步步揭示如何利用Selenium框架提升软件测试的效率和准确性。通过具体的代码示例,我们将展示Selenium如何简化测试流程,帮助开发者快速定位问题,确保软件的稳定性和可靠性。无论你是测试新手还是资深开发者,这篇文章都将为你打开一扇通往高效自动化测试的大门。
|
11天前
|
Java Spring
🔥JSF 与 Spring 强强联手:打造高效、灵活的 Web 应用新标杆!💪 你还不知道吗?
【8月更文挑战第31天】JavaServer Faces(JSF)与 Spring 框架是常用的 Java Web 技术。本文介绍如何整合两者,发挥各自优势,构建高效灵活的 Web 应用。首先通过 `web.xml` 和 `ContextLoaderListener` 配置 Spring 上下文,在 `applicationContext.xml` 定义 Bean。接着使用 `@Autowired` 将 Spring 管理的 Bean 注入到 JSF 管理的 Bean 中。
25 0
|
11天前
|
UED 开发者
哇塞!Uno Platform 数据绑定超全技巧大揭秘!从基础绑定到高级转换,优化性能让你的开发如虎添翼
【8月更文挑战第31天】在开发过程中,数据绑定是连接数据模型与用户界面的关键环节,可实现数据自动更新。Uno Platform 提供了简洁高效的数据绑定方式,使属性变化时 UI 自动同步更新。通过示例展示了基本绑定方法及使用 `Converter` 转换数据的高级技巧,如将年龄转换为格式化字符串。此外,还可利用 `BindingMode.OneTime` 提升性能。掌握这些技巧能显著提高开发效率并优化用户体验。
32 0
|
11天前
|
SQL 存储 数据库

热门文章

最新文章

推荐镜像

更多