Apache POI Java读取100万行Excel性能优化:split vs indexOf+subString,谁性能好

摘要:
对于这样一个简单的字符串分割,indexOf+subString的性能如何?使用此方法重写前面的splitLine和getRowNum。代码如下:privateStringgetRowNum{intindex=-1;for{if{index=k;break;}}如果{String[]nums=newString[]{cellRef.substring,cellRef.substring};如果{returnnums[1];}}返回“-1”;}privateString[]splitLine{intindex=line.indexOf;if{returnnewString[]{line.substring,line.substrang};}returnnewString[0];}优化后,使用jvisualvm测试每个方法的执行时间:如您所见,我自己的数据处理方法不再是明显的性能瓶颈,而Apache POI的压缩解压缩和文件读取占用了大部分时间。我们知道indexOf是一种暴力搜索。拆分使用正则表达式进行匹配。当搜索字符串简单时,indexOf必须表现良好。

使用Apache POI eventmodel实现一个Excel流式读取类,目标是100万行,每行46列,文件大小152MB的Excel文件能在20s读取并处理完。一开始实现的程序需要260s,离目标差太远了,使用jvisualvm分析各方法执行时间,结果如下:

Apache POI Java读取100万行Excel性能优化:split vs indexOf+subString,谁性能好第1张

可以看到,程序中的splitLine和getRowNum方法消耗了大量时间。这两个方法都特别简单。splitLine方法将类似“123==hello”这样的字符串分解成{"123","hello"}数组,使用了String.split方法,getRowNum从Excel单元格地址字符串(比如“AB123456”)中获取行号“123456”,以下是原始实现方法:

privateString getRowNum(String cellRef){
    if(cellRef == null || cellRef == ""){
        return "-1";
    }
    
    String[] nums = cellRef.split("\D+");
    if(nums.length > 1){
        return nums[1];
    }
    return "-1;
}

privateString[] splitLine(String line){
    return line.split("==");
}

两个如此简单的方法却消耗了这么多时间,一时间不知如何优化。最后突然想到:split的性能是否最优呢?对于如此简单的字符串分割,使用indexOf + subString性能如何呢?于是,我做了如下的实验:

public static void main(String[] args) throwsParseException{
    String str = "AB123456";
    long start =System.currentTimeMillis();
    for(int i = 0 ; i < 10 * 10000 ; i ++){
        String[] lines = str.split("\D+");
    }
    long end =System.currentTimeMillis();
    System.out.println("split time consumed:" + (end - start) / 1000.0 + "s");
    
    start =System.currentTimeMillis();
    int index = -1;
    for(int i = 0 ; i < 10 * 10000 ; i ++){
        index = -1;
        for(int k = 0 ; k < str.length() ; k ++){
            if(str.charAt(k) >= '0' && str.charAt(k) <= '9'){
                index =k;
                break;
            }
        }
        
        if(index > 0){
            String[] lines = new String[]{str.substring(0, index),str.substring(index)};
        }
    }
    end =System.currentTimeMillis();
    System.out.println("indexof time consumed:" + (end - start) / 1000.0 + "s");
}

以下是输出结果:
split time consumed:0.104s
indexof time consumed:0.007s

虽然表面上看,split比index + subString要简单很多,但后者性能是前者的将近15倍。用这种方法改写前面的splitLine和getRowNum,代码如下:

privateString getRowNum(String cellRef){
    int index = -1;
    for(int k = 0 ; k < cellRef.length() ; k ++){
        if(cellRef.charAt(k) >= '0' && cellRef.charAt(k) <= '9'){
            index =k;
            break;
        }
    }
    
    if(index >= 0){
        String[] nums = new String[]{cellRef.substring(0, index),cellRef.substring(index)};
        if(nums.length > 1){
            return nums[1];
        }
    }
    
    return "-1";
}

privateString[] splitLine(String line){
    int index = line.indexOf("==");
    
    if(index > 0){
        return new String[]{line.substring(0, index),line.substring(index + 2)};
    }
    
    return new String[0]; 
}

优化后再用jvisualvm测试各方法执行时间:
Apache POI Java读取100万行Excel性能优化:split vs indexOf+subString,谁性能好第2张

可以看到,我自己的数据处理方法已不是明显的性能瓶颈,而Apache POI的zip解压和文件读取占用了绝大部分时间。整体时间也从260s下降到了160s,已有了明显的提高。

