文章目录

  • 概述
  • 主要属性
  • 构造方法
  • put
    • indexOfNull()
    • indexOf
  • remove
  • get
  • 参考

概述

ArrayMap 是 Android 的 API,它和 Java 的 HashMap 相比,虽然在查找效率上不如 HashMap(HashMap 查找的时间复杂度是 O(1), ArrayMap 查找的时间复杂度是 O(logn)),但是 ArrayMap 的空间消耗更小,它内部使用数组存储 hash 和键值对,不用花费多余的指针维护链表或树结构,扩容的时候只扩容 1.5 倍,并且元素小于一定量后还会收缩数组来回收空间。所以在数据量不大并且需要节省空间的时候可以考虑 ArrayMap。

下面就从源码的角度看下 ArrayMap 的实现原理,主要看它的增删查方法。

主要属性

    final boolean mIdentityHashCode;    // 是否利用 System.identityHashCode(key) 获取唯一 hashCode/*** 保存每个元素的 hash,长度为容量大小* mHashes 是有序的,按照元素的 hash 值由小到大排序,hash 值相同的元素,先插入的排在前面* 由于 mHashes 是有序的,所以使用二分法查找元素的位置,时间复杂度为 O(logn)*/int[] mHashes; /*** 保存键值对,长度为容量的两倍* 根据 key 的 hash 在 mHashes 的存放位置 index,可以确定键值对在 mArray 的存放位置* key 存放在 index << 1 处,value 存放在 (index << 1) + 1 处*/Object[] mArray;  int mSize;          // 当前键值对数量

构造方法

ArrayMap()

    public ArrayMap() {this(0, false);}

默认构造方法,初始容量为 0,第一次插入元素时再扩容

ArrayMap(int)

    public ArrayMap(int capacity) {this(capacity, false);}

指定初始容量

