高效容器
针对Android这种移动平台,SparseArray
、ArrayMap
用来代替HashMap
在有些情况下能带来更好的性能提升。
SparseArray
SparseArray
比HashMap
更省内存,在某些条件下性能更好,主要是因为它避免了对key
的自动装箱(int
转为Integer
类型),它内部则是通过两个数组来进行数据存储的,一个存储key
,另外一个存储value
,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间,我们从源码中可以看到key
和value
分别是用数组表示:
privateint[] mKeys;
private Object[] mValues;
SparseArray
只能存储key
为int
类型的数据,同时,SparseArray
在存储和读取数据时候,使用的是二分查找法。
添加数据
public void put(int key, E value)
删除数据
public void remove(int key)
public void delete(int key)
获取数据
public E get(int key, E valueIfKeyNotFound)
public E get(int key)
查询
获取对应的key:
publicintkeyAt(int index)
获取对应的value:
public E valueAt(int index)
虽说SparseArray
性能比较好,但是由于其添加、查找、删除数据都需要先进行一次二分查找,所以在数据量大的情况下性能并不明显,将降低至少50%。
满足下面两个条件我们可以使用SparseArray代替HashMap:
- 数据量不大,最好在千级以内
key
必须为int
类型,这中情况下的HashMap
可以用SparseArray
代替。
ArrayMap
ArrayMap
是一个<key,value>
映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key
的hash
值,另外一个数组记录Value
值,它和SparseArray
一样,也会对key
使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index
,然后通过index
来进行添加、查找、删除等操作,所以,应用场景和SparseArray
的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%。
添加数据
public V put(K key, V value)
删除数据
public V remove(Object key)
获取数据
public V get(Object key)
查询
获取对应的key:
public K keyAt(int index)
获取对应的value:
public V valueAt(int index)
ArrayMap
应用场景:
- 数据量不大,最好在千级以内
- 数据结构类型为Map类型
ArrayMap
在循环遍历:
for(int i=0;i<map.size();i++){
Object keyObj=map.keyAt(i);
Object valObj=map.valueAt(i);
...
}
为了避免autoboxing
带来的效率问题,Android特地提供了一些如下的Map
容器用来替代HashMap
,不仅避免了autoboxing
,还减少了内存占用:
容器 | 类型 |
---|---|
SparseBoolMap | <bool,obj> |
SparseIntMap | <int,obj> |
SparseLongMap | <long,obj> |
LongSparseMap | <long,obj> |
enum
使用enum之后的dex大小会变大,而且使用enum,运行时还会产生额外的内存占用
Android官方强烈建议不要在Android程序里面使用到enum。
Object Pools
在程序里面经常会遇到的一个问题是短时间内创建大量的对象,导致内存紧张,从而触发GC导致性能问题。对于这个问题,我们可以使用对象池技术来解决它。通常对象池中的对象可能是bitmaps,views,paints等等。
使用对象池技术有很多好处,它可以避免内存抖动,提升性能,但是在使用的时候有一些内容是需要特别注意的。通常情况下,初始化的对象池里面都是空白的,当使用某个对象的时候先去对象池查询是否存在,如果不存在则创建这个对象然后加入对象池,但是我们也可以在程序刚启动的时候就事先为对象池填充一些即将要使用到的数据,这样可以在需要使用到这些对象的时候提供更快的首次加载速度,这种行为就叫做预分配。使用对象池也有不好的一面,程序员需要手动管理这些对象的分配与释放,所以我们需要慎重地使用这项技术,避免发生对象的内存泄漏。为了确保所有的对象能够正确被释放,我们需要保证加入对象池的对象和其他外部对象没有互相引用的关系。
Index 还是Iterate
遍历容器是编程里面一个经常遇到的场景。在Java语言中,使用Iterate是一个比较常见的方法。可是在Android开发团队中,大家却尽量避免使用Iterator来执行遍历操作。下面我们看下在Android上可能用到的三种不同的遍历方法:
- List(iterate)
for(Iterate it=list.iterate();it.hasNext();){
Object obj=it.next();
...
}
- List(For-Index)
int size=list.size();
for(int i=0;i<size;i++){
Object obj=list.get(i);
...
}
- List(Simplified)
for(Object obj:list){
...
}
使用上面三种方式在同一台手机上,使用相同的数据集做测试,他们的表现性能如下所示:
从上面可以看到for index的方式有更好的效率,但是因为不同平台编译器优化各有差异,我们最好还是针对实际的方法做一下简单的测量比较好,拿到数据之后,再选择效率最高的那个方式。