我们知道indexOf就是暴力搜索,split内部使用正则表达式做匹配,在搜索字符串较简单时肯定是indexOf性能好。大多数情况下调用split时都用不到正则表达式的那些高大上功能,所以完全没必要图方便在任何时候都用split,而是有所取舍:当简单分割字符串时自己用indexOf实现split,而涉及到复杂的分割操作,不得不用正则表达式时,才用split。为了看清String.split方法在做什么,我们看看JDK中String.split的源码:

    public String[] split(String regex, intlimit) {
        /*fastpath if the regex is a
         (1)one-char String and this character is not one of the
            RegEx's meta characters ".$|()[{^?*+\", or
         (2)two-char String and the first char is the backslash and
            the second is not the ascii digit or ascii letter.
         */
        char ch = 0;
        if (((regex.value.length == 1 &&
             ".$|()[{^?*+\".indexOf(ch = regex.charAt(0)) == -1) ||(regex.length() == 2 &&regex.charAt(0) == '\' &&(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&((ch-'a')|('z'-ch)) < 0 &&((ch-'A')|('Z'-ch)) < 0)) &&(ch < Character.MIN_HIGH_SURROGATE ||ch >Character.MAX_LOW_SURROGATE))
        {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) {
                if (!limited || list.size() < limit - 1) {
                    list.add(substring(off, next));
                    off = next + 1;
                } else {    //last one
                    //assert (list.size() == limit - 1);
list.add(substring(off, value.length));
                    off =value.length;
                    break;
                }
            }
            //If no match was found, return this
            if (off == 0)
                return new String[]{this};

            //Add remaining segment
            if (!limited || list.size() <limit)
                list.add(substring(off, value.length));

            //Construct result
            int resultSize =list.size();
            if (limit == 0)
                while (resultSize > 0 && list.get(resultSize - 1).length() == 0)
                    resultSize--;
            String[] result = newString[resultSize];
            return list.subList(0, resultSize).toArray(result);
        }
        return Pattern.compile(regex).split(this, limit);
    }

尽管split方法的实现还是挺优化的,但仍做了太多的操作。

想一想我过去写的代码经常图方便滥用String.split,这样是经不起大数据量考验的,学了这么长时间Java,竟从没想过这样的问题,不禁感叹自己还是菜鸟。虽然像Java或C#这种语言各种方法使用起来方便,但其库方法之下隐藏的性能开销,需要每一个使用者注意。

(全文完)

免责声明:文章转载自《Apache POI Java读取100万行Excel性能优化:split vs indexOf+subString,谁性能好》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Hazelcast介绍C#枚举(一)使用总结以及扩展类分享下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

asp.net CommandArgument用法

1.绑定数据库中一个主键前台代码: <ItemTemplate> <asp:ImageButton ID="ibtnUpdate" runat="server" CommandArgument='<%# Eval("studentNum")%>'CommandName="edit"...

把页面的Table直接输出到Excel文件中

有个需求是统计的时候,为生成的html表格提供导出功能,但是这样导出Excel不会显示自身的表格 影响美观,但是excel会显示html的css样式,这里可以通过处理行对象的方式进行导出,但是处理起纵向合并比较麻烦,这里待续! 1 String newStr = new String(name.getBytes(), "ISO8859_1"); 2 res...

尚硅谷《谷粒商城项目总结》

1、前言 花了几天的时间把尚硅谷的视频项目看完了,跟着做了一遍,基本上没啥大的问题,有几个小问题也做了总结。 技术方面除了 vue/nacos 没用过,其他的基本都用过,我们公司实际开发中用的也就是这一套东西。 中间的不想看,可以直接点击目录,看总结,总结里有你针对此项目所有的总结及问题解决的说明 1.1技术栈 springcloud 统一配置中心:apo...

C#字节取高低位以及byte和string的转换

byte a = 0xF9; string high = Convert.ToString((a & 0xf0) >> 4);//这里的位操作数以及位移的数目可以根据自己的需要修改 string low = Convert.ToString(a & 0x0f);//这里的位操作数以及位移的数目可以根据自己的需要修改 byte和s...

Android驱动学习-APP操作新硬件的两种方法(支持添加的驱动)

在给Android添加新的驱动后,app要如何使用呢? 正常的使用一个设备,需要getService。但是像LED等我们自己添加的硬件驱动,Android源代码根本没有我们自己添加的服务。 第一种: 我们自己的创建的硬件设备驱动的类是被系统定义为了隐藏类,那么在Android系统中如何使用隐藏类呢?为此我们可以根据android的编译过程可以看到我们添加的...

Solr与MySQL查询性能对比

测试环境 本文简单对比下Solr与MySQL的查询性能速度。 测试数据量:10407608     Num Docs: 10407608 普通查询 这里对MySQL的查询时间都包含了从MySQL Server获取数据的时间。 在项目中一个最常用的查询,查询某段时间内的数据,SQL查询获取数据,30s左右 SELECT * FROM `tf_hotspotd...