ArrayMap(int, boolean)

    public ArrayMap(int capacity, boolean identityHashCode) {mIdentityHashCode = identityHashCode;if (capacity < 0) { // 容量小于 0,创建一个不可变的 ArrayMapmHashes = EMPTY_IMMUTABLE_INTS;mArray = EmptyArray.OBJECT;} else if (capacity == 0) { // 容量等于 0,创建一个空 ArrayMapmHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;} else {// 容量大于 0,根据指定容量初始化 mHashes 和 mArray 数组// 如果容量是 4 或 8 的话,会查看之前是否有缓存的数组,有就使用缓存allocArrays(capacity);  }mSize = 0;}

指定初始容量和 mIdentityHashCode

put

    public V put(K key, V value) {final int osize = mSize;final int hash;       // 存放 key 的 hashint index;  // 通过该 index 得到 hash 在 mHashes 的存放位置以及键值对在 mArray 的存放位置if (key == null) {hash = 0;       // null 的 hash 为 0index = indexOfNull();   // 得到 null 的存放位置} else {hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();  // 得到 key 的 hashindex = indexOf(key, hash);        // 得到 key 的 index}// 存在相同的 key,更新 value,返回旧的 valueif (index >= 0) {index = (index<<1) + 1;final V old = (V)mArray[index];mArray[index] = value;return old;}index = ~index;// 判断是否要扩容if (osize >= mHashes.length) {// n 为新数组长度// 如果旧数组长度小于 4,那么新数组长度为 4// 如果旧数组长度大于等于 4 并且小于 8,那么新数组长度为 8// 如果旧数组长度大于等于 8,新数组长度是原来的 1.5 倍final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1)): (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);final int[] ohashes = mHashes;final Object[] oarray = mArray;allocArrays(n);        // 创建新数组if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}// 移动旧元素到新数组中if (mHashes.length > 0) {System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);System.arraycopy(oarray, 0, mArray, 0, oarray.length);}// 如果旧的 mHashes 数组长度为 4 或 8,会缓存起来freeArrays(ohashes, oarray, osize);}// 如果元素要插入到 mHashes 和 mArray 的中间,那么在该元素后面的先后移,给新元素腾出位置if (index < osize) {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();}}// 新元素插入到 mHashes 和 mArray 的合适位置mHashes[index] = hash;mArray[index<<1] = key;mArray[(index<<1)+1] = value;mSize++;return null;}

首先得到 key 的 hash 值,null 的 hash 值为 0,对于非 null 的 key,默认情况下使用 key 的 hashCode() 作为它的 hash 值,如果在创建 ArrayMap 时指定 mIdentityHashCode 为 true,则 key 的 hash 值为 System.identityHashCode(key)。

然后使用二分法在 mHashes 数组查找 key 的 hash 所在位置索引 index。如果 index >= 0,说明存在相同元素,更新 value,返回旧的 value。否则就要根据 index 将新元素插入到合适位置。

在插入前,判断是否要扩容,当键值对数量大于等于 mHashes 数组的长度时,进行扩容。扩容过程和 ArrayList 相似,得到新数组长度,创建新数组,并将旧数组的元素复制过去。新数组长度和旧数组长度有关:

如果旧数组长度小于 4,那么新数组长度为 4;如果旧数组长度大于等于 4 并且小于 8,那么新数组长度为 8;如果旧数组长度大于等于 8,新数组长度是原来的 1.5 倍。

插入元素的时候,如果不是插入到最后,那么需要先将位于插入位置及其后面的元素后移,腾出位置,然后将 hash 和键值对插入到 mHashes 和 mArray 的相应位置。

得到 index 使用的是 indexOfNull()indexOf 方法:

indexOfNull()

    int indexOfNull() {final int N = mSize;// 当前集合为空,返回if (N == 0) {return ~0;}// 二分查找,找到 mHashes 数组中元素为 0 的索引,不存在返回一个负数int index = binarySearchHashes(mHashes, N, 0);// 找到的 index 小于 0,说明之前没有存储过 null,直接返回 indexif (index < 0) {return index;}// 根据 index 找到 mArray 中的对应 key,如果该 key 为 null,说明 index 有效,返回 indexif (null == mArray[index<<1]) {return index;}// 下面两个循环是在出现 hash 冲突的时候,从左右两边寻找其他 hash 值相同的元素,看下是否有相同的元素int end;for (end = index + 1; end < N && mHashes[end] == 0; end++) {if (null == mArray[end << 1]) return end;}for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {if (null == mArray[i << 1]) return i;}// 找不到相同的元素,说明 null 是新的元素,返回一个负数,指示 null 的存放位置return ~end;}

indexOf

    int indexOf(Object key, int hash) {final int N = mSize;// 当前集合为空,返回if (N == 0) {return ~0;}// 二分查找,找到 mHashes 数组中元素为 hash 的索引,不存在返回一个负数int index = binarySearchHashes(mHashes, N, hash);// 找到的 index 小于 0,说明之前没有存储过该 hash,直接返回 indexif (index < 0) {return index;}// 根据 index 找到 mArray 中的对应 key,如果该 key 相同,说明存在相同元素,返回 indexif (key.equals(mArray[index<<1])) {return index;}// 下面两个循环是在出现 hash 冲突的时候,从左右两边寻找其他 hash 值相同的元素,看下是否有相同的元素int end;for (end = index + 1; end < N && mHashes[end] == hash; end++) {if (key.equals(mArray[end << 1])) return end;}for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {if (key.equals(mArray[i << 1])) return i;}// 找不到相同的元素,说明插入的是新元素,返回一个负数,指示新元素的存放位置return ~end;}

remove

    public V remove(Object key) {final int index = indexOfKey(key);if (index >= 0) {return removeAt(index);}return null;}

先找到 key 的 index,然后调用 removeAt 方法

    public V removeAt(int index) {final Object old = mArray[(index << 1) + 1];   // 保存旧的 valuefinal int osize = mSize;final int nsize;if (osize <= 1) {// 只有一个元素,删除后数组为空final int[] ohashes = mHashes;final Object[] oarray = mArray;mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;freeArrays(ohashes, oarray, osize);nsize = 0;} else {nsize = osize - 1;// 当容量大于 8,并且键值对数量小于容量的 1/3 时if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {// 键值对数量大于 8,n 为键值对数量的 1.5 倍// 键值对数量小于等于 8,n 为 8final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);final int[] ohashes = mHashes;final Object[] oarray = mArray;// 收缩数组allocArrays(n);if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}// 将元素复制到新数组上if (index > 0) {System.arraycopy(ohashes, 0, mHashes, 0, index);System.arraycopy(oarray, 0, mArray, 0, index << 1);}if (index < nsize) {System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,(nsize - index) << 1);}} else {if (index < nsize) {System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,(nsize - index) << 1);}// 将就的键值对置空mArray[nsize << 1] = null;mArray[(nsize << 1) + 1] = null;}}if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}// 更新键值对数量,并返回旧 valuemSize = nsize;return (V)old;}

在删除元素后,如果发现当前容量大于 8,并且剩余的键值对数量小于容量的 1/3 时,将收缩数组,如果键值对数量小于等于 8,那么新数组长度为 8;如果键值对数量大于 8,那么新数组长度为键值对数量的 1.5 倍。

get

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

它的 get 方法比较简单,得到 key 的 hash 在 mHashes 的 index 后,如果 index 大于等于 0,通过索引可以直接从 mArray 数组得到 value。如果 index 小于 0,说明不存在该元素,返回 null。

参考

  • 面试必备:ArrayMap源码解析

ArrayMap 源码分析相关推荐

  1. ArrayMap源码分析

    成员变量说明 private static final boolean CONCURRENT_MODIFICATION_EXCEPTIONS = true; //多线程操作判断,值为 true 时一些 ...

  2. 【转】HashMap,ArrayMap,SparseArray源码分析及性能对比

    HashMap,ArrayMap,SparseArray源码分析及性能对比 jjlanbupt 关注 2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43 Array ...

  3. Framework 源码解析知识梳理(5) startService 源码分析

    一.前言 最近在看关于插件化的知识,遇到了如何实现Service插件化的问题,因此,先学习一下Service内部的实现原理,这里面会涉及到应用进程和ActivityManagerService的通信, ...

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

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

  5. Android技术栈--HashMap和ArrayMap源码解析

    1 总览 WARNING!!:本文字数较多,内容较为完整并且部分内容难度较大,阅读本文需要较长时间,建议读者分段并耐心阅读. 本文会对 Android 中常用的数据结构进行源码解析,包括 HashMa ...

  6. Framework学习之路(一)—— UI绘制深入源码分析

    Framework学习之路(一)-- UI绘制深入源码分析 本篇为笔者对Android SDK 33版本的UI绘制入口进行追踪的过程,主要作笔记作用.由于笔者经验尚浅,水平也有限,所以会存在很多不足的 ...

  7. Android技术栈(五)HashMap(包括红黑树)与ArrayMap源码解析

    1 总览 本文会对 Android 中常用HashMap(有红黑树)和ArrayMap进行源码解析,其中 HashMap 源码来自 Android Framework API 28 (JDK=1.8) ...

  8. 不再害怕面试问ArrayMap一文完全看懂Android ArrayMap源码解析

    作者:VIjolie 前言 ArrayMap是谷歌推出的在安卓等设备上用于替代HashMap的数据结构,和HashMap相比,具有更高的内存使用率,因此适合在Android等内存较为紧张的移动设备,下 ...

  9. 【安卓 R 源码】 bindService 源码分析

    使用bindService主要分两种情形: 1. Service的调用者client与Service在同一个App中: 2. Service的调用者client是App1中的一个Activity,而S ...

最新文章

  1. 一: 建立Vue sampleproject
  2. Java学习总结:52(Java网络编程)
  3. 量子纠缠真的很怪异吗?
  4. 对现代软件工程开发看法
  5. [dp][前缀和] Jzoj P5907 轻功(qinggong)
  6. [BZOJ4027][HEOI2015]兔子与樱花(贪心)
  7. Hibernate之映射
  8. 【转】三、QT例子-打开一个图片并且显示
  9. Splunk学习心得
  10. 如何使用WordPress美化网站
  11. c语言万能源代码,经典C语言源代码
  12. maven下载安装配置3.5.2
  13. 16、 基于STM32单片机WIFI控制家电插座
  14. 如何处理偶现的 Bug
  15. 一款熊猫游戏java_狂热的熊猫_JAVA游戏免费版下载_7723手机游戏[www.7723.cn]
  16. 僵尸网络 DDoS 攻击活动分析
  17. matlab做瀑布图,Matlab画瀑布图,福利叶变换,频谱图代码
  18. Android 系统(213)---如何内置多张静态壁纸(图片)到系统中
  19. MiniDao-PE精简版
  20. 装了双系统怎么删除一个

热门文章

  1. 你的数字人处在哪个阶段?
  2. MySQL快捷键注释
  3. 2014年创业致富的佛香公司-武汉吉香缘gs 公司
  4. 日常JAVA基础面试题集8(含答案)
  5. Mac 下解决键盘和触摸板失灵
  6. 浅谈cocos2dx渲染方式
  7. 计算机网络期末总复习知识点
  8. 艾司博讯:拼多多开店选择那些类目好
  9. 找不到客户采购邮箱?其实你只需要精通谷歌搜索
  10. C语言字符串查找基础----strchr()、strrchr()、strpbrk()、strstr()