高效程序有两个重要指标:速度,内存,移动app中内存比重要多一些,为此在速度相差不是很大的时候,优先考虑内存,container是一个重要部分,对此google对一些原java容器设计新的容器进行替换Map结构。在写程序时使用Map类大部份情况都会用到,尤其是HashMap使用频率相当高,使用HashMap会涉及一个要求key与value必须为对象类型,

而不能为基本类型,这就导致了本可以基本类型的数据必须转换为其对象包装类型(int->Integer,long->Long......)这就涉及到需要占用更多内存以及拆箱装箱频繁转换问题。

例如:Map<Integer,Integer>,Integer占用内存>12

这里涉及一个引用类型计算问题,引用java规范;

在Java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。

看下面语句:

Object ob = new Object();

这样在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。4byte是Java栈中保存引用的所需要的空间。而那8byte则是Java堆中对象的信息。

因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte。

包装类型已经成为对象了,因此需要把他们作为对象来看待。包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,同时,因为Java对象分配内存时其大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),有些类型的内存占用更是夸张(随便想下就知道了)。因此,可能的话应尽量少使用包装类。

为此google专门设计了当key为基本类型时的替换Map数据结构容器,在android.util包下,根据文档介绍如下

SparseArray -> map integer to Object
SparseBooleanArrays -> map integers to booleans
SparseIntArrays -> map integers to integers
SparseLongArrays -> map integers to longs
LongSparseArray -> map longs to Objects

可以看出key为integer时情况较多,原理是key存储在int[] mKeys数组内,value存储在对应的(int,long,object)[] mValues数组内,采用二分法计算索引位置

SparseArray实现

public class SparseArray<E> implements Cloneable {
private static final Object DELETED = new Object();
private boolean mGarbage = false; private int[] mKeys;//使用array存储int key
private Object[] mValues;//使用array存储泛型Value
private int mSize; /**
* Creates a new SparseArray containing no mappings.
*/
public SparseArray() {
this(10);//默认容量10
} public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;
} else {
mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
mKeys = new int[mValues.length];
}
mSize = 0;
} public E get(int key) {
return get(key, null);
} @SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);//使用二分查找查找key if (i < 0 || mValues[i] == DELETED) {//没有找到key或者Value已经被deleted
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
} public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);//二分查找key是否存在,如果没有找到时,这里返回的是-low,也就是待插入位置取反
if (i >= 0) {//存在直接替换value
mValues[i] = value;
} else {
i = ~i;//待插入位置 if (i < mSize && mValues[i] == DELETED) {//pos在size范围内,并且该pos被deleted直接赋值
mKeys[i] = key;
mValues[i] = value;
return;
} if (mGarbage && mSize >= mKeys.length) {//是否需要回收处理
gc(); // Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
} mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);//扩容key处理
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);//扩容value处理
mSize++;
}
}
......

这里注意到一个Deleted标识,这个是什么呢?就是前面类的一个object成员变量,当执行delete,remove时value[i]被标记

public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
} public void removeAt(int index) {
if (mValues[index] != DELETED) {
mValues[index] = DELETED;
mGarbage = true;
}
}

这样标记的一个好处就是避免array频繁移动元素,对数据频繁的delete,removed,put操作时,在一定程度上可以提供效率,只有当执行gc时才进行回收处理

