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

在前两篇文章当中,咱们主要学习了Android内存方面的相关知识,包括如何合理地使用内存,以及当发生内存泄露时如何定位出问题的缘由。那么关于内存的知识就讨论到这里,今天开始咱们将学习一些性能编码优化的技巧。html

 

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

 

避免建立没必要要的对象

 

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

 

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

 

  • 若是咱们有一个须要拼接的字符串,那么能够优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号链接符,由于使用加号链接符会建立多余的对象,拼接的字符串越长,加号链接符的性能越低。编程

  • 在没有特殊缘由的状况下,尽可能使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是同样。数组

  • 当一个方法的返回值是String的时候,一般能够去判断一下这个String的做用是什么,若是咱们明确地知道调用方会将这个返回的String再进行拼接操做的话,能够考虑返回一个StringBuffer对象来代替,由于这样能够将一个对象的引用进行返回,而返回String的话就是建立了一个短生命周期的临时对象。数据结构

  • 正如前面所说,基本数据类型要优于对象数据类型,相似地,基本数据类型的数组也要优于对象数据类型的数组。另外,两个平行的数组要比一个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的两个数组,使用起来要比Custom(Foo,Bar)[]这样的一个数组高效得多。并发

 

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

 

静态优于抽象

 

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

 

对常量使用static final修饰符

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

使用加强型for循环语法

 

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
}
}

 

能够看到,上述代码当中咱们使用了三种不一样的循环方式来对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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}
}

 

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

 

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

 

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

 

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

 

 

http://www.androidchina.net/1961.html#rd?sukey=fc78a68049a14bb25feaa125f45f038b5eee1640d4fb19c886789710713c8e21474b702046947fc4bc660b2c685cb70a

相关文章
相关标签/搜索