开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 2878|回复: 30
收起左侧

[android教程] 【转】Java代码优化(长期更新)

[复制链接]
结帖率:100% (2/2)
发表于 2016-12-10 17:04:12 | 显示全部楼层 |阅读模式   山东省青岛市

前言
2016年3月修改,结合自己的工作和平时学习的体验重新谈一下为什么要进行代码优化。在修改之前,我的说法是这样的:
就像鲸鱼吃虾米一样,也许吃一个两个虾米对于鲸鱼来说作用不大,但是吃的虾米多了,鲸鱼自然饱了。代码优化一样,也许一个两个的优化,对于提升代码的运行效率意义不大,但是只要处处都能注意代码优化,总体来说对于提升代码的运行效率就很有用了。
这个观点,在现在看来,是要进行代码优化的一个原因,但不全对。在机械工艺发展的今天,服务器动辄8核、16核,64位CPU,代码执行效率非常高,StringBuilder替换StringBuffer、ArrayList替换Vector,对于代码运行效率的提升是微乎其微的,即使是项目中的每个点都注意到了,代码运行也看不出什么明显的变化。
我认为,代码优化的最重要的作用应该是:避免未知的错误。在代码上线运行的过程中,往往会出现很多我们意想不到的错误,因为线上环境和开发环境是非常不同的,错误定位到最后往往是一个非常小的原因。然而为了解决这个错误,我们需要先自验证、再打包出待替换的class文件、暂停业务并重启,对于一个成熟的项目而言,最后一条其实影响是非常大的,这意味着这段时间用户无法访问应用。因此,在写代码的时候,从源头开始注意各种细节,权衡并使用最优的选择,将会很大程度上避免出现未知的错误,从长远看也极大的降低了工作量。
代码优化的目标是:
1、减小代码的体积
2、提高代码运行的效率
本文的内容有些来自网络,有些来自平时工作和学习,当然这不重要,重要的是这些代码优化的细节是否真真正正地有用。那本文会保持长期更新,只要有遇到值得分享的代码优化细节,就会不定时地更新此文。


点评

你这直接外链太过了吧?   北京市北京市  发表于 2016-12-18 21:35
请正序浏览   山东省青岛市  发表于 2016-12-10 17:50
结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:44:57 | 显示全部楼层   山东省青岛市
回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:44:36 | 显示全部楼层   山东省青岛市
后记

优秀的代码来自每一点点小小的优化,关注每一个细节,不仅仅能提升程序运行效率,同样可以规避许多未知的问题。

==================================================================================

我不能保证写的每个地方都是对的,但是至少能保证不复制、不黏贴,保证每一句话、每一行代码都经过了认真的推敲、仔细的斟酌。每一篇文章的背后,希望都能看到自己对于技术、对于生活的态度。

我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正地改变世界。面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩。

其实我想说的是,我只是一个程序员,这就是我现在纯粹人生的全部。

回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:43:59 | 显示全部楼层   山东省青岛市
37、对于ThreadLocal使用前或者使用后一定要先remove

当前基本所有的项目都使用了线程池技术,这非常好,可以动态配置线程数、可以重用线程。

然而,如果你在项目中使用到了ThreadLocal,一定要记得使用前或者使用后remove一下。这是因为上面提到了线程池技术做的是一个线程重用,这意味着代码运行过程中,一条线程使用完毕,并不会被销毁而是等待下一次的使用。我们看一下Thread类中,持有ThreadLocal.ThreadLocalMap的引用:
  1. /* ThreadLocal values pertaining to this thread. This map is maintained
  2. * by the ThreadLocal class. */
  3. ThreadLocal.ThreadLocalMap threadLocals = null;
复制代码
线程不销毁意味着上条线程set的ThreadLocal.ThreadLocalMap中的数据依然存在,那么在下一条线程重用这个Thread的时候,很可能get到的是上条线程set的数据而不是自己想要的内容。

这个问题非常隐晦,一旦出现这个原因导致的错误,没有相关经验或者没有扎实的基础非常难发现这个问题,因此在写代码的时候就要注意这一点,这将给你后续减少很多的工作量。
回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:34:31 | 显示全部楼层   山东省青岛市
36、对资源的close()建议分开操作

意思是,比如我有这么一段代码:

  1. try{
  2.     XXX.close();
  3.     YYY.close();
  4. }catch (Exception e){
  5.     ...
  6. }
复制代码

建议修改为:


  1. try{
  2.     XXX.close();
  3. }catch (Exception e){
  4.     ...
  5. }
  6. try{
  7.     YYY.close();
  8. }catch (Exception e){
  9.     ...
  10. }
复制代码