private void gc() {
int n = mSize;
int o = 0;
int[] keys = mKeys;
Object[] values = mValues; for (int i = 0; i < n; i++) {
Object val = values[i]; if (val != DELETED) {
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
} mGarbage = false;
mSize = o; // Log.e("SparseArray", "gc end with " + mSize);
}

对于常用的基本类型google已经有对应实现类,例如:SparseLongArray,SparseIntArray,SparseBooleanArray

对于key不是Integer,Long的基本类型情况,在api 19时可以使用ArrayMap,原理:与SparseArray类似处理,hash值存储在int []mHashes,value存储在Object[] mArray中,

ArrayMap实现:

public final class ArrayMap<K, V> implements Map<K, V> {
... /**
* @hide Special immutable empty ArrayMap.
*/
public static final ArrayMap EMPTY = new ArrayMap(true);
... /**
* Special hash array value that indicates the container is immutable.
*/
static final int[] EMPTY_IMMUTABLE_INTS = new int[0]; int[] mHashes;//存储key的hashCode
Object[] mArray;//存储key(偶数索引存储key)与value(奇数索引存储value)
int mSize; int indexOf(Object key, int hash) {//查找hash code索引位置
final int N = mSize; // Important fast case: if nothing is in here, nothing to look for.
if (N == 0) {
return ~0;
} int index = ContainerHelpers.binarySearch(mHashes, N, hash);//二分查找hash code的index // If the hash code wasn't found, then we have no entry for this key.
if (index < 0) {//未查找到
return index;
} // If the key at the returned index matches, that's what we want.
if (key.equals(mArray[index<<1])) {//查找到index,对应到mArray位置中指定的key index
return index;
} // Search for a matching key after the index.
int end;
for (end = index + 1; end < N && mHashes[end] == hash; end++) {
if (key.equals(mArray[end << 1])) return end;
} // Search for a matching key before the index.
for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
if (key.equals(mArray[i << 1])) return i;
} return ~end;
} private void allocArrays(final int size) {
if (mHashes == EMPTY_IMMUTABLE_INTS) {
throw new UnsupportedOperationException("ArrayMap is immutable");
}
......
mHashes = new int[size];//指定hash array size
mArray = new Object[size<<1];//mArray大小为size x2,因为这里使用一个array即存储key,又存储value
} public void ensureCapacity(int minimumCapacity) {//容量不足时扩容处理
if (mHashes.length < minimumCapacity) {
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(minimumCapacity);
if (mSize > 0) {
System.arraycopy(ohashes, 0, mHashes, 0, mSize);
System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
}
freeArrays(ohashes, oarray, mSize);
}
} @Override
public boolean containsKey(Object key) {
return indexOfKey(key) >= 0;
} public int indexOfKey(Object key) {
return key == null ? indexOfNull() : indexOf(key, key.hashCode());
} int indexOfValue(Object value) {//查找指定value的索引位置
final int N = mSize*2;
final Object[] array = mArray;
if (value == null) {//null分开查找,value存储在奇数位置,每次+2跳步
for (int i=1; i<N; i+=2) {
if (array[i] == null) {
return i>>1;
}
}
} else {
for (int i=1; i<N; i+=2) {
if (value.equals(array[i])) {
return i>>1;
}
}
}
return -1;
} @Override
public boolean containsValue(Object value) {
return indexOfValue(value) >= 0;
} @Override
public V get(Object key) {
final int index = indexOfKey(key);
return index >= 0 ? (V)mArray[(index<<1)+1] : null;
} @Override
public V remove(Object key) {
final int index = indexOfKey(key);
if (index >= 0) {
return removeAt(index);
} return null;
} public V removeAt(int index) {
final Object old = mArray[(index << 1) + 1];
if (mSize <= 1) {
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
freeArrays(mHashes, mArray, mSize);
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
mSize = 0;
} else {
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {//hash array长度>预定baseSize 2倍,元素个数小于3分之一时,进行容量缩减处理
// Shrunk enough to reduce size of arrays. We don't allow it to//减少内存占用,提供使用效率
// shrink smaller than (BASE_SIZE*2) to avoid flapping between
// that and BASE_SIZE.
final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2); if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n); mSize--;
if (index > 0) {
if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, index);
System.arraycopy(oarray, 0, mArray, 0, index << 1);
}
if (index < mSize) {
if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
+ " to " + index);
System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
(mSize - index) << 1);
}
} else {
mSize--;
if (index < mSize) {
if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
+ " to " + index);
System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
(mSize - index) << 1);
}
mArray[mSize << 1] = null;
mArray[(mSize << 1) + 1] = null;
}
}
return (V)old;
} @Override
public V put(K key, V value) {
final int hash;
int index;//根据key为null,不为null两种方式查找index
if (key == null) {
hash = 0;
index = indexOfNull();
} else {
hash = key.hashCode();
index = indexOf(key, hash);
}
if (index >= 0) {//查找到已有key,则替换新值,返回旧值
index = (index<<1) + 1;
final V old = (V)mArray[index];
mArray[index] = value;
return old;
} index = ~index;//等到插入位置
if (mSize >= mHashes.length) {//扩展array大小
final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
: (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n); final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n); if (mHashes.length > 0) {
if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
} freeArrays(ohashes, oarray, mSize);
} if (index < mSize) {//移位腾出指定位置空间,待插入位置
if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
+ " to " + (index+1));
System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
} mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
return null;
} /**
* Special fast path for appending items to the end of the array without validation.
* The array must already be large enough to contain the item.
* @hide
*/
public void append(K key, V value) {//快速插入指定key-value,当array容量够大,元素较少时使用,去掉了扩容,处理使用抛异常替代
int index = mSize;//在最后一个元素位置后执行添加
final int hash = key == null ? 0 : key.hashCode();
if (index >= mHashes.length) {//hash array边界检测
throw new IllegalStateException("Array is full");
}
if (index > 0 && mHashes[index-1] > hash) {
//hash array采用升序排序存储,即前面hash code <后面元素,当插入元素<最后元素时,说明需要进行元素移动
RuntimeException e = new RuntimeException("here");
e.fillInStackTrace();
Log.w(TAG, "New hash " + hash
+ " is before end of array hash " + mHashes[index-1]
+ " at index " + index + " key " + key, e);
put(key, value);//执行移动元素
return;
}
mSize = index+1;
mHashes[index] = hash;
index <<= 1;
mArray[index] = key;
mArray[index+1] = value;
}

