对于性能优化这个知识点来说, 实在是太广了, 博主本人也一直非常关注这方面的学习, 而对于性能优化来说它包括了非常非常非常多方面, 比如: I/O 的优化, 网络操作的优化, 内存的优化, 数据结构的优化, 代码层次的优化, UI 渲染优化, CPU 资源使用率的优化, 异常处理的优化等等等等...
本篇文章就博主本人的理解来讲述一些在 Android 开发中可以优化的地方
ArrayList 和 Vector
ArrayList 和 Vector 都是内部以数组实现的 List, 它们两唯一的区别就是对多线程的支持, ArrayList 是线程不安全的, 而 Vector 内部对大多数方法都做了同步, 是线程安全的, 既然是线程安全的, 所以性能方面肯定不如 ArrayList 了(当然想法肯定是对的), 不过这需要看哪方面了, ArrayList 在 add,get,remove 等操作效率肯定是高于 Vector 的, 而在内存方面, Vector 却比 ArrayList 表现的更好, 这归根都是 ArrayList 的扩容策略导致的, 稍后分析.
实现 RandomAccess 接口的集合使用 fori 遍历
先谈谈 List 集合的遍历方式, 有三种: foreach,iterator,fori.
而在开发中一般需要遍历时首选肯定是 foreach 了, 因为它效率高, 这个观点没错, 不过需要分场合了.
下面是我用这三种方式测试遍历有 100w 条数据的 ArrayList 集合:
- long start = System.currentTimeMillis();
- for (int i = 0; i <size; i++) {
- data.get(i);
- }
- long end = System.currentTimeMillis();
- Log.v("zxy","fori 花费:"+(end-start));
- start = System.currentTimeMillis();
- for (Integer integer : data) {
- }
- end = System.currentTimeMillis();
- Log.v("zxy","foreach 花费:"+(end-start));
- Iterator<Integer> iterator = data.iterator();
- start = System.currentTimeMillis();
- while (iterator.hasNext()){
- iterator.next();
- }
- end = System.currentTimeMillis();
- Log.v("zxy","iterator 花费:"+(end-start));
11-19 09:11:44.276 1418-1418/? V/zxy: fori 花费: 30
11-19 09:11:44.380 1418-1418/? V/zxy: foreach 花费: 105
11-19 09:11:44.476 1418-1418/? V/zxy: iterator 花费: 95
而通常我们所说的效率高的 foreach 在遍历上却显得不如意, 而 fori 效率表现的最好, 这是因为 ArrayList 和 Vector 集合内部实现由数组实现, 所以随机访问的速度是很快的, 对于可以进行随机访问的 List,JDK 为它们实现了 RandomAccess 接口, 表示支持快速随机访问.
而在遍历有 1w 条数据的 LinkedList 集合时:
11-19 09:33:23.984 1737-1737/? V/zxy: fori 花费: 351
11-19 09:33:23.988 1737-1737/? V/zxy: foreach 花费: 2
11-19 09:33:23.992 1737-1737/? V/zxy: iterator 花费: 4
则 foreach 表现最佳, 所以对数组, 或者实现了 RandomAccess 接口的 List, 遍历用 fori 性能最佳, 对 LinkedList 等以链表实现的集合遍历时使用 foreach 或者 iterator 性能最佳, 因为 foreach 的实现就是通过 iterator 实现的.
我们可以这样判断该 List 遍历用哪种方式:
- if (list instanceof RandomAccess)
- {
- for (int i = 0; i <list.size(); i++) {}
- } else {
- Iterator<?> iterator = list.iterator();
- while (iterator.hasNext()) {
- iterator.next();
- }
- }
预知容量的情况下构造 ArrayList 时尽量指定初始大小
ArrayList 内部的扩容策略是当其所存储的元素数量超过它已有的大小时, 它就会以 1.5 倍的容量进行扩容, 也就是假如当前 ArrayList 的容量为 10000, 那么它在需要再存储一个元素时, 即第 10001 个元素, 由于容量不够而进行一次扩容, 而 ArrayList 扩容后的容量则变为了 15000, 而多出了一个元素就多了 5000 个元素的空间, 这太浪费内存资源了, 而且扩容还会导致整个数组进行一次内存复制, 而 ArrayList 集合默认大小为 10, 因此合理的设置 ArrayList 的容量可避免集合进行扩容. ArrayList 内部扩容和数组为:
- Object[] newArray = new Object[s +
- (s <(MIN_CAPACITY_INCREMENT / 2) ?
- MIN_CAPACITY_INCREMENT : s>> 1)];
- System.arraycopy(a, 0, newArray, 0, s);
- array = a = newArray;
而 Vector 内部扩容策略为按需扩容, 每次 + 1:
- if (capacityIncrement <= 0) {
- if ((adding = elementData.length) == 0) {
- adding = 1;
- }
- } else {
- adding = capacityIncrement;
- }
- E[] newData = newElementArray(elementData.length + adding);
同样, 在众多 Map 集合中也有各自扩容策略, 比如 HashMap 每次扩容时新容量等于原始的容量 * 2. 在我们常用做字符串拼接的 StringBuffer 和 StringBuilder 内部, 实际上也是有扩容策略, 默认为扩容为原始的 1.5 倍.
所以, 在这些需要扩容的 API 上, 如果预先知道了数据的大小, 则预先设置, 这样不仅可以避免扩容导致的空间浪费, 而且还可避免内部调用 System.arraycopy()进行大量数据复制.
* 程序如果需要通过索引下标对 List 做随机访问, 应优先考虑 ArrayList 和 Vector, 迫不得已尽量不要使用 LinkedList
虽说 ArrayList 在内存上比不上 Vector, 不过它对数据操作的效率高, 特别是在 Android 等移动设备上, 采取牺牲一点空间换时间的方式还是可取的, 而涉及到线程安全方面, 则使用 Vector.
如果一个方法不需要使用该对象的成员, 那么把该方法设为 static
静态调用该方法比对象调用该方法快 15%~20%, 因为这样可以从方法签名上就可以看出该方法调用不会影响该对象的状态
巧用 final 关键字
final 关键字一般在定义常量和方法用的比较多, 而大多数人对 final 的理解往往是在不可变性上, 而 final 对性能优化也有很大的作用.
比如: static int AGE = 10; 当 10 在后面被引用时, 这时会有一个字段查找的过程, 对于 int 类型也就是查找方法区中的整型常量池, 而对于 final 的常量, 则省去了这个过程, 比如: static final int AGE = 10; 在使用到 AGE 的地方将直接用 10 代替.
不过对于上面这种优化技巧, 仅对基本类型和 String 类型有效, 对于其它的引用类型则无效, 但是我们在声明常量的时候加上 static final 依然是个好习惯
对与 final 关键字, 还有一个强大的作用, 就是对那些使用频繁, 已经确定为终态的方法定义 final, 这样有什么好处呢?
说这个前先来说说 java 中方法的执行过程吧, 当调用某个方法时, 首先这个方法会入栈, 执行完毕后, 这个方法出栈, 资源释放, 而这个过程内部其实是内存地址的转移过程, 当执行入栈的方法时, 其实就是把程序的执行地址转移到该方法存放的内存地址中, 而做此操作前, 还有必须进行原先程序执行的内存地址保存过程, 当方法执行完出栈后则继续按保存的地址继续执行程序, 而这个过程, 就是方法的调用过程.
所以, 方法的调用过程实际上是需要空间和时间的, 而对于同一个方法的频繁调用的优化实际上就是使用内联的办法.
又说到内联函数, 内联函数实际上是在编译期做的优化, 编译器会将标为为内联的函数在其调用的地方直接用整个函数体进行替换掉, 这就省去了函数调用所耗去的时间资源了, 而换来的却是目标代码量的增加, 所以内联这种优化策略实际上是采取了以空间换时间的策略, 对于移动端来说, 巧用内联函数实则非常有益.
而要是一个函数成为内联函数, 就是将它定义为 final, 这样在程序编译时, 编译器会自动将 final 函数进行内联优化, 那么在调用该函数时则直接展开该函数体进行使用.
总结, 并不是内联函数越多越好, 一方面它对我们程序的运行效率上确实有提升, 而另一方面, 对于过多的使用内联函数, 则会弄巧成拙, 有可能会把某个方法的方法体越搞越大, 而且对于某些方法体比较大的方法, 内联展开的时间有可能超过方法调用的时间, 所以这不仅不会提供性能, 反而是降低了本该有的性能.
综合来看, 我们可以对那些使用频繁, 已经确定为终态的方法, 方法体不大的方法用 final 修饰, 提供程序的性能.
优先考虑系统中提供的代码而不是自己写
系统内置了许多非常方便的 API 供我们使用, 比如: System,Arrays,Collections,String 等内置了许多方法 API, 这比我们自己手写方便多了, 除了这个外, 对于 Android 来说许多 API 都使用了底层 C/C++ 实现, 所以效率上也比我们自己写快, 同样, 对于系统 API,DVM 往往也会使用内联的方式提高效率
慎用异常
慎用异常并不是不用异常, 而是指程序中用抛异常的方式来执行某些操作, 比如有些人会以强抛异常方式来中断某些操作等. 因为抛异常时都会执行 fillInStackTrace(); 方法, 该方法作用就是重新调整堆栈, 这使得没有必要用异常的地方一定要避免使用
最后
来源: http://www.jianshu.com/p/92d8872fc101