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

java开发中常见的简单效率问题

阅读更多

 

本文要说的内容都是小问题,为编写代码图省劲而出的小问题。这样的代码在代码量小、执行频率低、并发量低、占用内存低、占用cpu时间低的情况下没什么,一旦这样的代码在应用中达到一定数量,或在执行频率、并发量、内存占用量、cpu时间消耗达到一个临界值的时候,应用的运行效率就会显著降低,甚至会使响应时间和吞吐量达到难以忍受的程度。

本文只说其中最常见的两个,一个是使用“+”拼字符串,一个是使用“+”拼sqlhql

编写代码的时候,用“+”拼字符串确实很方便。要拼的字符串内容包含在变量里,在用“+”拼字符串的代码行加个断点调试一下就会知道,“+”实际上就是创建了一个StringBuilder对象然后调用append方法最后再调用toString方法,这就是一个“+”做的全部工作。如果n个“+”参数计算,整个拼接过程创建了n个字符串对象,和nStringBuilder对象,调用了ntoString方法,这就是用“+”拼字符串效率低的原因所在,即耗堆内存又消耗时间。

我们不妨做一个测试。

//测试+

public static void test1(){

    String str=””;

    long s=System.currentTimeMillis();

    for(int i=0;i<10000;i++){

        str+=’a’;

    }

        System.out.println(System.currentTimeMillis()-s);

}

 

//测试StringBuilder

public static void test2(){

    StringBuilder builder=new StringBuilder();

    long s=System.currentTimeMillis();

    for(int i=0;i<10000;i++){

        builder.append('a');

    }

    String str=builder.toString();

    System.out.println(System.currentTimeMillis()-s);

}

不妨运行下上面两段代码,第一段代码消耗的内存要比第二段大的多,默认jvm堆内存可能会在运行第一段代码时抛出OOM,而且运行第一段代码耗时是第二段的1000多倍(视机器硬件不同,会有差异,不过耗时绝对明显)。

另一方面,如果用“+”将连接多个字面量连接成新字符串,eclipse jdt编译器会在编译时就完成这个连接的计算工作。(javac是不是也这样,我没有测试过。对于编译器的问题就不要有疑问了,eclipse确实按照java标准实现了一个编译器,据说比javac要好的多,另据说javacbug。)

最后如果使用连+拼字符串,而不是在一个方法内将拼字符串的操作分散在多处,这个时候编译器会把连+编译成一个StringBuilder对象和若干次append调用。

综上所述,如果一个字符串太长,不利于阅读代码,可以用“+”连接配合缩进增强代码可读性,否则在要连接的字符串超过一+时最好使用StringBuilder代替。

另外,jdk还提供了所有方法都是线程安全的StringBuffer,由于它的方法都用synchronized声明,单线程性能比StringBuilder略低;而且带synchronized声明的方法实际上锁定的是方法所在的Class对象,因此随着使用StringBuffer的代码越来越多,线程越来越多,因synchronized同步导致的效率问题也会越来越凸显,这跟应用中StringBuffer对象的数量无关,只跟使用StringBuffer的线程数和代码量有关,因为锁是加在StringBuffer.class对象上的,只要线程里面使用的StringBuffer类都是同一个ClassLoader对象加载的就会如此,而绝大多数情况下,jdk本身的类一定是由根类加载器加载。

至于直接把查询参数拼到sqlhql的代码风格也会造成与“+”连接字符串类似的结果。hibernate会首先分析hql,构造出一个hql语法树,这个语法树由一系列对象构成,hibernate session缓存、二级缓存、各种关联映射关系都依赖于这个hql语法树,并且将利用hql语法树翻译出sql;数据库也会分析sql,构造出一个sql语法树,并最终完成数据库表的增删改查。一条hql对应一个语法树,分析完成后,即使hql结构相同,仅仅参数值不同,也会被hibernate判断为不同的hql;对于完成分析的hqlhibernate会将分析结果缓存起来,以备以后使用。sql对于数据库亦然。当然由于内存有限,如果sql/hql,不论hibernate还是数据库都不会缓存应用运行期出现的所有语法树,最不常用的语法树会被清除。所以根据以上分析,把查询参数拼到sql/hql里面同样是运行期即耗内存又耗时间的做法。使用占位符的方式构造sql/hql会大大减少内存消耗和降低运行时间。

将参数作为sql/hql的一部分还会引起一个严重问题,就是sql注入,如果参数里面有sql/hql片断,这个片断就会被拼到要执行的sql/hql里面,这可能会带来灾难性的后果,而造成这一切的原因仅仅是因为没有使用占位符sql/hql。对此不必怀疑,使用占位符sql/hql就能杜绝sql注入。原因就在于传入参数前sql/hql已经被解析完成,语法树已经构建完毕,任何传入的参数都不会再被当作sql/hql的一部分处理。

另外,如果使用hibernate作为orm框架,最好增删改查都使用hibernate完成,不要绕过hibernate直接操作jdbc。保持一致的代码风格是一方面,另一方面也是更重要的,全部使用hibernate操作增删改查,任何数据变更hibernate都能管理的到,会进一步通过hibernate提升针对数据库操作的运行效率。而且hql除了支持查询,也早就支持updatedelete了。现在hibernate除了批量更新、删除尚不支持外,可以很方便的通过hql完成增删改查工作。

最近听到很多人讨论不推荐在项目中使用太多框架,都想除spring以外杜绝第三方框架,其实个人认为大可不必,应用效率的瓶颈不在框架,而在于使用它们的方式、代码风格和架构设计。优秀的架构设计配合合理应用的框架以及高效的代码完全能够实现高效的应用。应用效率低下并不在于使用什么框架,如果由于框架本身引发了低下的效率,这个框架早就不会存在了,而它们能够长久的生存下来本身就足够说明问题。同时合理选择框架也会大大提高开发效率。因此如果遇到了性能或效率问题还是应首先从应用自身的架构、代码规范与代码风格上着手解决。将问题归绺于框架,是南辕北辙,甚至逃避责任。

最后,关于框架再多说一些。个人并不推崇惟框架是从,个人认为在产品早期为了追求尽快面世,可以采用框架开发;在小规模应用或技术积累尚不够充分时应采用框架开发;而在应用规模达到一定程度的时候,这时应该已经有了足够的技术积累去实现有自主知识产权的框架。纵观国内外各大互联网公司所走过的技术路线,莫不如是。关于它们的技术发展史网上有很多介绍的文章,甚至一些来自这些公司的架构师也亲自发文分享自己的心路历程与公司的技术路线。有兴趣的朋友不妨看一下。

 

0
0
分享到:
评论
2 楼 runfriends 2012-11-24  
laogao3232 写道
那段示例代码是开玩笑的吗?

不好意思。这段测试逻辑是2009年测试的,原来的代码早就丢了。这一段是我在编辑器里写的。有语法错误,实在抱歉。
我把错误改了。
1 楼 laogao3232 2012-11-24  
那段示例代码是开玩笑的吗?

相关推荐

Global site tag (gtag.js) - Google Analytics