本来接下来应该分析MessageQueue了,可是我这几天正好在实际开发中又再次用到了SparseArray(之前有用到过一次,那次只是

大概浏览了下源码,没做深入研究),于是在兴趣的推动下,花了些时间深入研究了下,趁着记忆还是新鲜的,就先在这里分析了。

MessageQueue的分析应该会在本周末给出。

  和以往一样,首先我们来看看关键字段和ctor:

    private static final Object DELETED = new Object();
private boolean mGarbage = false; private int[] mKeys;
private Object[] mValues;
private int mSize; /**
* Creates a new SparseArray containing no mappings.
*/
public SparseArray() {
this(10);
} /**
* Creates a new SparseArray containing no mappings that will not
* require any additional memory allocation to store the specified
* number of mappings. If you supply an initial capacity of 0, the
* sparse array will be initialized with a light-weight representation
* not requiring any additional array allocations.
*/
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = ContainerHelpers.EMPTY_INTS;
mValues = ContainerHelpers.EMPTY_OBJECTS;
} else {
initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
mKeys = new int[initialCapacity];
mValues = new Object[initialCapacity];
}
mSize = 0;
}

常量DELETED对象用来标记删除,如果某个位置的值是DELETED则表示这个位置没对应的元素(被删除了);

mGarbage在删除操作中会被设置(置为true),表示接下来需要清理压缩了(compacting);

mkeys,mValues分别表示SparseArray的内部存储,即分别是key、value的存储数组;

mSize表示有效的key-value对的数目;

接下来来看ctor,无参版本会调用带参数的版本并且传递10,表示初始容量。我们可以看到如果initialCapacity=0的话,mkeys、mValues

会分别被初始化为EMPTY的东西,实际是长度为0的数组,不会产生内存分配;否则初始化2个数组为具体的大小,这里要留意一点就是代码

里并没有直接用你传递进来的值,而是调用了initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);来我们顺便看一下:

  public static int idealIntArraySize(int need) {
return idealByteArraySize(need * 4) / 4;
}   public static int idealByteArraySize(int need) {
for (int i = 4; i < 32; i++)
if (need <= (1 << i) - 12)
return (1 << i) - 12; return need;
}

Android认为它这个方式是比较ideal的,比客户端直接传的值要好,所以经过这一堆运算后,initialCapacity很可能已经不是你当初传的值了。

最后都设置了mSize为0。

  接下来看2个依据key来得到value的方法:

  /**
* Gets the Object mapped from the specified key, or <code>null</code>
* if no such mapping has been made.
*/
public E get(int key) {
return get(key, null);
} /**
* Gets the Object mapped from the specified key, or the specified Object
* if no such mapping has been made.
*/
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}

看2个参数的get方法,你可以提供一个fallback的值,表示如果没找到key对应的值就返回你提供的这个值;SparseArray内部是通过二分

查找算法来search key的,顺便看看:

    // This is Arrays.binarySearch(), but doesn't do any argument validation.
static int binarySearch(int[] array, int size, int value) {
int lo = 0;
int hi = size - 1; while (lo <= hi) {
final int mid = (lo + hi) >>> 1;
final int midVal = array[mid]; if (midVal < value) {
lo = mid + 1;
} else if (midVal > value) {
hi = mid - 1;
} else {
return mid; // value found
}
}
return ~lo; // value not present
}

如源码所说,这个版本和java.util.Arrays.java里的实现一样,只是省略了参数检查。二分查找大家大学都接触过,应该印象都比较深刻,

这里只说一点即最后没找到时的返回值~lo。如方法的doc所说,没找到的情况下会返回一个负值,那到底返回哪个负值呢,-1行不?其实

这里的~lo(取反)就相当于-(lo+1)(参看Arrays.binarySearch的实现)。为什么要这样做,因为我们不仅想表示没找到,还想返回

更多信息,即这个key如果要插进来应该在的位置(外面的代码只需要再次~即取反就可以得到这个信息)。接下来回到刚才的get方法,

明白了这里使用的二分查找这个方法就非常简单明了了。get内部通过binarySearch的返回值来做判断,如果是负的或i>=0但是位置i已经

