Android内存优化(一)

Android内存优化(一)

在Android开发的时候,我们大部分使用的是java的api,但是对于移动端来说,内存是一个很敏感的资源,如果不注意的话很容易crash,而内存优化又没有一个可以立竿见影的东西说是使用了就会很显著的降低内存消耗,而是需要在代码中根据具体的使用场景来合理去优化,每个细节都做到了优化那么整体的性能当然就会提升。所针对Android这种移动平台,也推出了更符合自己的api,比如SparseArray(稀疏数组)以及ArrayMap用来代替HashMap,在某些情况下合理使用会带来更好的性能提升。

HashMap

HashMap内部使用了一个默认容量为16的数组来存储数据,而数组中每个元素又是一个链表的头结点,所以其实HashMap就是一个数组+链表的数据结构。如图:

左边纵向是数组,右边横向为链表。

这个数据结构中每一个都一个Entry类型,那么Entry的数据结构是怎么样的呢:

1
2
3
4
final K key;
V value;
final int hash;
HashMapEntry<K, V> next;

从中我们可以看到Entry中有key,value,hash值以及下一个结点Entry,那么Entry数据是按照什么规则来存储的呢:通过计算元素key的hash值,然后对HashMap中的数组长度(默认16)取余从而得到该元素应该存在哪里,计算公式为==hash(key)%len==,举一个例子:

hash(14)=14,hash(30)=30,hash(46)=46,我们分别对len取余,得到
hash(14)%16=14,hash(30)%16=14,hash(46)%16=14,所以key为14、30、46的这三个元素存储在数组下标为14的位置

如图:

从中可以看出,如果有多个元素key的hash值相同的话,后一个元素并不回覆盖前一个,而是放在链表末尾,从而解决了Hash冲突的问题。(问题1:如果key的hash值取余大于15,那么元素该放在哪里?)。

所以,重点是:我们知道HashMap默认存储大小就是一个容量为16的数组,所以当我们创建出一个HashMap的对象的时候,即使里面没有任何元素,系统也是会分配这么多空间给它的,而且,当我们不断往里面put数据的时候,当达到了一定的容量限制时,HashMap会扩容,而且扩大后的新的空间一定是原来的==两倍==。

扩容条件:HashMap中的数据量>容量*加载因子,默认加载因子是0.75

参见HashMap详解

参见HashMap的实现

只要一满足扩容条件,HashMap的空间将会以2倍的规律进行增大。假如我们有几十万、几百万条数据,那么HashMap要存储完这些数据将要不断的扩容,而且在此过程中也需要不断的做hash运算,这将对我们的内存空间造成很大消耗和浪费。附上hash函数的实现图:

可以看到这个函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或。

SparseArray

SparesArray相比HashMap更省内存,某些条件下性能更好。它的内部规则是通过两个数组来存储数据的,一个存储key,另一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约了内存空间。我们从源码中可以看到key和value分别是用数组表示:

1
2
private int[] mKeys;
private Object[] mValues;

从源码中看到,SparesArray只能存储key为int类型的数据,同时,SparesArray在存储和读取数据的时候,使用的是二分查找法

1
2
3
4
5
6
7
8
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
...
}
public E get(int key, E valueIfKeyNotFound) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
...
}

也就是在put添加数据的时候,会使用二分查找法和之前的key比较当前我们添加的元素的key的大小,然后按照从==小到大的顺序排列好==,所以,SparseArray存储的元素都是按元素的key值从小到大排列好的。
而在获取数据的时候,也是使用二分查找法判断元素的位置,所以,在获取数据的时候非常快,比HashMap快的多,因为HashMap获取数据是通过遍历Entry[]数组来得到对应的元素。

使用场景:
虽然SparesArray性能好,但是由于其添加,查找,删除数据都需要先进行一次二分查找,所以在数据量大的情况下性能并不明显,还有有下降,所以使用也是要看使用场景的:

  • 数据量不大,在千以内(Android开发大部分场景吧)
  • key必须为int类型
    以上就是使用它的场景了。

ArrayMap

ArrayMap其实也是一个key-value映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用了两个数组存储数据,一个数组记录key的hash值,另外一个数组记录value值,和SparesArray一样,也会对可以使用二分法进行从小到大的排序,在添加,删除,查找数据的时候都是使用二分法查到相应的index,然后通过index来进行操作,它的使用场景:

  • 数据量不大,千以内
  • 数据结构为Map类型

例子:

1
ArrayMap<Key, Value> arrayMap = new ArrayMap<>();

如果我们要兼容api19以下版本,那么导入V4包
import android.support.v4.util.ArrayMap;