★一个简单的需求

  首先描述一下需求:
给定一个 String 对象,过滤掉除了数字(字符'0'到'9')以外的其它字符。要求时间开销尽可能小。过滤函数的原型如下:

String filter(String str);

  针对上述需求,俺写了5个不同的过滤函数。为了叙述方便,函数名分别定为 filter1 到 filter5。其中 filter1 性能最差、filter5 性能最好。在看后续的内容之前,你先暗自思考一下,如果由你来实现该函数,大概会写成什么样?最好把你想好的函数写下来,便于跟俺给出的例子作对比。

★代码——循序渐进的5种实现方式

◇测试代码

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

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,使用的测试字符串是随机生成的,长度32个 char,只含字母和数字。由于 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 的代码。
  有些同学可能会问:为啥不用 StringBuilder?
  确实,在 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 是通过消除函数重复调用来提升性能,其它的几个版本都是通过节省内存分配,降低了时间开销。可见内存分配对于性能的影响有多大啊!

★一点补充说明,关于时间和空间的平衡

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

AJPFX浅谈Java 性能优化之字符串过滤实战的更多相关文章

  1. AJPFX浅谈Java 性能优化之垃圾回收(GC)

    ★JVM 的内存空间 在 Java 虚拟机规范中,提及了如下几种类型的内存空间: ◇栈内存(Stack):每个线程私有的.◇堆内存(Heap):所有线程公用的.◇方法区(Method Area):有点 ...

  2. AJPFX浅谈Java性能优化之finalize 函数

    ★finalize 函数的调用机制 俺经常啰嗦:“了解本质机制的重要性”.所以今天也得先谈谈 finalize 函数的调用机制.在聊之前,先声明一下:Java虚拟机规范,并没有硬性规定垃圾回收该不该搞 ...

  3. 浅谈java性能分析

    浅谈java性能分析,效能分析 在老师强烈的要求下做了效能分析,对上次写过的词频统计的程序进行分析以及改进. 对于效能分析:我个人很浅显的认为就是程序的运行效率,代码的执行效率等等. java做性能测 ...

  4. 浅谈Oracle 性能优化

    基于大型Oracle数据库应用开发已有6个年头了,经历了从最初零数据演变到目前上亿级的数据存储.在这个经历中,遇到各种各样的性能问题及各种性能优化. 在这里主要给大家分享一下数据库性能优化的一些方法和 ...

  5. 开发高性能的MongoDB应用—浅谈MongoDB性能优化(转)

    出处:http://www.cnblogs.com/mokafamily/p/4102829.html 性能与用户量 “如何能让软件拥有更高的性能?”,我想这是一个大部分开发者都思考过的问题.性能往往 ...

  6. 开发高性能的MongoDB应用—浅谈MongoDB性能优化

    关联文章索引: 大数据时代的数据存储,非关系型数据库MongoDB 性能与用户量 “如何能让软件拥有更高的性能?”,我想这是一个大部分开发者都思考过的问题.性能往往决定了一个软件的质量,如果你开发的是 ...

  7. 浅谈前端性能优化(二)——对HTTP传输进行压缩

    1.前端性能优化的一点: 对js.css.图片等进行压缩,尽可能减小文件的大小,减少文件下载的时间,从而减少网页响应的时间. 2.前端性能优化的另一点: 对HTTP传输进行压缩,即在js,css.图片 ...

  8. 浅谈前端性能优化(PC版)

    前端的性能优化是一个很宽泛的概念,最终目的都是为了提升用户体验,改善页面性能.面试的时候经常会遇到问谈谈性能优化的手段,这个我分几大部分来概述,具体细节需要自己再针对性的去搜索,只是提供一个索引(太多 ...

  9. AJPFX谈Java 性能优化之基本类型 vs 引用类型

    ★名词定义 先明确一下什么是“基本类型”,什么是“引用类型”. 简单地说,所谓基本类型就是 Java 语言中如下的8种内置类型: booleancharbyteshortintlongfloatdou ...

随机推荐

  1. 浏览器和服务器 对post get请求 url长度限制

    1. URL长度限制 2. Post数据的长度限制 3. Cookie的长度限制 1. GET  URL长度限制 在Http1.1协议中并没有提出针对URL的长度进行限制,RFC协议里面是这样描述的, ...

  2. mysql优化----explain的列分析

    sql语句优化: : sql语句的时间花在哪儿? 答: 等待时间 , 执行时间. 等待时间:看是不是被锁住了,那就不是语句层面了是服务端层面了,看连接数内存. 执行时间:到底取出多少行,一次性取出1万 ...

  3. docker pure-ftp 搭建ftp服务器

    参考:https://hub.docker.com/r/stilliard/pure-ftpd/ docker-compose.yml: ftp: image: stilliard/pure-ftpd ...

  4. Android font

    ╔════╦════════════════════════════╦═════════════════════════════╗ ║ ║ FONT FAMILY ║ TTF FILE ║ ╠════ ...

  5. SideBar---fixed定位

      <style> /*外层fixed*/ body{ width:2000px; height:2000px; background:#000; } .wrap { position: ...

  6. maven实战(2)-- m2eclipse插件配置

    使用eclipse进行maven项目的开发,需要安装m2eclipse插件.下面介绍该插件的配置,插件的安装在此不作介绍. 配置m2eclipse 先决条件:已安装maven,m2eclipse 以上 ...

  7. codeforces 667C C. Reberland Linguistics(dp)

    题目链接: C. Reberland Linguistics time limit per test 1 second memory limit per test 256 megabytes inpu ...

  8. RESTful 架构与 RESTful 服务

    风格 ⇒ 标准 ⇒ 协议 ⇒ 实现 Representational State Transfer,是一种软件架构风格,既然是风格,就非标准或协议,而是定义了一组设计原则和约束条件.具有如下特点: 适 ...

  9. CodeForces937B:Vile Grasshoppers(素数性质)

    The weather is fine today and hence it's high time to climb the nearby pine and enjoy the landscape. ...

  10. Spring Data JPA 和MyBatis比较

    现在Dao持久层的解决方案中,大部分是采用Spring Data JPA或MyBatis解决方案,并且传统企业多用前者,互联网企业多用后者. Spring Data JPA 是Spring Data ...