被标记为删除了则返回valueIfKeyNotFound,否则直接返回(E) mValues[i]。

  接下来看一组delete/remove方法:

  /**
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
} /**
* Alias for {@link #delete(int)}.
*/
public void remove(int key) {
delete(key);
} /**
* Removes the mapping at the specified index.
*/
public void removeAt(int index) {
if (mValues[index] != DELETED) {
mValues[index] = DELETED;
mGarbage = true;
}
} /**
* Remove a range of mappings as a batch.
*
* @param index Index to begin at
* @param size Number of mappings to remove
*/
public void removeAtRange(int index, int size) {
final int end = Math.min(mSize, index + size);
for (int i = index; i < end; i++) {
removeAt(i);
}
}

delete(key)和remove(key)一样,都是先通过二分查找来找这个key,如果不存在则do nothing否则如果这个位置还没被标记为删除

则标记之,顺便设置mGarbage(之前提到过删除之类的操作都会设置这个值,表示接下来需要执行清理压缩操作了)。注意这里数组

的不同处理,没有直接移动数组元素(压缩处理)来删除这个key,而仅仅是标记操作。这2个方法都是通过key来进行操作,有时你可能

想基于index来执行某些操作,下面的removeAt(index)和removeAtRange(index, size)就是这样的方法,处理也都是如果位置i没标记

删除则标记之,并设置mGarbage的值。只是在removeAtRange中对上限做了保险处理即end = Math.min(mSize, index+size);

防止数组越界。

  接下来该打起来精神,睁大眼睛了,让我们一起来看看SparseArray的关键,我叫它清理压缩,上代码:

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

gc方法只有在mGarbage设置的时候才有可能调用,其总体思想是把靠后的有效元素(即没被删除的)往前(reusedIndex的位置)提,

从而达到清理压缩的目的。我们仔细分析下,首先初始化一些接下来要用到的值,这里特别留意下reusedIndex(源码中叫o,实在不好

理解,我按自己的理解重新命名了),初始化为0,指向第一个元素。接下来是遍历mValues数组的for循环,其内部是如果当前元素val是

DELETED则啥也不做,接着看下一个元素(注意此时reusedIndex不做任何改动);否则当前是个有效的元素,执行1,2:

1. reusedIndex不是当前的index则应该把当前元素拷贝到reusedIndex的位置(key和value都复制),当前位置的value清空(置null);

2. reusedIndex往前+1(每遇到一个有效元素),准备下次循环。

结束遍历之后reset mGarbage(因为刚刚做过了,暂时不需要了),更新mSize为reusedIndex(因为每遇到一个有效元素,reusedIndex

就+1,所以它的值就是新的mSize)。

  看完了删除我们来看put相关的方法,代码如下:

  /**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
*/
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) {
mValues[i] = value;
} else {
i = ~i; if (i < mSize && mValues[i] == 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);
} if (mSize >= mKeys.length) {
int n = ArrayUtils.idealIntArraySize(mSize + 1); int[] nkeys = new int[n];
Object[] nvalues = new Object[n]; // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
System.arraycopy(mValues, 0, nvalues, 0, mValues.length); mKeys = nkeys;
mValues = nvalues;
} if (mSize - i != 0) {
// Log.e("SparseArray", "move " + (mSize - i));
System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
} mKeys[i] = key;
mValues[i] = value;
mSize++;
}
}

put的实现一般都是有这个key则覆盖原先的value,否则新插入一个。这里的也一样,首先先调用二分查找算法去找key,如果找到了

(i>=0)则直接更新mValues[i]为新值;否则就得想办法插入这对key-value,具体做法是:

1. 首先把二分查找的返回值取反(~),拿到要插入的位置信息;

2. 接下来看如果这个位置i在有效范围内(即不需要额外分配空间)且此位置被标记为删除了,则这就是个可以重用的位置,也就是我们

要找的插入位置,插入之,完事(return);

3. 不然的话(也就是说i超出了有效范围或者没超出但位置i是有效元素),如果mGarbage被设置了且mSize >= mKeys.length,

表示该执行gc算法了,执行之,接着重新利用二分查找算法确定下key的新位置(因为index可能变了)。

接下来,如果mSize >= mKeys.length(即key数组不够用了或者说到了要分配更多内存的临界点了),利用mSize计算新的capacity,

int n = ArrayUtils.idealIntArraySize(mSize + 1); 分配新的数组,将旧内容拷过去,接着将mKeys, mValues指向新分配

的数组。然后看下i如果在有效范围内,则应该把现在从位置i开始的元素移动到位置i+1处(把i的位置腾出来给新put的元素让位),

以此类推,到mSize-1的位置为止(mSize-1位置的元素移动到mSize的位置)。最后将put的元素插在第i的位置上,mSize++,

