程序员人生 网站导航

Android最佳性能实践(三)――高性能编码优化

栏目:综合技术时间:2015-03-26 09:37:58

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/42318689

在前两篇文章当中,我们主要学习了Android内存方面的相干知识,包括如何公道地使用内存,和当产生内存泄漏时如何定位出问题的缘由。那末关于内存的知识就讨论到这里,今天开始我们将学习1些性能编码优化的技能。

这里先事前提示大家1句,本篇文章中讨论的编码优化技能都是属于1些“微优化”,也就是说即便我们都依照本篇文章的技能来优化代码,在性能方面也是看不出有甚么显著的提升的。使用适合的算法与数据结构将永久是你优化程序性能的最主要手段,但本篇文章中不会讨论这1块的内容。因此,这里我们行将学习的其实不是甚么灵丹妙药,而是大家应当把这些技能当作1种好的编码规范,我们在平时写代码时就能够潜移默化地使用这些编码规范,不但能够在微观层面提升程序1定的性能,也能够让我们的代码变得更加专业,下面就让我们来1起学习1下这些技能。

避免创建没必要要的对象

创建对象历来都不应当是1件随便的事情,由于创建1个对象就意味着垃圾回收器需要回收1个对象,而这两步操作都是需要消耗时间的。虽然说创建1个对象的代价确切非常小,并且Android 2.3版本当中又增加了并发垃圾回收器机制(详见 Android最好性能实践(2)――分析内存的使用情况),这让GC操作时的停顿时间也变得难以发觉,但是这些理由都不足以让我们可以肆意地创建对象,需要创建的对象我们自然要创建,但是没必要要的对象我们就应当尽可能避免创建。

下面来看1些我们可以免创建对象的场景:

  • 如果我们有1个需要拼接的字符串,那末可以优先斟酌使用StringBuffer或StringBuilder来进行拼接,而不是加号连接符,由于使用加号连接符会创建过剩的对象,拼接的字符串越长,加号连接符的性能越低。
  • 在没有特殊缘由的情况下,尽可能使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是1样。
  • 当1个方法的返回值是String的时候,通常可以去判断1下这个String的作用是甚么,如果我们明确地知道调用方会将这个返回的String再进行拼接操作的话,可以斟酌返回1个StringBuffer对象来代替,由于这样可以将1个对象的援用进行返回,而返回String的话就是创建了1个短生命周期的临时对象。
  • 正如前面所说,基本数据类型要优于对象数据类型,类似地,基本数据类型的数组也要优于对象数据类型的数组。另外,两个平行的数组要比1个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的两个数组,使用起来要比Custom(Foo,Bar)[]这样的1个数组高效很多。

固然上面所说的只是1些代表性的例子,我们所要遵照的1个基本原则就是尽量地少创建临时对象,越少的对象意味着越少的GC操作,同时也就意味着越好的程序性能和用户体验。

静态优于抽象

如果你其实不需要访问1个对象中的某些字段,只是想调用它的某个方法来去完成1项通用的功能,那末可以将这个方法设置成静态方法,这会让调用的速度提升15%⑵0%,同时也不用为了调用这个方法而去专门创建对象了,这样还满足了上面的1条原则。另外这也是1种好的编程习惯,由于我们可以放心肠调用静态方法,而不用担心调用这个方法后是不是会改变对象的状态(静态方法内没法访问非静态字段)。

对常量使用static final修饰符

我们先来看1下在1个类的最顶部定义以下代码:

static int intVal = 42; static String strVal = "Hello, world!";
编译器会为上述代码生成1个初始化方法,称为<clinit>方法,该方法会在定义类第1次被使用的时候调用。然后这个方法会将42的值赋值到intVal当中,并从字符串常量表中提取1个援用赋值到strVal上。当赋值完成后,我们就能够通过字段搜索的方式来去访问具体的值了。

但是我们还可以通过final关键字来对上述代码进行优化:

static final int intVal = 42; static final String strVal = "Hello, world!";
经过这样修改以后,定义类就不再需要1个<clinit>方法了,由于所有的常量都会在dex文件的初始化器当中进行初始化。当我们调用intVal时可以直接指向42的值,而调用strVal时会用1种相对轻量级的字符串常量方式,而不是字段搜索的方式。

另外需要大家注意的是,这类优化方式只对基本数据类型和String类型的常量有效,对其它数据类型的常量是无效的。不过,对任何常量都是用static final的关键字来进行声明依然是1种非常好的习惯。

使用增强型for循环语法

增强型for循环(也被称为for-each循环)可以用于去遍历实现Iterable接口的集合和数组,这是jdk 1.5中新增的1种循环模式。固然除这类新增的循环模式以外,我们依然还可使用原本的普通循环模式,只不过它们之间是有效力区分的,我们来看下面1段代码:

