最近在写framework层的系统服务,发现Android 12中用来去重注册监听的map都是用的ArrayMap,因此仔细研究了ArrayMap的原理。


目录

一. ArrayMap概述

二. ArrayMap源码解析

1.主要包含的成员变量

2.构造函数

3. public boolean containsKey(Object key)

4. public int indexOfKey(Object key)

5. public boolean containsValue(Object value)

6. public int indexOfValue(Object value)

7.核心get方法

8.核心put方法

9. public V remove(Object key)

10. public V removeAt(int index)

11. public void clear()

12.public void erase()

13. public K keyAt(int index)

14. public V valueAt(int index)

15. 二分查找的实现

16. System.arraycopy()


一. ArrayMap概述

ArrayMap是一个key-value的数据结构,它比HashMap有更高的内存效率。

它映射到两个数组结构:一个整数数组mHashes,保存key的hash code;一个对象数据mArray,顺序保存key-value。

它可以避免为push到map的item创建额外的对象,而且它试图控制这些数组大小的增长(因为增长数据大小只需要复制数组中的item即可,不需要重建hash map)。

它不适用于大量数据的存储,通常会比HashMap慢,因为查找需要二分法,而且添加和删除操作需要对数组中entries进行相应的插入和删除(通常数组中间元素的插入和删除效率很低,因为会导致插入或者删除位置之后的元素整体移动,请参考后边的remove和put函数解析),对于包含数百个元素的容器来说,性能差异不显著,小于50%。

因为ArrayMap是为了更好的平衡内存,和大部多数Java标准containers不同,当删除item时它会缩小数组。目前你无法控制这种(shrinking)缩小——如果您设置了capacity然后删除一个item,他可能会减少capacity来匹配目前大小。将来明确设置capacity应该会关闭这种激进的shriking行为。

二. ArrayMap源码解析

public final class ArrayMap<K, V> implements Map<K, V> {}

1.主要包含的成员变量

private static final int BASE_SIZE = 4;
private static final int CACHE_SIZE = 10;private final boolean mIdentityHashCode;
int[] mHashes;
Object[] mArray;
int mSize;
private MapCollections<K, V> mCollections;

注意:mSize表示数组mHashes的大小,而mArray的大小为2*mSize。mHashes中升序存放key的hash值,mArray中顺序存储了key和value。若key的hash在mHashes的位置索引为index,key在mArray中的位置keyIndex= index<<1 = index*2,value在mArray中的位置valueIndex= (index<<1) + 1 = index*2 + 1。

ArrayMap的两个数组存储结构如下:


2.构造函数

public ArrayMap()public ArrayMap(int capacity)public ArrayMap(int capacity, boolean identityHashCode)public ArrayMap(ArrayMap<K, V> map)

3. public boolean containsKey(Object key)

判断Array中是否存在该key,如果该key存在则返回true,否则false。该方法调用indexOfKey(key) >= 0来实现。


4. public int indexOfKey(Object key)

如果该key在数组中,则返回其index,否则返回一个负数。

它核心就是根据该key是否是null,来判断调用indexOf(Object key, int hash)还是indexOfNull(),该这两个方法中使用二分查找key。


5. public boolean containsValue(Object value)

如果该value存在则返回true,否则返回false。该方法调用indexOfValue(value) >= 0来实现。


6. public int indexOfValue(Object value)

如果该Object存在,则返回其index,否则返回-1。

该方法的查找效率没有indexOfKey()快,因为该方法是线性查找数组mArray,分为object = null和object = null的情况。


7.核心get方法

public V get(Object key)
public V get(Object key) {final int index = indexOfKey(key);return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}

注意:<<代表左移运算符,<<1表示左移1位,低位补0,相当于乘以2。


8.核心put方法

public V put(K key, V value)

给ArrayMap中添加一个新的value,如果key已存在,则会用参数中的value覆盖其原来对应的value值。返回值是给定key对应的老的value,如果该key不存在,则返回null。

该方法很长主要分为以下部分

  • 根据key是否为null,调用indexOf()或者indexOfNull()方法二分查找该key是否存在。
  • 如果mHashes数组中该key存在,则用新的value值覆盖旧的value。
