ArrayMap及SparseArray是android的系统API,是专门为移动设备而定制的。用于在一定情况下取代HashMap而达到节省内存的目的。
在Android开发中HashMap使用频率相当高,回顾一下
HashMap的结构,其是以array存储链表的头结点,找到头结点后在进行遍历查找,如下图:
时间效率方面,利用hash算法,插入和查找等操作都很快,且一般情况下,每一个数组值后面不会存在很长的链表(因为出现hash冲突毕竟占比较小的比例),所以不考虑空间利用率的话,HashMap的效率非常高。
但是HashMap
有如下特点:
1: HashMap中默认的存储大小是一个容量为16的数组,所以当我们创建出一个HashMap对象时,即使里面没有任何元素,也要分别一块内存空间给它,而且当我们不断的向HashMap里put数据时,达到一定的容量限制时HashMap的空间将会扩大,从图示中可以看出当HashMap扩容容量过多,元素较少时会产生内存使用不平衡,即浪费不少内存。
2: 还有使用HashMap会涉及一个要求:key与value必须为对象类型,而不能为基本类型,
这就导致了本可以使用基本类型的数据必须转换为其对象包装类型(int->Integer,long->Long......)这就涉及到需要占用更多内存以及拆箱装箱频繁转换问题。
在jvm中一个Integer类型占用内存大小 > 12byte,
一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。
Object ob = new Object();
这样在程序中完成了一个空Java对象的声明,但是它所占的空间为:4byte+8byte,4byte是Java statck中保存引用所需要的空间,而8byte则是Java heap中对象的信息。
所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte。
包装类型已经成为对象类型,因此需要按照对象类型的大小计算至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,同时,Java进行对对象的内存分配时其大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。
3:HashMap本质上是一个HashMapEntry构成的数组。每个HashMapEntry的实体都包括:
- 一个非基本类型的key
- 一个非基本类型的value
- 一个key的Hashcode
- 指向下一个Entry的指针
final K key;
V value;
HashMapEntry<K,V> next;
int hash;
所以总结下HashMap的缺点:
- 当HashMap扩容容量过多,元素较少时会产生内存使用不平衡,即浪费不少内存。
- 基本数据类型自动装箱意味着需要产生额外的对象。
- HashMapEntry对象的创建,并且HashMapEntry自己本身也会产生额外的对象。
- 哈希是很好的实现方案,但是如果实现的不好将会让我们的开销回到O(N)
所以下面开始介绍Android为我们提供的各种优化的类:
ArrayMap,
SimpleArrayMap
,
SparseArray,
LongSparseArray,
SparseIntArray, SparseLongArray,SparseBooleanArray。
先从如何使用它们开始吧:
java.util.HashMap<String, String> hashMap = new java.util.HashMap<String, String>(16);
hashMap.put("key", "value");
hashMap.get("key");
hashMap.entrySet().iterator();
android.util.ArrayMap<String, String> arrayMap = new android.util.ArrayMap<String, String>(16);
arrayMap.put("key", "value");
arrayMap.get("key");
arrayMap.entrySet().iterator();
android.support.v4.util.ArrayMap<String, String> supportArrayMap =
new android.support.v4.util.ArrayMap<String, String>(16);
supportArrayMap.put("key", "value");
supportArrayMap.get("key");
supportArrayMap.entrySet().iterator();
android.support.v4.util.SimpleArrayMap<String, String> simpleArrayMap =
new android.support.v4.util.SimpleArrayMap<String, String>(16);
simpleArrayMap.put("key", "value");
simpleArrayMap.get("key");
//simpleArrayMap.entrySet().iterator(); <- will not compile
android.util.SparseArray<String> sparseArray = new android.util.SparseArray<String>(16);
sparseArray.put(10, "value");
sparseArray.get(10);
android.util.LongSparseArray<String> longSparseArray = new android.util.LongSparseArray<String>(16);
longSparseArray.put(10L, "value");
longSparseArray.get(10L);
android.util.SparseLongArray sparseLongArray = new android.util.SparseLongArray(16);
sparseLongArray.put(10, 100L);
sparseLongArray.get(10);
SparseArray
先看看
SparseArray的结构如下图:
SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),还避免了HashMapEntry对象的创建,它内部则是通过两个数组来进行数据存储的,一个存储key(有序),另外一个存储value,为了优化性能,对key使用了二分查找的方法,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间,我们从源码中可以看到key和value分别是用数组表示:
因为key为int也就不需要什么hash值了,只要int值相等,那就是同一个对象,简单粗暴。
空间上对比:与HashMap,去掉了Hash值的存储空间,没有next的指针占用,还有
HashMapEntry
的内存占用。
时间上对比:因为需要有序,可能存在大量的数组搬移。但是它避免了装箱的环节,装箱过程还是很费时的。
- 它里面也用了两个数组。一个int[] mKeys和Object[] mValues。从名字都可以看得出来一个用来存储key一个用来保存value的。
- key(不是它的hashcode)保存在mKeys[]的下一个可用的位置上。所以不会再对key自动装箱了。
- value保存在mValues[]的下一个位置上,value还是要自动装箱的,如果它是基本类型。
- 查找key还是用的二分法查找。也就是说它的时间复杂度还是O(logN)
- 知道了key的index,也就可以用key的index来从mValues中检索出value。
SparseArray只能存储key为int类型的数据,同时SparseArray在存储和读取数据时候,对key使用的是二分查找法,所以key存储是有序的并不是按插入的顺序,可以看看:
也就是在put添加数据的时候,会使用二分查找法查找添加的元素的key,然后按照从小到大的顺序排列好,所以,SparseArray存储的元素都是按元素的key值从小到大排列好的。 而在获取数据的时候,也是使用二分查找法判断元素的位置,所以,在获取数据的时候非常快。
满足下面两个条件我们可以使用SparseArray代替HashMap:
- 数据量不大,最好在千级以内
- key必须为int类型
LongSparseArray,SparseIntArray,SparseLongArray,SparseBooleanArray
SparseArray只接受int类型作为key,而LongSparseArray可以用long作为key,实现原理和SparseArray一致。
其次对于key是int类型而value也是基本数据类型,我们可以对应使用SparseIntArray,SparseLongArray ,SparseBooleanArray 。它们使用方式是和SparseArray一样的。它的好处是mValues[]是基本类型的数组。也就意味着无论是key还是value都不用装箱。并且相对于HashMap来说我们节约了3个对象的初始化(Entry,Key和Value),但是我们将查询复杂度从O(1)上升到了O(logN)
SparseArray实现