Android性能优化之被忽视的优化点
对于性能优化这个知识点来说,实在是太广了,博主本人也一直非常关注这方面的学习,而对于性能优化来说它包括了非常非常非常多方面,比如: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();方法,该方法作用就是重新调整堆栈,这使得没有必要用异常的地方一定要避免使用
欢迎补充。。。
Android性能优化之被忽视的优化点的更多相关文章
- Android性能优化之被忽视的Memory Leaks
起因 写博客就像讲故事.得有起因,经过,结果,人物.地点和时间.今天就容我给大家讲一个故事. 人物呢.肯定是我了. 故事则发生在近期的这两天,地点在coder君上班的公司.那天无意中我发现了一个奇怪的 ...
- android 性能优化
本章介绍android高级开发中,对于性能方面的处理.主要包括电量,视图,内存三个性能方面的知识点. 1.视图性能 (1)Overdraw简介 Overdraw就是过度绘制,是指在一帧的时间内(16. ...
- Android性能优化典范第一季
2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有关 ...
- [转]Android性能优化典范
2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有关 ...
- [Android Pro] Android性能优化典范第一季
reference to : http://www.cnblogs.com/hanyonglu/p/4244035.html#undefined 2015年伊始,Google发布了关于Android性 ...
- Android优化—— Google 发布 Android 性能优化典范
阅读目录 0)Render Performance 1)Understanding Overdraw 2)Understanding VSYNC 3)Tool:Profile GPU Renderin ...
- Android性能优化典范(转)
转载自oschina. 2015年伊始,Google发布了关于Android性能优化典范的专题, 一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍 ...
- Android性能优化之如何避免Overdraw
什么是Overdraw? Overdraw就是过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,而每次绘 ...
- Google 发布 Android 性能优化典范
2015年伊始,Google发布了关于Android性能优化典范的专题, 一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有 ...
随机推荐
- python基础(1)
1.python中三元表达式 类比于C.C++.Java中都有的三目运算符,python中使用下面语句实现三元表达式 true_part if condition else false_part. c ...
- Windows Server2003本地用户的批量导入和导出(转)
AD域环境的用户导入和导出 Windows server 2003 批量导入用户---CSVDE 在新搭建的域环境中,有许多的域帐号需要导入,可以采用csvde命令来导入域用户:新建一个txt文本文件 ...
- 利用Runtime实现简单的字典转模型
前言 我们都知道,开发中会有这么一个过程,就是将服务器返回的数据转换成我们自己定义的模型对象.当然服务器返回的数据结构有xml类型的,也有json类型的.本文只讨论json格式的. 大家在项目中一般是 ...
- ES6(Decorator(修饰器))
Decorator(修饰器) 1.基本概念 函数用来修改 类 的行为 1.Decorator 是一个函数 2.通过Decorator(修饰器)能修改 类 的行为(扩展 类 的功能)3.Decorato ...
- python OptParse模块的用法详解
OptParse模块的简单介绍 Python 有两个内建的模块用于处理命令行参数: 一个是 getopt只能简单处理 命令行参数: 另一个是 optparse,它功能强大,而且易于使用,可以方便地生成 ...
- 20160216.CCPP体系详解(0026天)
程序片段(01):01.MemCpy.c 内容概要:内存拷贝 #include <stdio.h> #include <stdlib.h> #include <memor ...
- solr界面
1.1 界面功能介绍 1.1.1 Analysis
- 长度为N的数组乱序存放着0带N-1.现在只能进行0与其他数的swap操作,请设计并实现排序,必须通过交换实现排序。
void sort(int* arr, int len) { if (!arr) { return; } for (int i = 1; i < len; ++i) { while (arr[0 ...
- Java进阶(四十三)线程与进程的区别
Java进阶(四十三)线程与进程的区别 1.线程的基本概念 概念:线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必 ...
- 带你深入理解STL之Set和Map
在上一篇博客带你深入理解STL之RBTree中,讲到了STL中关于红黑树的实现,理解起来比较复杂,正所谓前人种树,后人乘凉,RBTree把树都种好了,接下来就该set和map这类关联式容器来" ...