public V put(K key, V value) {final int osize = mSize;final int hash;int index;//查找key是否存在,若存在,则返回其indexif (key == null) {hash = 0;index = indexOfNull();} else {hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();index = indexOf(key, hash);}//key存在,用新value替换就的value,覆盖旧值if (index >= 0) {index = (index<<1) + 1;final V old = (V)mArray[index];mArray[index] = value;return old;}
...
}
  • 如果mHashes数组中该key不存在,则二分查找返回的iindex取反就是要该key要插入的位置。(请参考后边的二分查找算法)
  • 给mHashes数组和mArray数组扩容,如果ArrayMap现有数组长度osize > (BASE_SIZE*2),则扩容到3*osize,否则扩容为8或者4。

public V put(K key, V value) {...index = ~index;if (osize >= mHashes.length) {//osize=mSize,插入前mHashes数组的长度,BASE_SIZE=4final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1)): (osize >= 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 (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}if (mHashes.length > 0) {if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);System.arraycopy(oarray, 0, mArray, 0, oarray.length);}freeArrays(ohashes, oarray, osize);}...
}
  • 如果要加入的元素index在mHashes数组中间,则把mHashes和mArray中index及之后的元素整体后移一位,即使用System.arraycopy()实现。
  • 然后把要put的key和value分别插入对应下标的数组中。
public V put(K key, V value) {...if (index < osize) {if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index)+ " to " + (index+1));//要插入的key的index在mHashes数组中间,则需要将mHashes中index及之后的元素整体后移一位。System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);}if (CONCURRENT_MODIFICATION_EXCEPTIONS) {if (osize != mSize || index >= mHashes.length) {throw new ConcurrentModificationException();}}//把要push的key和value分别加入mHashes数组以及mArray数组中mHashes[index] = hash;mArray[index<<1] = key;mArray[(index<<1)+1] = value;//数组长度加1mSize++;return null;
}

以上就是ArrayMap的整个put方法过程,因为新增元素涉及到插入位置后的两个数组元素的整体后移(复制),这就是ArrayMap顺序存储效率慢的原因。


9. public V remove(Object key)

删除给定key对应的元素,该方法会同时删除mHashes中和mArray数组中的元素,并引起Shrink数组。

如果该key存在,则会返回删除的该key对应的value,否则返回null。

该方法先调用indexOfKey()二分查找该key对应的index,如果index>=0,则调用removeAt()实现删除。


10. public V removeAt(int index)

该方法是ArrayMap删除数据的核心,我们解析下代码:

  • 已知要删除的index,可以获取该index对应的value值,仅通过一行代码就可以实现

    final Object old = mArray[(index << 1) + 1];
  • 判断当前数组的长度mSize<=1,如果true,直接把mHashes和mArray数组赋值为EmptyArray,然后释放空间。这是一次shrink。
public V removeAt(int index) {if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {// The array might be slightly bigger than mSize, in which case, indexing won't fail.// Check if exception should be thrown outside of the critical path.throw new ArrayIndexOutOfBoundsException(index);}final Object old = mArray[(index << 1) + 1];final int osize = mSize;final int nsize;if (osize <= 1) {// Now empty.if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");final int[] ohashes = mHashes;final Object[] oarray = mArray;mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;freeArrays(ohashes, oarray, osize);nsize = 0;}
...if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}mSize = nsize;return (V)old;
}
  • 如果mHashes数组长度>8或者mSize<mHashes.length/3,则会触发shrink数组,并删除该元素。
  • 否则删除该index对应的mHashes和mArray中的元素。
System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, (nsize - index) << 1);

11. public void clear()

清空arraymap,会释放所有的存储空间。该方法中将数组mHashes和mArray赋值EmptyArray,并释放存储空间。


12.public void erase()

该方法只将mArray数组中的元素全部置为null,不释放ArrayMap的存储空间。


13. public K keyAt(int index)

获取指定index对应的Key值。