ArraySet用来替换HashSet,实现与ArrayMap类似,只不过ArraySet实现Collection<E>接口,ArrayMap实现Map接口具体不在详细说明

HashMap结构是以array存储链表的头结点,找到头结点后在进行遍历查找,如下图:

从图示中可以看出当HashMap扩容容量过多,元素较少时会产生内存使用不平衡,即浪费不少内存,而ArrayMap则不会有过多的内存浪费问题,

虽然效率比Hashmap低一些但是内存使用率有很大提高,采用时间换空间方式解决移动设备内存问题。

summary:

1,android中采用用时间换空间的方式,平衡移动设备内存问题而使用SparseArray,ArrayMap替换HashMap

2,SparseArray使用int[],为Integer类型key存储,Object[]为value即双数组一一对应的方式实现存储,替HashMap<Integer,Object> ArrayMap使用int[] hash 存储hashcode,Object[] mArrays偶数索引存储key,奇数索引存储value巧妙方式存储keyPair

3,当key为int类型value为reference object可以使用SparseArray,value为基本类型时使用使用SparsexxxArray,当key为其它引用类型时使用ArrayMap<K, V>替换HashMap

Android 之Map容器替换 SparseArray,ArrayMap,ArraySet的更多相关文章

  1. Android内存优化(使用SparseArray和ArrayMap代替HashMap)

    在Android开发时,我们使用的大部分都是Java的api,比如HashMap这个api,使用率非常高,但是对于Android这种对内存非常敏感的移动平台,很多时候使用一些java的api并不能达到 ...

  2. Android内存优化(使用SparseArray和ArrayMap取代HashMap)

    在Android开发时,我们使用的大部分都是Java的api,比方HashMap这个api,使用率非常高,可是对于Android这样的对内存非常敏感的移动平台,非常多时候使用一些java的api并不能 ...

  3. 【C++】map容器的用法

    检测map容器是否为空: 1 #include <iostream> 2 #include<map> 3 #include<string> 4 using name ...

  4. map 容器的使用

    C++中map容器提供一个键值对容器,map与multimap差别仅仅在于multiple允许一个键对应多个值. 一.map的说明    1   头文件   #include   <map> ...

  5. 一种map容器遍历的方法

    遍历算法是一种很常见而且非常重要的算法,我们用map容器的时候可能用的比较多的是查找,我今天才第一次要用到遍历.下面举个例子就知道了. map<string,string> mp; str ...

  6. CSU 1113 Updating a Dictionary(map容器应用)

    题目链接:http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1113 解题报告:输入两个字符串,第一个是原来的字典,第二个是新字典,字典中的元素的格式为 ...

  7. map容器

    map容器一般用于对字符串进行编号,主要用于建图方面,例如把城市名按数字进行编号 #include"stdio.h" #include"string.h" #i ...

  8. Android google map 两点之间的距离

    在Android google map中,有时候会碰到计算两地的距离,下面的辅助类就可以帮助你计算距离: public class DistanceHelper { /** Names for the ...

  9. Tangled in Cables(Kruskal+map容器处理字符串)

    /** 题意:     给你两个城市之间的道路(无向图),求出需要的     电缆.如果大于所提供的,就输出Not enough ...     否则输出所需要的电缆长度.       输入:N (给 ...

  10. (6)Xamarin.android google map v2

    原文 Xamarin.android google map v2 Google Map v1已经在2013年的3月开始停止支持了,目前若要在你的Android手机上使用到Google Map,就必须要 ...

随机推荐

  1. kernel 启动流程

    一.概述 之前学习了uboot的启动流程,现在接着学习uboot的启动流程,关于 kernel 的启动流程分析的大佬也是很多的,这里还是通过流程的图的方式进行记录,为了像我一样的新手,直观的了解 ke ...

  2. 边框 display属性 盒子模型 浮动 溢出 定位 z-index

    目录 边框 隐藏属性 钓鱼网站 display visibility 盒子模型 调整方式 浮动 溢出 圆形头像的制作 定位 z-index属性 边框 /*border-left-width: 5px; ...

  3. JavaScript:控制跳转:break、continue与标签

    在循环结构中,经常需要使用关键字break和continue来控制跳转: 遇到break,就会跳出循环结构,执行循环体后面的代码: 遇到continue,就会跳出本次循环,进入下一次循环: 那么,假如 ...

  4. APICloud平台使用融云模块实现音视频通话实践经验总结分享

    需求概要:实现视频拨打.接听.挂断.视频界面大小窗口.点击小窗口实现大小窗口互换. 实现思路:一方拨打后,另一方要能收到相应事件,然后接听.接通后,渲染对方视频画面.那么己方视频画面什么时候渲染呢?对 ...

  5. 【RocketMQ】负载均衡源码分析

    RocketMQ在集群模式下,同一个消费组内,一个消息队列同一时间只能分配给组内的某一个消费者,也就是一条消息只能被组内的一个消费者进行消费,为了合理的对消息队列进行分配,于是就有了负载均衡. 接下来 ...

  6. 软件安装——idea的安装和使用

    Idea的安装和使用 一.下载和安装 下载步骤 官网下载地址:Download IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBr ...

  7. obj转换为gltf方法three.js一步一步来--性能优化超厉害的!!!!!超赞操作!!!Obj模型转Gltf模型并超强压缩!!!!!

    1.准备好模型文件table.obj和table.mtl 2.下载obj2gltf 下载地址https://github.com/AnalyticalGraphicsInc/obj2gltf 解压至文 ...

  8. Java自动装箱与拆箱

    装箱就是自动将基本数据类型转换为包装器类型(int-->Integer).调用方法:Integer的 valueOf(int) 方法 拆箱就是自动将包装器类型转换为基本数据类型(Integer- ...

  9. java 入门与进阶P-6.5+P-6.6

    字符串操作 字符串是对象,对它的所有操作都是通过" . " 这个运算符来进行的 字符串.操作 他表示对.左边的这个字符串做右边的那个操作 这里的字符串可以是变量也可以是常量 Str ...

  10. KStudio-Java程序连接KingbaseES数据库异常

    错误信息: --KStudio客户端工具错误信息 The conncetion attempt failed.Reason:connect time out --Java应用程序控制台日志 Cause ...