虽然有些麻烦,却能避免资源泄露。我们想,如果没有修改过的代码,万一XXX.close()抛异常了,那么就进入了catch块中了,YYY.close()不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,是可能引起资源句柄泄露的。而改为下面的写法之后,就保证了无论如何XXX和YYY都会被close掉
回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:32:46 | 显示全部楼层   山东省青岛市
35、使用最有效率的方式去遍历Map

遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是:

  1. public static void main(String[] args)
  2. {
  3.     HashMap<String, String> hm = new HashMap<String, String>();
  4.     hm.put("111", "222");
  5.         
  6.     Set<Map.Entry<String, String>> entrySet = hm.entrySet();
  7.     Iterator<Map.Entry<String, String>> iter = entrySet.iterator();
  8.     while (iter.hasNext())
  9.     {
  10.         Map.Entry<String, String> entry = iter.next();
  11.         System.out.println(entry.getKey() + "\t" + entry.getValue());
  12.     }
  13. }
复制代码


如果你只是想遍历一下这个Map的key值,那用"Set<String> keySet = hm.keySet();"会比较合适一些
回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:31:56 | 显示全部楼层   山东省青岛市
34、把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+""最慢

把一个基本数据类型转为一般有三种方式,我有一个Integer型数据i,可以使用i.toString()、String.valueOf(i)、i+""三种方式,三种方式的效率如何,看一个测试:


  1. public static void main(String[] args){
  2.     int loopTime = 50000;
  3.     Integer i = 0;
  4.     long startTime = System.currentTimeMillis();
  5.     for (int j = 0; j < loopTime; j++)    {
  6.         String str = String.valueOf(i);
  7.     }   
  8.     System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms");
  9.     startTime = System.currentTimeMillis();
  10.     for (int j = 0; j < loopTime; j++)    {
  11.         String str = i.toString();
  12.     }   
  13.     System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms");
  14.     startTime = System.currentTimeMillis();
  15.     for (int j = 0; j < loopTime; j++)    {
  16.         String str = i + "";
  17.     }   
  18.     System.out.println("i + "":" + (System.currentTimeMillis() - startTime) + "ms");
  19. }
复制代码


运行结果为:

  1. String.valueOf():11ms
  2. Integer.toString():5ms
  3. i + "":25ms
复制代码

所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单:

1、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断

2、Integer.toString()方法就不说了,直接调用了

3、i + ""底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串

三者对比下来,明显是2最快、1次之、3最慢

点评

还可以, 说到点子上了. 我看过JDK1.6 API部分的源码 的确是这样实现的   北京市北京市  发表于 2016-12-10 18:14
回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:30:26 | 显示全部楼层   山东省青岛市
33、公用的集合类中不使用的数据一定要及时remove掉

如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。
回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:30:04 | 显示全部楼层   山东省青岛市
32、不要对超出范围的基本数据类型做向下强制转型

这绝不会得到想要的结果:

  1. public static void main(String[] args)
  2. {
  3.     long l = 12345678901234L;
  4.     int i = (int)l;
  5.     System.out.println(i);
  6. }
复制代码

我们可能期望得到其中的某几位,但是结果却是:
1942892530

解释一下。Java中long是8个字节64位的,所以12345678901234在计算机中的表示应该是:

0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010

一个int型数据是4个字节32位的,从低位取出上面这串二进制数据的前32位是:
0111 0011 1100 1110 0010 1111 1111 0010

这串二进制表示为十进制1942892530,所以就是我们上面的控制台上输出的内容。
从这个例子上还能顺便得到两个结论:

1、整型默认的数据类型是int,long l = 12345678901234L,这个数字已经超出了int的范围了,所以最后有一个L,表示这是一个long型数。顺便,浮点型的默认类型是double,所以定义float的时候要写成""float f = 3.5f"

2、接下来再写一句"int ii = l + i;"会报错,因为long + int是一个long,不能赋值给int
回复 支持 反对

使用道具 举报

结帖率:100% (2/2)
 楼主| 发表于 2016-12-10 17:28:48 | 显示全部楼层   山东省青岛市
30、不要对数组使用toString()方法

看一下对数组使用toString()打印出来的是什么:
  1. public static void main(String[] args)
  2. {
  3.     int[] is = new int[]{1, 2, 3};
  4.     System.out.println(is.toString());
  5. }
复制代码

结果是:
  1. [I@18a992f
复制代码

本意是想打印出数组内容,却有可能因为数组引用is为空而导致空指针异常。不过虽然对数组toString()没有意义,但是对集合toString()是可以打印出集合里面的内容的,因为集合的父类AbstractCollections<E>重写了Object的toString()方法。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表