`
elvisli
  • 浏览: 19263 次
  • 性别: 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

分享到:
评论

相关推荐

    java8实战:使用流收集数据之toList、joining、groupBy(多字段分组)

    接着,`joining`方法用于将流中的字符串连接成一个单一的字符串。它接受可选的分隔符、前缀和后缀参数,允许我们自定义连接方式。例如,如果我们有一个包含多个城市名的流,我们可以这样做: ```java String cities...

    Remove-item-from-list:一个简单的Java程序,用于从字符串列表中删除选定的字符串

    这个程序提供了一个简单的解决方案,帮助开发者从字符串列表中删除指定的元素。下面我们将深入探讨这个话题,包括Java中的列表操作、删除元素的方法以及如何实现这个程序。 1. **Java列表(List)接口**: Java...

    java 开发实战经典 习题答案 java

    Java开发实战经典习题涉及了Java编程的基础概念和常见算法,涵盖了循环结构、条件判断、数组操作、字符串处理以及面向对象编程等多个知识点。下面逐一解析这些习题。 1. **水仙花数**:水仙花数是三位数,其每位...

    java FASTJSON 解析复杂JSON实例源码

    - `JSONObject.parseObject(String jsonString, Class&lt;T&gt; clazz)`:将JSON字符串解析为Java对象,其中`T`是你想要的类类型。 - `JSONArray.parseArray(String jsonString, Class&lt;T&gt; clazz)`:将JSON字符串解析为...

    java+web项目实战大全源码搜索引擎Java源码

    1. **Java基础**:包括类、对象、封装、继承、多态等面向对象编程概念,以及异常处理、数据类型、控制结构、数组、字符串等基础知识。 2. **Java集合框架**:如ArrayList、LinkedList、HashMap、HashSet等,这些是...

    Java_Web开发实战经典 李新华 课后习题答案

    《Java_Web开发实战经典》是由李新华老师编著的一本深入浅出的Java Web...对于使用不同数据库的情况,只需按照数据库的API进行适当的调整,如连接字符串、SQL语法等,即可适应不同的环境。祝你在学习过程中收获满满!

    Java-Study:Java知识点总结

    - 程序优化:避免过度的对象创建,合理使用`StringBuilder`或`StringBuffer`替换字符串连接,减少`if`语句的使用,提高代码效率。 - 数据结构与算法:熟悉各种数据结构(如ArrayList、LinkedList、HashMap等)的...

    java echars入门实战代码

    在本项目"java echarts入门实战代码"中,我们将探讨如何使用Java后端与ECharts前端库协同工作,实现动态数据显示。ECharts是一款基于JavaScript的数据可视化库,它提供了丰富的图表类型,如柱状图、折线图、饼图等,...

    Lucene4.X实战类baidu搜索的大型文档海量搜索系统-19.Lucene过滤 共4页.pptx

    同样,对于字符串类型的字段,我们可以使用`newStringRange`方法创建相应的范围过滤器: ```java FieldCacheRangeFilter&lt;String&gt; stringFilter = FieldCacheRangeFilter.newStringRange( "field", lowerVal, ...

    Java-Advanced:这是课程Java-Advanced(线性数据结构,集合和映射,InputOutput,文件和目录,字符串处理,函数编程,内置查询方法-Stream API)的练习库。

    5. **字符串处理**:Java中的String类是不可变的,因此字符串操作很常见且需要优化。课程会涵盖String的构造、比较、拼接、截取和模式匹配等方法,同时也会介绍StringBuilder和StringBuffer类用于大量操作的情况。 ...

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

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

    JAVA中防止SQL注入攻击类的源代码

    - **字符串过滤**:`stringFilter`方法用于过滤字符串中的特殊字符,通过遍历`FilterChars`数组进行替换。 - **JavaScript字符过滤**:`stringFilterScriptChar`方法专门用于过滤JavaScript中的特殊字符,确保字符串...

    Java实训教程 Java软件开发实战 Java开发框架介绍 SpringMVC_5_常用注解 共22页.pptx

    - **重定向和转发**:使用特定字符串前缀(如`redirect:`或`forward:`)实现重定向或转发操作。 - **JSON数据响应**:通过`@ResponseBody`注解配合Jackson等库直接返回JSON格式的数据。 #### 六、SpringMVC_5_常用...

    使用Java正则表达式分析处理日志

    正则表达式(Regular Expression)是一种模式匹配语言,用于在字符串中查找符合特定规则的模式。Java中的正则表达式主要通过`java.util.regex`包中的类来实现,如`Pattern`和`Matcher`。 在日志处理中,我们通常...

    EasyCommons JAVA 通用开发组件项目组

    2. **StringUtils**: 对字符串进行各种处理,如格式化、拼接、分割、校验等,避免了对Java内置String类的频繁调用,提高了代码可读性。 3. **DateUtils**: 处理日期和时间,提供格式化、比较、计算等功能,简化日期...

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

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

    Curso-Java-Loiane:Cusro Java loiane Groner

    5. **字符串处理**:String类的常用方法,以及StringBuilder和StringBuffer的使用。 6. **多线程**:线程的创建与管理,同步机制,死锁和线程池的使用。 7. **I/O流**:理解流的概念,学习文件操作、字符流和字节...

    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 第...

Global site tag (gtag.js) - Google Analytics