最终结束整个put操作。

  长舒一口气啊,接着来看看几个非常简单的方法,

  /**
* Returns the number of key-value mappings that this SparseArray
* currently stores.
*/
public int size() {
if (mGarbage) {
gc();
} return mSize;
} /**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*
* <p>The keys corresponding to indices in ascending order are guaranteed to
* be in ascending order, e.g., <code>keyAt(0)</code> will return the
* smallest key and <code>keyAt(size()-1)</code> will return the largest
* key.</p>
*/
public int keyAt(int index) {
if (mGarbage) {
gc();
} return mKeys[index];
} /**
* Given an index in the range <code>0...size()-1</code>, returns
* the value from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*
* <p>The values corresponding to indices in ascending order are guaranteed
* to be associated with keys in ascending order, e.g.,
* <code>valueAt(0)</code> will return the value associated with the
* smallest key and <code>valueAt(size()-1)</code> will return the value
* associated with the largest key.</p>
*/
@SuppressWarnings("unchecked")
public E valueAt(int index) {
if (mGarbage) {
gc();
} return (E) mValues[index];
} /**
* Given an index in the range <code>0...size()-1</code>, sets a new
* value for the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public void setValueAt(int index, E value) {
if (mGarbage) {
gc();
} mValues[index] = value;
} /**
* Returns the index for which {@link #keyAt} would return the
* specified key, or a negative number if the specified
* key is not mapped.
*/
public int indexOfKey(int key) {
if (mGarbage) {
gc();
} return ContainerHelpers.binarySearch(mKeys, mSize, key);
} /**
* Returns an index for which {@link #valueAt} would return the
* specified key, or a negative number if no keys map to the
* specified value.
* <p>Beware that this is a linear search, unlike lookups by key,
* and that multiple keys can map to the same value and this will
* find only one of them.
* <p>Note also that unlike most collections' {@code indexOf} methods,
* this method compares values using {@code ==} rather than {@code equals}.
*/
public int indexOfValue(E value) {
if (mGarbage) {
gc();
} for (int i = 0; i < mSize; i++)
if (mValues[i] == value)
return i; return -1;
} /**
* Removes all key-value mappings from this SparseArray.
*/
public void clear() {
int n = mSize;
Object[] values = mValues; for (int i = 0; i < n; i++) {
values[i] = null;
} mSize = 0;
mGarbage = false;
}

size()方法返回当前有效的key-value对的数目,值得注意的是在其内部都会有条件的执行gc方法,因为之前的操作可能导致mGarbage

被设置了,所以必须执行下清理压缩才能返回最新的值;keyAt(index)和valueAt(index)提供了遍历SparseArray的方法,如下:

  for (int i = 0; i < sparseArray.size(); i++) {
System.out.println("key = " + sparseArray.keyAt(i) + ", value = " + sparseArray.valueAt(i));
}

同样和size()方法一样其内部也都会有条件的执行gc方法,注意你传递的index必须在[0, size()-1]之间,否则会数组越界。

setValueAt让你像使用数组一样设置某个位置处的值,同样会有条件的执行gc方法。

indexOfKey通过二分查找来search key的位置;indexOfValue在整个mValues数组中遍历查找value,有则返回对应的index

否则返回-1。

clear()方法清空了mValues数组,重置了mSize,mGarbage,但请注意并没动mKeys数组;

  最后我们看下SparseArray的最后一个方法append,源码如下:

  /**
* Puts a key/value pair into the array, optimizing for the case where
* the key is greater than all existing keys in the array.
*/
public void append(int key, E value) {
if (mSize != 0 && key <= mKeys[mSize - 1]) {
put(key, value);
return;
} if (mGarbage && mSize >= mKeys.length) {
gc();
} int pos = mSize;
if (pos >= mKeys.length) {
int n = ArrayUtils.idealIntArraySize(pos + 1); int[] nkeys = new int[n];
Object[] nvalues = new Object[n]; // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
System.arraycopy(mValues, 0, nvalues, 0, mValues.length); mKeys = nkeys;
mValues = nvalues;
} mKeys[pos] = key;
mValues[pos] = value;
mSize = pos + 1;
}

看源码我们知道,当mSize != 0且key<=SparseArray中最大的key时,则直接调用put方法;否则当key比现存所有的key都大,

