`
elvisli
  • 浏览: 18467 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java性能优化:字符串过滤实战

阅读更多



  ★关于需求
  首先描述一下需求,具体如下:给定一个String对象,过滤掉除数字 (字符'0'-'9')以外的其它字符。要求时间开销尽可能小。过滤函数的原型如下:String filter(String str);
   针对上述需求,我写了5个不同的过滤函数。为了叙述方便,分别称为filter1到filter5。其中filter1性能最差、filter5性能最 好。在你接着看后续的内容之前,你先暗自思考一下,如果由你来实现该函数,大概会写成什么样?最好把你想好的函数写下来,便于后面的对比。

  ★代码实现

  ◇测试代码
  为了方便测试性能,先准备好一个测试代码,具体如下:


class Test
{
  public static void main(String[] args)
  {
    if(args.length != 1)
    {
      return;
    }

    String str = "";
    long nBegin = System.currentTimeMillis();
    for(int i=0; i<1024*1024; i++)
    {
      str = filterN(args[0]);  //此处调用某个具体的过滤函数
    }
    long nEnd = System.currentTimeMillis();

    System.out.println(nEnd-nBegin);
    System.out.println(str);
  }
};


   在没有想好你的实现方式之前,先别偷看后续内容哦!另外,先注明一下,我使用的Java环境是JDK 1.5.0-09,使用的测试字符串为“D186783E36B721651E8AF96AB1C4000B”。由于JDK版本和机器性能不尽相同,你在 自己机器上测试的结果可能和我下面给出的数值不太一样。


  ◇版本1
  先来揭晓性能最差的filter1,代码如下:


  private static String filter1(String strOld)
  {
    String strNew = new String();
    for(int i=0; i<strOld.length(); i++)
    {
      if('0'<=strOld.charAt(i) && strOld.charAt(i)<='9')
      {
        strNew += strOld.charAt(i);
      }
    }
    return strNew;
  }


  如果你的代码不幸和filter1雷同,那你的Java功底可就是相当糟糕了,连字符串拼接需要用StringBuffer来优化都没搞明白。
  为了和后续对比,先记下filter1的处理时间,大约在8.81-8.90秒之间。

  ◇版本2
  再来看看filter2,代码如下:


  private static String filter2(String strOld)
  {
    StringBuffer strNew = new StringBuffer();
    for(int i=0; i<strOld.length(); i++)
    {
      if('0'<=strOld.charAt(i) && strOld.charAt(i)<='9')
      {
        strNew.append(strOld.charAt(i));
      }
    }
    return strNew.toString();
  }


   其实刚才在评价filter1的时候,已经泄露了filter2的天机。filter2通过使用StringBuffer来优化连接字符串的性能。为什 么StringBuffer连接字符串的性能比String好,这个已经是老生常谈,我就不细说了。尚不清楚的同学自己上Google一查便知。我估计应 该有挺多同学会写出类似filter2的代码。
  另外,JDK 1.5新增加了StringBuilder,性能会比StringBuffer更好,不过考虑到有可能要拿到其它版本的JDK上作对比测试,而且 StringBuilder和StringBuffer之间的差异不是本文讨论的重点,所以后面的例子都使用StringBuffer来实现。
  filter2的处理时间大约为2.14-2.18秒,提升了大约4倍。

  ◇版本3
  接着看看filter3,代码如下:


  private static String filter3(String strOld)
  {
    StringBuffer strNew = new StringBuffer();
    int nLen = strOld.length();
    for(int i=0; i<nLen; i++)
    {
      char ch = strOld.charAt(i);
      if('0'<=ch && ch<='9')
      {
        strNew.append(ch);
      }
    }
    return strNew.toString();
  }


   乍一看filter3和filter2的代码差不多嘛!你再仔细瞧一瞧,原来先把strOld.charAt(i)赋值给char变量,节省了重复调用 charAt()方法的开销;另外把strOld.length()先保存为nLen,也节省了重复调用length()的开销。能想到这一步的同学,估 计是比较细心的。
  经过此一优化,处理时间节省为1.48-1.52,提升了约30%。由于charAt()和length()的内部实现都挺简单的,所以提升的性能不太明显。
  另外补充一下,经网友反馈,在JDK 1.6上,filter3和filter2的性能基本相同。可能是由于JDK 1.6已经进行了相关的优化。

  ◇版本4
  然后看看filter4,代码如下:


  private static String filter4(String strOld)
  {
    int nLen = strOld.length();
    StringBuffer strNew = new StringBuffer(nLen);
    for(int i=0; i<nLen; i++)
    {
      char ch = strOld.charAt(i);
      if('0'<=ch && ch<='9')
      {
        strNew.append(ch);
      }
    }
    return strNew.toString();
  }


  filter4和filter3差别也很小,唯一差别就在于调用了StringBuffer带参数的构造函数。通过StringBuffer的构造函数设置初始的容量大小,可以有效避免append()追加字符时重新分配内存,从而提高性能。
  filter4的处理时间大约在1.33-1.39秒。约提高10%,可惜提升的幅度有点小 :-(

  ◇版本5
  最后来看看终极版本,性能最好的filter5。


  private static String filter5(String strOld)
  {
    int nLen = strOld.length();
    char[] chArray = new char[nLen];
    int nPos = 0;
    for(int i=0; i<nLen; i++)
    {
      char ch = strOld.charAt(i);
      if('0'<=ch && ch<='9')
      {
        chArray[nPos] = ch;
        nPos++;
      }
    }
    return new String(chArray, 0, nPos);
  }


  猛一看,你可能会想:filter5和前几个版本的差别也忒大了吧!filter5既没有用String也没有用StringBuffer,而是拿字符数组进行中间处理。
  filter5的处理时间,只用了0.72-0.78秒,相对于filter4提升了将近50%。为啥捏?是不是因为直接操作字符数组,节省了append(char)的调用?通过查看append(char)的源代码,内部的实现很简单,应该不至于提升这么多。
  那是什么原因捏?
   首先,虽然filter5有一个字符数组的创建开销,但是相对于filter4来说,StringBuffer的构造函数内部也会有字符数组的创建开 销。两相抵消。所以filter5比filter4还多节省了StringBuffer对象本身的创建开销。(在我的JDK 1.5环境中,这个因素比较明显)
  其次,由于StringBuffer是线程安全的(它的方法都是synchronized),因此调用它的方法有一定的同步开销,而字符数组则没有,这又是一个性能提升的地方。(经网友反馈,此因素在JDK 1.6中比较明显)
  基于上述两个因素,所以filter5比filter4又有较大幅度的提升。

  ★对于5个版本的总结
   上述5个版本,filter1和filter5的性能相差约12倍(已经超过一个数量级)。除了filter3相对于filter2是通过消除函数重复 调用来提升性能,其它的几个版本都是通过节省内存分配,降低了时间开销。可见内存分配对于性能的影响有多大啊!如果你是看了上一个帖子 才写出filter4或者filter5,那说明你已经领会了个中奥妙,我那个帖子也就没白写了。

  ★一点补充说明,关于时间和空间的平衡
  另外,需要补充说明一下。版本4和版本5使用了空间换时间的手法来提升性能。假如被过滤的字符串很大 ,并且数字字符的比例很低 ,这种方式就不太合算了。
   举个例子:被处理的字符串中,绝大部分都只含有不到10%的数字字符,只有少数字符串包含较多的数字字符。这时候该怎么办捏?对于filter4来说, 可以把new StringBuffer(nLen);修改为new StringBuffer(nLen/10);来节约空间开销。但是filter5就没法这么玩了。
  所以,具体该用版本4还是版本5,要看具体情况了。只有在你非常 看重时间开销,且数字字符比例很高(至少大于50%)的情况下,用filter5才合算。否则的话,建议用filter4。

  下一个帖子,打算介绍一下“关于垃圾回收(GC) ”的话题。


版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想 和本文原始地址:

http://program-think.blogspot.com/2009/03/java-performance-tuning-2-string.html

分享到:
评论

相关推荐

    Java开发实战1200例(第1卷).(清华出版.李钟尉.陈丹丹).part3

    书名:《Java开发实战1200例(第I卷)》(清华大学出版社.李钟尉,陈丹丹) PDF格式扫描版,全书分为24章,共817页。2011年1月出版。 全书压缩打包成4部分,这是第3部分 注:本系列图书的第I、II卷再版时均相应改名为...

    基于Java和Python的爬虫项目实战源码.zip

    基于Java和Python的爬虫项目实战源码.zip 自己动手写网络爬虫》,并基于Python3和Java实现 为什么采用宽度优先搜索策略? 深度优先遍历可能会在深度上过“深”而陷入“黑洞”; 重要的网页往往距离种子网页比较近,...

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

    4.4.3 字符串编码 115 4.5 查询java api文件 117 4.6 重点复习 119 4.7 课后练习 120 chapter5 对象封装 125 5.1 何谓封装 126 5.1.1 封装对象初始流程 126 5.1.2 封装对象操作流程 128 5.1.3 封装...

    Java_Web开发实战1200例第1卷.part3

    5.1 字符串处理 146 5.2 数据验证 167 5.3 日期时间处理 176 5.4 输出实用的HTML代码 182 5.5 窗口与对话框 186 5.6 对数据库操作的JavaBean 189 第6章 Servlet技术 211 6.1 Servlet基础 212 6.2 Servlet应用 223 第...

    Java_Web开发实战1200例第1卷.part2

    5.1 字符串处理 146 5.2 数据验证 167 5.3 日期时间处理 176 5.4 输出实用的HTML代码 182 5.5 窗口与对话框 186 5.6 对数据库操作的JavaBean 189 第6章 Servlet技术 211 6.1 Servlet基础 212 6.2 Servlet应用 223 第...

    JavaStudy:一角钱技术 All in One

    嗨你好,我是一角钱技术~ 大家可以去获取或者加我提意见(别忘记Star哟)。 原创文章每周最少两篇,...字符串算法 数据结构与算法—冒泡排序 数据结构与算法—树论 数据结构与算法—哈夫曼 数据结构与算法—字典树(T

    Java典型模块

    第21章 查找和替换项目(GUI+字符串处理) 21.1 查找和替换原理 21.1.1 项目结构框架分析 21.1.2 项目功能业务分析 21.2 查找和替换项目——利用AWT组件 21.2.1 设计项目的界面——查找和替换输入界面 21.2.2 各种...

    Java开发技术大全 电子版

    第5章数组与字符串200 5.1数组200 5.1.1一维数组的声明200 5.1.2一维数组的创建201 5.1.3一维数组的使用202 5.1.4二维数组的声明204 5.1.5二维数组的创建205 5.1.6二维数组的使用207 5.1.7for~each循环208 ...

    Grails权威指南

     2.2.3 groovy字符串  2.2.4 闭包(closures)  2.2.5 列表(list)和映射(map)  2.2.6 expando动态对象  2.2.7 范围(range)  2.3 groovy的高级特性  2.3.1 一切都是对象  2.3.2 ...

Global site tag (gtag.js) - Google Analytics