Android memory optimization (use SparseArray and ArrayMap instead of HashMap)

In Android development, most of the APIs we use are Java APIs, such as the HashMap API, which has a very high usage rate. However, for a mobile platform such as Android, which is very sensitive to memory, many times using some Java APIs cannot achieve better performance. Good performance, on the contrary, consumes more memory, so for mobile platforms such as Android, APIs that are more in line with their own are also introduced, such as SparseArray and ArrayMap to replace HashMap, which can bring better performance improvement in some cases.

Before introducing them, let's introduce the internal storage structure of HashMap, and understand why SparseArray and ArrayMap are recommended.

HashMap

Internally, HashMap uses an array with a default capacity of 16 to store data, and each element in the array is the head node of a linked list. Therefore, to be more precise, the internal storage structure of HashMap uses a hash table. Zipper structure (array + linked list), as shown in the figure: 
This method of storing data is called the zipper method 
write picture description here 
and each node is an Entry type, so what is an Entry? Let's take a look at the properties of Entry in HashMap:

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

From this, we know that the contents stored in Entry include key, value, hash value, and the next Entry. Then, what rules are these Entry data stored according to? It is to calculate the hash value of the element key, and then take the remainder of the length of the array in the HashMap to get the storage location of the element. The calculation formula is hash(key)%len, for example: suppose hash(14)=14, hash(30)=30 ,hash(46)=46, we take the remainder of len respectively to get 
hash(14)%16=14, hash(30)%16=14, hash(46)%16=14, so the keys are 14, 30, The three elements of 46 are stored in the position with the subscript 14 of the array, such as: 
write picture description here 
It can be seen from this that if there are multiple elements with the same hash value of the key, the latter element will not overwrite the previous element, but take a linked list In the method of adding the elements added later to the end of the linked list, the problem of hash conflicts is solved. From this, we know that the method of dealing with hash conflicts in HashMap is the chain address method. Here is a supplementary knowledge point. The methods of dealing with hash conflicts are: The following:

  1. open address law
  2. Rehashing
  3. chain address method
  4. Create a common overflow area

Speaking of this, the point is, we know that the default storage size in HashMap is an array with a capacity of 16, so when we create a HashMap object, even if there are no elements in it, we must give it a separate memory space, and , when we continue to put data into HashMap, when a certain capacity limit is reached (this capacity satisfies such a relationship, the capacity will be expanded: the amount of data in HashMap > capacity * load factor, and the default load factor in HashMap is 0.75), the space of the HashMap will be expanded, and the new space must be twice as large after the expansion. We can see that there is such a line of code in the put() method:

int newCapacity = oldCapacity * 2;
  • 1
  • 1

所以,重点就是这个,只要一满足扩容条件,HashMap的空间将会以2倍的规律进行增大。假如我们有几十万、几百万条数据,那么HashMap要存储完这些数据将要不断的扩容,而且在此过程中也需要不断的做hash运算,这将对我们的内存空间造成很大消耗和浪费,而且HashMap获取数据是通过遍历Entry[]数组来得到对应的元素,在数据量很大时候会比较慢,所以在Android中,HashMap是比较费内存的,我们在一些情况下可以使用SparseArray和ArrayMap来代替HashMap。

SparseArray

SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间,我们从源码中可以看到key和value分别是用数组表示:

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

我们可以看到,SparseArray只能存储key为int类型的数据,同时,SparseArray在存储和读取数据时候,使用的是二分查找法,我们可以看看:

 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);
        ...
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

添加数据

public void put(int key, E value)
  • 1
  • 1

删除数据

 public void remove(int key)
  • 1
  • 1

or

public void delete(int key)
  • 1
  • 1

其实remove内部还是通过调用delete来删除数据的

获取数据

public E get(int key)
  • 1
  • 1

or

public E get(int key, E valueIfKeyNotFound)
  • 1
  • 1

该方法可设置如果key不存在的情况下默认返回的value

特有方法

在此之外,SparseArray还提供了两个特有方法,更方便数据的查询: 
获取对应的key:

public int keyAt(int index)
  • 1
  • 1

获取对应的value:

public E valueAt(int index)
  • 1
  • 1

SparseArray应用场景:

虽说SparseArray性能比较好,但是由于其添加、查找、删除数据都需要先进行一次二分查找,所以在数据量大的情况下性能并不明显,将降低至少50%。

满足下面两个条件我们可以使用SparseArray代替HashMap:

  • 数据量不大,最好在千级以内
  • key必须为int类型,这中情况下的HashMap可以用SparseArray代替:
HashMap<Integer, Object> map = new HashMap<>();
用SparseArray代替:
SparseArray<Object> array = new SparseArray<>();
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

ArrayMap

这个api的资料在网上可以说几乎没有,然并卵,只能看文档了 
ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%。

添加数据

public V put(K key, V value)
  • 1
  • 1

获取数据

public V get(Object key)
  • 1
  • 1

删除数据

public V remove(Object key)
  • 1
  • 1

特有方法

它和SparseArray一样同样也有两个更方便的获取数据方法:

public K keyAt(int index)
public V valueAt(int index)
  • 1
  • 2
  • 1
  • 2

ArrayMap应用场景

  • 数据量不大,最好在千级以内
  • 数据结构类型为Map类型
ArrayMap<Key, Value> arrayMap = new ArrayMap<>();
  • 1
  • 1

【注】:如果我们要兼容aip19以下版本的话,那么导入的包需要为v4包

import android.support.v4.util.ArrayMap;
  • 1
  • 1

总结

SparseArray和ArrayMap都差不多,使用哪个呢? 
假设数据量都在千级以内的情况下:

1. If the type of the key has been determined to be an int type, then use SparseArray, because it avoids the process of automatic boxing. If the key is of type long, it also provides a LongSparseArray to ensure that the key is used when the type is long

2. If the key type is another type, use ArrayMap




Reprinted from http://blog.csdn.net/u010687392/article/details/47809295?ticket=ST-167417-vk6TdDcwcHQWVKht5YhD-passport.csdn.net

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325594708&siteId=291194637