static class Counter { int mCount; } Counter[] mArray = ... public void zero() { int sum = 0; for (int i = 0; i < mArray.length; ++i) { sum += mArray[i].mCount; } } public void one() { int sum = 0; Counter[] localArray = mArray; int len = localArray.length; for (int i = 0; i < len; ++i) { sum += localArray[i].mCount; } } public void two() { int sum = 0; for (Counter a : mArray) { sum += a.mCount; } }
可以看到,上述代码当中我们使用了3种不同的循环方式来对mArray中的所有元素进行求和。其中zero()方法是最慢的1种,由于它是把mArray.length写在循环当中的,也就是说每循环1次都需要重新计算1次mArray的长度。而one()方法则相对快很多,由于它使用了1个局部变量len来记录数组的长度,这样就省去了每次循环时字段搜索的时间。two()方法在没有JIT(Just In Time Compiler)的装备上是运行最快的,而在有JIT的装备上运行效力和one()方法不相上下,唯1需要注意的是这类写法需要JDK 1.5以后才支持。

但是这里要跟大家提1个特殊情况,对ArrayList这类集合,自己手写的循环要比增强型for循环更快,而其他的集合就没有这类情况。因此,对我们来讲,默许情况下可以都使用增强型for循环,而遍历ArrayList时就还是使用传统的循环方式吧。

多使用系统封装好的API

Java语言当中其实给我们提供了非常丰富的API接口,我们在编写程序时如果可使用系统提供的API就应当尽可能使用,系统提供的API完成不了我们需要的功能时才应当自己去写,由于使用系统的API在很多时候比我们自己写的代码要快很多,它们的很多功能都是通过底层的汇编模式履行的。

比如说String类当中提供的好多API都是具有极高的效力的,像indexOf()方法和1些其它相干的API,虽然说我们通过自己编写算法也能够完成一样的功能,但是效力方面会和这些方法差的比较远。这里举个例子,如果我们要实现1个数组拷贝的功能,使用循环的方式来对数组中的每个元素逐一进行赋值固然是可行的,但是如果我们直接使用系统中提供的System.arraycopy()方法将会让履行效力快9倍以上。

避免在内部调用Getters/Setters方法

我们平时写代码时都被告知,1定要使用面向对象的思惟去写代码,而面向对象的3大特性我们都知道,封装、多态和继承。其中封装的基本思想就是不要把类内部的字段暴漏给外部,而是提供特定的方法来允许外部操作相应类的内部字段,从而在Java语言当中就出现了Getters/Setters这类封装技能。

但是在Android上这个技能就不再是那末的受推重了,由于字段搜索要比方法调用效力高很多,我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。不过我们肯定不能仅仅由于效力的缘由就将封装这个技能给抛弃了,编写代码还是要依照面向对象思惟的,但是我们可以在能优化的地方进行优化,比如说避免在内部调用getters/setters方法。

那甚么叫做在内部调用getters/setters方法呢?这里我举1个非常简单的例子:

public class Calculate { private int one = 1; private int two = 2; public int getOne() { return one; } public int getTwo() { return two; } public int getSum() { return getOne() + getTwo(); } }
可以看到,上面是1个Calculate类,这个类的功能非常简单,先将one和two这两个字段进行了封装,然后提供了getOne()方法获得one字段的值,提供了getTwo()方法获得two字段的值,还提供了1个getSum()方法用于获得总和的值。

这里我们注意到,getSum()方法当中的算法就是将one和two的值相加进行返回,但是它获得one和two的值的方式也是通过getters方法进行获得的,其实这是1种完全没有必要的方式,由于getSum()方法本身就是Calculate类内部的方法,它是可以直接访问到Calculate类中的封装字段的,因此这类写法在Android上是不推重的,我们可以进行以下修改:

public class Calculate { private int one = 1; private int two = 2; ...... public int getSum() { return one + two; } }
改成这类写法以后,我们就避免了在内部调用getters/setters方法,而对外部而言Calculate类依然是具有很好的封装性的。

固然,本篇文章中推荐的这些技能呢也其实不全面,只是从Android官方文档抽取了几个感觉比较实用的分享给大家,更多技能大家也能够到Android官网上去浏览。另外在高性能编码方面《Efficient Java》这本书当中也提供了非常多的技能,有兴趣的朋友也能够去浏览1下这本书。那末本篇文章就到这里,下篇文章当中将会介绍Android布局优化的技能,敬请期待。

第1时间取得博客更新提示,和更多技术信息分享,欢迎关注我的微信公众号,扫1扫下方2维码或搜索微信号guolin_blog,便可关注。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