这种情况下我们执行的只是put方法中i==mSize的部分(小分支,所以说是个优化相比直接调用put方法)。

  目前为止,SparseArray类的所有关键代码都已经分析完毕,希望对各位平时的开发有所帮助(由于本人水平有限,欢迎批评指正)。

Android源码分析之SparseArray的更多相关文章

  1. Android源码分析-全面理解Context

    前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...

  2. Android源码分析(六)-----蓝牙Bluetooth源码目录分析

    一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...

  3. Android源码分析(十七)----init.rc文件添加脚本代码

    一:init.rc文件修改 开机后运行一次: chmod 777 /system/bin/bt_config.sh service bt_config /system/bin/bt_config.sh ...

  4. Android源码分析(十六)----adb shell 命令进行OTA升级

    一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...

  5. Android源码分析(十五)----GPS冷启动实现原理分析

    一:原理分析 主要sendExtraCommand方法中传递两个参数, 根据如下源码可以知道第一个参数传递delete_aiding_data,第二个参数传递null即可. @Override pub ...

  6. Android源码分析(十四)----如何使用SharedPreferencce保存数据

    一:SharedPreference如何使用 此文章只是提供一种数据保存的方式, 具体使用场景请根据需求情况自行调整. EditText添加saveData点击事件, 保存数据. diff --git ...

  7. Android源码分析(十三)----SystemUI下拉状态栏如何添加快捷开关

    一:如何添加快捷开关 源码路径:frameworks/base/packages/SystemUI/res/values/config.xml 添加headset快捷开关,参考如下修改. Index: ...

  8. Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果

    一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...

  9. Android源码分析(十一)-----Android源码中如何引用aar文件

    一:aar文件如何引用 系统Settings中引用bidehelper-1.1.12.aar 文件为例 源码地址:packages/apps/Settings/Android.mk LOCAL_PAT ...

随机推荐

  1. jQuery简单实例

    jQuery 选择器 $(this).hide() 演示 jQuery 的 hide() 函数,隐藏当前的 HTML 元素. $("p").hide() 演示 jQuery 的 h ...

  2. 找出链表中倒数第 k 个结点

    /* 题目:输入一个单向链表,输出该链表中倒数第 k 个结点.链表的倒数第 0 个结点为链表 的尾指针. 链表结点定义如下: struct node { int data; struct node * ...

  3. Mina、Netty、Twisted一起学(四):定制自己的协议

    在前面的博文中,介绍一些消息分割的方案,以及MINA.Netty.Twisted针对这些方案提供的相关API.例如MINA的TextLineCodecFactory.PrefixedStringCod ...

  4. 网络通信分享(一):数字签名,数字证书,https通信,数据加密

    加密算法: 一:对称加密算法 在对称加密算法中,加密使用的密钥和解密使用的密钥是相同的.也就是说,加密和解密都是使用的同一个密钥.因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道, ...

  5. Frame - 快速创建高品质的 Web 应用原型

    Frame 是一个让你够能够快速创建高品质的网站或应用程序产品原型的工具.你上传的图片将被包裹在真实的设备环境中.它是一个用于创建宣传资料的专业工具.Frame 完全免费供给商业和个人使用.他们也正探 ...

  6. Django项目--web聊天室

    需求 做一个web聊天室,主要练习前端ajax与后台的交互: 一对一聊天和群组聊天 添加用户为好友 搜索并添加群组 管理员可以审批用户加群请求,群管理员可以有多个,群管理员可以删除,添加禁言群友 与聊 ...

  7. 重构第8天:使用委托代替继承(Replace Inheritance with Delegation)

    理解:根本没有父子关系的类中使用继承是不合理的,可以用委派的方式来代替. 详解:我们经常在错误的场景使用继承.继承应该在仅仅有逻辑关系的环境中使用,而很多情况下却被使用在达到方便为目的的环境中. 看下 ...

  8. 【分享】深入浅出WPF全系列教程及源代码

    来源:http://blog.csdn.net/yishuaijun/article/details/21345893 本来想一篇一篇复制的.但是考虑到别人已经做过了,就没有必要了吧,就给大家一个目录 ...

  9. 引入js和css文件的总结

    1.用script标签引入javascript时,浏览器对于javascript的加载某些是并行的,某些是串行的,如IE8,Chorme2和firefox3都是串行加载的. 2.charset编码也就 ...

  10. div与>div区别小结

    两者之间的区别:例如div span得到的是div下所有的span元素,而div>span则是取得的div下第一级的span元素. 示例代码如下: <!DOCTYPE html> & ...