public K keyAt(int index) {if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {// The array might be slightly bigger than mSize, in which case, indexing won't fail.// Check if exception should be thrown outside of the critical path.throw new ArrayIndexOutOfBoundsException(index);}return (K)mArray[index << 1];
}

14. public V valueAt(int index)

获取指定index对应的value值

public V valueAt(int index) {if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {// The array might be slightly bigger than mSize, in which case, indexing won't fail.// Check if exception should be thrown outside of the critical path.throw new ArrayIndexOutOfBoundsException(index);}return (V)mArray[(index << 1) + 1];
}

15. 二分查找的实现

ArrayMap的二分查找是调用的ContainerHelper工具类中的binarySearch()方法。

static int binarySearch(int[] array, int size, int value) {int lo = 0;int hi = size -1;while (lo <= hi) {final int mid = (lo + li) >>> 1;final int minVal = array[mid];if (midVal < value) {lo = mid + 1;} else if (midVal > value) {hi = mid - 1;} else {return mid;}  }return ~lo;
}

16. System.arraycopy()

private static void arraycopy(int[] src, int srcPos, int[] dst, int dstPos, int length)

ArrayMap 源码的详细解析相关推荐

  1. collection集合 多少钱_Java 集合(2)-- Iterator接口源码超级详细解析

    一.iterator接口介绍 iterator接口,也是集合大家庭中的一员.和其他的Map和Collection接口不同,iterator 主要是为了方便遍历集合中的所有元素,用于迭代访问集合中的元素 ...

  2. java activerecord.db_JFinal 源码超详细解析之DB+ActiveRecord

    我记得以前有人跟我说,"面试的时候要看spring的源码,要看ioc.aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇 ...

  3. java activerecord.db_JFinal 源码超详细解析之DB+ActiveRecord-java-火龙果软件工程

    我记得以前有人跟我说,"面试的时候要看spring的源码,要看ioc.aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇 ...

  4. 怎么证明建立了存储过程_【Filecoin源码仓库全解析】第七章:了解PoRep与PoSt并参与复制证明游戏

    欢迎大家来到第七章,经过前章<[Filecoin源码仓库全解析]第六章:如何单机部署多节点集群及矿池设计思路>的介绍,我们分享了如何在单机部署多节点集群的知识以及矿池设计的一些思路. 我们 ...

  5. (02)Cartographer源码无死角解析-(32) LocalTrajectoryBuilder2D::AddRangeData()→点云的体素滤波

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录 ...

  6. (01)ORB-SLAM2源码无死角解析-(55) 闭环线程→计算Sim3:总体流程讲解ComputeSim3()

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  7. (01)ORB-SLAM2源码无死角解析-(57) 闭环线程→计算Sim3:理论推导(2)求解R,使用四元数

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  8. (01)ORB-SLAM2源码无死角解析-(62) BA优化(g2o)→追踪线程:Optimizer::PoseOptimization→仅位姿优化

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

  9. (01)ORB-SLAM2源码无死角解析-(63) BA优化(g2o)→局部建图线程:Optimizer::LocalBundleAdjustment→位姿与地图点优化

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (0 ...

最新文章

  1. 根据当前时间查询上月26号的日期 本月月25号的日期
  2. JRuby大捷:ThoughtWorks宣布Mingle发布在即
  3. 什么是常驻内存式的开发模式?_“直播+”模式下的直播系统开发需要注意什么问题?...
  4. 【渝粤教育】广东开放大学 Photoshop 图像处理 形成性考核 (24)
  5. DateTime和字符串转换问题
  6. Spring Boot中使用MyBatis注解配置详解
  7. mysql 大小写敏感设置_MySQL 中的大小写敏感设置
  8. robotac属于a类还是b类_所得税A类和B类的区别,什么样的属于B类??
  9. 安装HDFS过程中Browse Directory报错
  10. 大工18春计算机原理在线作业答案,大工13春《计算机组成原理》在线作业2 及答案...
  11. Android SDK的下载与安装
  12. 二次拟合r方_excel曲线拟合中的决定系数R平方是如何求出来的?
  13. 惠普HP ProDesk 400 G2 加装BCM94352HMB网卡
  14. 链塔年会圆桌论坛实录
  15. 关于三角函数级数的一个重要结论+和差化积+积化和差
  16. js返回浏览器的顶部
  17. 根据ID3算法给出游玩的决策树的实战案例
  18. Linux rm -rf 之rm: cannot remove `linux': Device or resource busy
  19. 聚观早报 | ChatGPT 停止 Plus 付费;李子柒油管广告收益登顶热搜
  20. Java,第一次作业——六边形面积

热门文章

  1. 简单几行命令让pip升级
  2. 常见室内观赏植物的管理技术大全
  3. Blinker点灯科技绑定GitHub增加设备数量到10个
  4. 云计算大数据学习中心作业8
  5. php 银行支付通道_接口--php对接农行网上支付平台-b2b
  6. Maven setting.xml 配置
  7. 去哪儿旅行携程旅行淘宝旅行移动端产品分析报告
  8. 阅读Learning towards Minimum Hyperspherical Energy笔记
  9. 五、 try_files的使用
  10. python热图_python – 使用matplotlib中的3D数据生成热图