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

这里先事先提醒你们一句,本篇文章中讨论的编码优化技巧都是属于一些“微优化”,也就是说即便咱们都按照本篇文章的技巧来优化代码,在性能方面也是看不出有什么显著的提高的。使用合适的算法与数据结构将永远是你优化程序性能的最主要手段,但本篇文章中不会讨论这一块的内容。所以,这里咱们即将学习的并非什么灵丹妙药,而是你们应该把这些技巧看成一种好的编码规范,咱们在平时写代码时就能够潜移默化地使用这些编码规范,不只可以在微观层面提高程序必定的性能,也可让咱们的代码变得更加专业,下面就让咱们来一块儿学习一下这些技巧。java

 

避免建立没必要要的对象

 

建立对象历来都不该该是一件随意的事情,由于建立一个对象就意味着垃圾回收器须要回收一个对象,而这两步操做都是须要消耗时间的。虽然说建立一个对象的代价确实很是小,而且Android 2.3版本当中又增长了并发垃圾回收器机制,这让GC操做时的停顿时间也变得难以察觉,可是这些理由都不足以让咱们能够肆意地建立对象,须要建立的对象咱们天然要建立,可是没必要要的对象咱们就应该尽可能避免建立。算法

 

下面来看一些咱们能够避免建立对象的场景:编程

  • 若是咱们有一个须要拼接的字符串,那么能够优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号链接符,由于使用加号链接符会建立多余的对象,拼接的字符串越长,加号链接符的性能越低。
  • 在没有特殊缘由的状况下,尽可能使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是同样。
  • 当一个方法的返回值是String的时候,一般能够去判断一下这个String的做用是什么,若是咱们明确地知道调用方会将这个返回的String再进行拼接操做的话,能够考虑返回一个StringBuffer对象来代替,由于这样能够将一个对象的引用进行返回,而返回String的话就是建立了一个短生命周期的临时对象。
  • 正如前面所说,基本数据类型要优于对象数据类型,相似地,基本数据类型的数组也要优于对象数据类型的数组。另外,两个平行的数组要比一个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的两个数组,使用起来要比Custom(Foo,Bar)[]这样的一个数组高效得多。

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

 

静态优于抽象

 

若是你并不须要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么能够将这个方法设置成静态方法,这会让调用的速度提高15%-20%,同时也不用为了调用这个方法而去专门建立对象了,这样还知足了上面的一条原则。另外这也是一种好的编程习惯,由于咱们能够放心地调用静态方法,而不用担忧调用这个方法后是否会改变对象的状态(静态方法内没法访问非静态字段)。数据结构

 

对常量使用static final修饰符

 

咱们先来看一下在一个类的最顶部定义以下代码:并发

  1. static int intVal = 42;  
  2. static String strVal = "Hello, world!";  

编译器会为上述代码生成一个初始化方法,称为<clinit>方法,该方法会在定义类第一次被使用的时候调用。而后这个方法会将42的值赋值到intVal当中,并从字符串常量表中提取一个引用赋值到strVal上。当赋值完成后,咱们就能够经过字段搜寻的方式来去访问具体的值了。性能

 

可是咱们还能够经过final关键字来对上述代码进行优化:学习

  1. static final int intVal = 42;  
  2. static final String strVal = "Hello, world!";  

通过这样修改以后,定义类就再也不须要一个<clinit>方法了,由于全部的常量都会在dex文件的初始化器当中进行初始化。当咱们调用intVal时能够直接指向42的值,而调用strVal时会用一种相对轻量级的字符串常量方式,而不是字段搜寻的方式。优化

 

另外须要你们注意的是,这种优化方式只对基本数据类型以及String类型的常量有效,对于其它数据类型的常量是无效的。不过,对于任何常量都是用static final的关键字来进行声明仍然是一种很是好的习惯。ui

 

使用加强型for循环语法

 

加强型for循环(也被称为for-each循环)能够用于去遍历实现Iterable接口的集合以及数组,这是jdk 1.5中新增的一种循环模式。固然除了这种新增的循环模式以外,咱们仍然还可使用原有的普通循环模式,只不过它们之间是有效率区别的,咱们来看下面一段代码:

  1. static class Counter {  
  2.     int mCount;  
  3. }  
  4.   
  5. Counter[] mArray = ...  
  6.   
  7. public void zero() {  
  8.     int sum = 0;  
  9.     for (int i = 0; i < mArray.length; ++i) {  
  10.         sum += mArray[i].mCount;  
  11.     }  
  12. }  
  13.   
  14. public void one() {  
  15.     int sum = 0;  
  16.     Counter[] localArray = mArray;  
  17.     int len = localArray.length;  
  18.     for (int i = 0; i < len; ++i) {  
  19.         sum += localArray[i].mCount;  
  20.     }  
  21. }  
  22.   
  23. public void two() {  
  24.     int sum = 0;  
  25.     for (Counter a : mArray) {  
  26.         sum += a.mCount;  
  27.     }  
  28. }  

能够看到,上述代码当中咱们使用了三种不一样的循环方式来对mArray中的全部元素进行求和。其中zero()方法是最慢的一种,由于它是把mArray.length写在循环当中的,也就是说每循环一次都须要从新计算一次mArray的长度。而one()方法则相对快得多,由于它使用了一个局部变量len来记录数组的长度,这样就省去了每次循环时字段搜寻的时间。two()方法在没有JIT(Just In Time Compiler)的设备上是运行最快的,而在有JIT的设备上运行效率和one()方法不相上下,惟一须要注意的是这种写法须要JDK 1.5以后才支持。

 

可是这里要跟你们提一个特殊状况,对于ArrayList这种集合,本身手写的循环要比加强型for循环更快,而其余的集合就没有这种状况。所以,对于咱们来讲,默认状况下能够都使用加强型for循环,而遍历ArrayList时就仍是使用传统的循环方式吧。

 

多使用系统封装好的API

 

Java语言当中其实给咱们提供了很是丰富的API接口,咱们在编写程序时若是可使用系统提供的API就应该尽可能使用,系统提供的API完成不了咱们须要的功能时才应该本身去写,由于使用系统的API在不少时候比咱们本身写的代码要快得多,它们的不少功能都是经过底层的汇编模式执行的。

 

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

 

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

 

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

 

然而在Android上这个技巧就再也不是那么的受推崇了,由于字段搜寻要比方法调用效率高得多,咱们直接访问某个字段可能要比经过getters方法来去访问这个字段快3到7倍。不过咱们确定不能仅仅由于效率的缘由就将封装这个技巧给抛弃了,编写代码仍是要按照面向对象思惟的,可是咱们能够在能优化的地方进行优化,好比说避免在内部调用getters/setters方法。

 

那什么叫作在内部调用getters/setters方法呢?这里我举一个很是简单的例子:

  1. public class Calculate {  
  2.       
  3.     private int one = 1;  
  4.       
  5.     private int two = 2;  
  6.   
  7.     public int getOne() {  
  8.         return one;  
  9.     }  
  10.   
  11.     public int getTwo() {  
  12.         return two;  
  13.     }  
  14.       
  15.     public int getSum() {  
  16.         return getOne() + getTwo();  
  17.     }  
  18. }  

能够看到,上面是一个Calculate类,这个类的功能很是简单,先将one和two这两个字段进行了封装,而后提供了getOne()方法获取one字段的值,提供了getTwo()方法获取two字段的值,还提供了一个getSum()方法用于获取总和的值。

 

这里咱们注意到,getSum()方法当中的算法就是将one和two的值相加进行返回,可是它获取one和two的值的方式也是经过getters方法进行获取的,其实这是一种彻底没有必要的方式,由于getSum()方法自己就是Calculate类内部的方法,它是能够直接访问到Calculate类中的封装字段的,所以这种写法在Android上是不推崇的,咱们能够进行以下修改:

  1. public class Calculate {  
  2.       
  3.     private int one = 1;  
  4.       
  5.     private int two = 2;  
  6.   
  7.     ......  
  8.       
  9.     public int getSum() {  
  10.         return one + two;  
  11.     }  
  12. }  

改为这种写法以后,咱们就避免了在内部调用getters/setters方法,而对于外部而言Calculate类仍然是具备很好的封装性的。