【实习周记】ArrayMap源码分析

一.概述

ArrayMap是Android专门针对内存优化而设计的,用于取代Java API中的HashMap数据结构。
内部通过两个数组实现,存储结构如下

二.主要方法的源码分析

1.重要字段

(1).private static final int BASE_SIZE = 4; // 容量增量的最小值
(2).private static final int CACHE_SIZE = 10; // 缓存数组的上限

(2).static Object[] mBaseCache; //用于缓存大小为4的ArrayMap
(3).static int mBaseCacheSize; // 记录大小为4的ArrayMap的缓存数量
(4).static Object[] mTwiceBaseCache; //用于缓存大小为8的ArrayMap
(5).static int mTwiceBaseCacheSize; // 记录大小为8的ArrayMap的缓存数量

(6).final boolean mIdentityHashCode; //默认false
(7).int[] mHashes; //由key的hashcode所组成的数组
(8).Object[] mArray; //由key-value对所组成的数组,是mHashes大小的2倍
(9).int mSize; //成员变量的个数

2.构造方法

创建ArrayMap对象时可以指定ArrayMap的长度,默认长度为0。

    public ArrayMap(int capacity, boolean identityHashCode) {mIdentityHashCode = identityHashCode;if (capacity < 0) {mHashes = EMPTY_IMMUTABLE_INTS;mArray = EmptyArray.OBJECT;} else if (capacity == 0) {mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;} else {//分配内存allocArrays(capacity);}mSize = 0;
}

(1).内存分配

private void allocArrays(final int size) {
if (size == (BASE_SIZE*2)) {
//当分配大小为8的对象,先查看缓存池synchronized (ArrayMap.class) {
// 当缓存池不为空时if (mTwiceBaseCache != null) { final Object[] array = mTwiceBaseCache;
//从缓存池中取出mArraymArray = array;            //将缓存池指向上一条缓存地址
mTwiceBaseCache = (Object[])array[0];
//从缓存中mHashesmHashes = (int[])array[1];//清空缓存  array[0] = array[1] = null;//缓存池大小减1mTwiceBaseCacheSize--;  return;}}//当分配大小为4的对象,原理同上} else if (size == BASE_SIZE) { synchronized (ArrayMap.class) {if (mBaseCache != null) {final Object[] array = mBaseCache;mArray = array;mBaseCache = (Object[])array[0];mHashes = (int[])array[1];array[0] = array[1] = null;mBaseCacheSize--;return;}}}// 分配大小除了4和8之外的情况,则直接创建新的数组mHashes = new int[size];mArray = new Object[size<<1];
}

3.put方法

public V put(K key, V value) {
//osize记录当前map大小final int osize = mSize; final int hash;int index;if (key == null) {hash = 0;index = indexOfNull();} else {//获取hashCodehash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();//采用二分查找法,从mHashes数组中查找值等于hash的keyindex = indexOf(key, hash); }//若index大于零,说明在mHashes中key存在,所以用新的value覆盖旧的valueif (index >= 0) {//index的2倍+1所对应的元素存在相应value的位置index = (index<<1) + 1;  final V old = (V)mArray[index];mArray[index] = value;return old;}//当index<0,说明是新的键值对,对index进行加一取相反数作为新的键值对的位置
index = ~index;
//当mSize大于或等于mHashes数组长度时,需要扩容if (osize >= mHashes.length) { 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); //由于ArrayMap并非线程安全的类,不允许并行,如果扩容过程其他线程
//mSize则抛出异常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);}//释放内存freeArrays(ohashes, oarray, osize); }//当需要插入的位置不在数组末尾时,需要将index位置后的数据通过拷贝往后移动一位if (index < osize) {System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);//这里,index+1比index大1,但是<<1操作扩大二倍后,就相差2了//所以不是从index<<1到(index+2)<<1System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);}if (CONCURRENT_MODIFICATION_EXCEPTIONS) {if (osize != mSize || index >= mHashes.length) {throw new ConcurrentModificationException();}}//将hash、key、value添加相应数组的位置,数据个数mSize加1mHashes[index] = hash;mArray[index<<1] = key;mArray[(index<<1)+1] = value;mSize++; return null;
}

(1).二分查找

indexOfNull和indexOf方法内部主要通过binarySearch实现,并对返回值进行了一些处理。

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; }}return ~lo;
}

(2).内存释放

private static void freeArrays(final int[] hashes, final Object[] array, final int size) {//当释放的是大小为8的对象if (hashes.length == (BASE_SIZE*2)) {  synchronized (ArrayMap.class) {// 当大小为8的缓存池的数量小于10个,则将其放入缓存池if (mTwiceBaseCacheSize < CACHE_SIZE) { //array[0]指向原来的缓存池array[0] = mTwiceBaseCache;  //array[1]存储hash数组array[1] = hashes;//清空其他数据for (int i=(size<<1)-1; i>=2; i--) {array[i] = null;  }//mTwiceBaseCache指向新加入缓存池的arraymTwiceBaseCache = array; //缓存池大小加1mTwiceBaseCacheSize++; }}//当释放的是大小为4的对象,原理同上} else if (hashes.length == BASE_SIZE) {  synchronized (ArrayMap.class) {if (mBaseCacheSize < CACHE_SIZE) {array[0] = mBaseCache;array[1] = hashes;for (int i=(size<<1)-1; i>=2; i--) {array[i] = null;}mBaseCache = array;mBaseCacheSize++;}}}
}

4.get方法

public V get(Object key) {//根据key找到index,并返回相应值final int index = indexOfKey(key);return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}

(1).indexOfKey

public int indexOfKey(Object key) {return key == null ? indexOfNull(): indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
}

可以看出indexOfKey内部通过调用indexOfNull和indexOf实现,核心也是通过二分查找实现。

5.remove方法

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

remove内部调用indexOfKey,把key转换为index,最后委派给removeAt处理。

(1).removeAt

public V removeAt(int index) {final Object old = mArray[(index << 1) + 1];final int osize = mSize;final int nsize;//当被移除的是ArrayMap的最后一个元素if (osize <= 1) {  //释放内存freeArrays(mHashes, mArray, osize);mHashes = EmptyArray.INT;mArray = EmptyArray.OBJECT;nsize = 0;} else {nsize = osize – 1;if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {final 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);}//再将最后一个位置设置为nullmArray[nsize << 1] = null;mArray[(nsize << 1) + 1] = null;}}if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {throw new ConcurrentModificationException();}mSize = nsize; //大小减1return (V)old;
}

三.总结

1.缓存机制——内存分配(allocArrays)和内存释放(freeArrays)

(1).allocArrays触发时机:

当执行ArrayMap的构造函数的情况
当执行removeAt()在满足容量收紧机制的情况
当执行ensureCapacity()在当前容量小于预期容量的情况下, 先执行allocArrays,再freeArrays
当执行put()在容量满的情况下, 先执行allocArrays, 再执行freeArrays

(2).freeArrays()触发时机:

当执行removeAt()移除最后一个元素的情况
当执行clear()清理的情况
当执行ensureCapacity()在当前容量小于预期容量的情况下, 先执行allocArrays,再freeArrays
当执行put()在容量满的情况下, 先执行allocArrays, 再执行freeArrays

2.扩容机制——容量扩张(put)和容量收缩(removeAt)

(1).容量扩张:put

触发:当mSize大于或等于mHashes数组长度时扩容
当map个数满足条件 osize<4时,则扩容后的大小为4;
当map个数满足条件 4<= osize < 8时,则扩容后的大小为8;
当map个数满足条件 osize>=8时,则扩容后的大小为原来的1.5倍;
可见ArrayMap大小在不断增加的过程,size的取值为4,8,12,18,27,40,60,……

(2).容量收缩:removeAt

触发:当数组内存的大小大于8,且已存储数据的个数mSize小于数组空间大小的1/3时,收缩
当mSize<=8,则设置新大小为8;
当mSize> 8,则设置新大小为mSize的1.5倍。
在数据较大的情况下,当内存使用量不足1/3的情况下,内存数组会收紧50%。

【实习周记】ArrayMap源码分析相关推荐

  1. ArrayMap 源码分析

    文章目录 概述 主要属性 构造方法 put indexOfNull() indexOf remove get 参考 概述 ArrayMap 是 Android 的 API,它和 Java 的 Hash ...

  2. ArrayMap源码分析

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

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

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

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

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

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

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

  6. NEO从源码分析看NEOVM

    2019独角兽企业重金招聘Python工程师标准>>> 0x00 前言 这篇文章是为下一篇<NEO从源码分析看UTXO转账交易>打前站,为交易的构造及执行的一些技术基础做 ...

  7. 视频教程-经典Vue从入门到案例到源码分析教程(含资料)-Vue

    经典Vue从入门到案例到源码分析教程(含资料) 张长志技术全才.擅长领域:区块链.大数据.Java等.10余年软件研发及企业培训经验,曾为多家大型企业提供企业内训如中石化,中国联通,中国移动等知名企业 ...

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

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

  9. 视频教程-Spring底层源码分析-Java

    Spring底层源码分析 鲁班学院-子路老师曾就职于谷歌.天猫电商等多家互联网公司,历任java架构师.研发经理等职位,参与并主导千万级并发电商网站与后端供应链研发体系搭建,多次参与电商大促活动技术保 ...

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

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

最新文章

  1. nyoj 931 货物运输(Floyd输出路径)
  2. java对接ldap_如何使用Java操作LDAP之LDAP连接(一)
  3. 40_自定义泛型方法及其应用
  4. 第七章 假设检验(2)
  5. qt 进度条最小_QT:圆形进度条设计
  6. php获取微信图片访问权限,微信小程序访问图片出现403,图片防盗链的解决办法...
  7. tsql 正则_sql里的正则表达式
  8. Mac安装Anaconda
  9. pytorch函数之nn.Linear
  10. Docker使用小结(四)发布镜像
  11. 配置 mysql/mariadb sphinxSE 错误解决 1st column must be bigint to accept 64-bit DOCID
  12. ffmpeg 中 swscale 的用法
  13. Linux 常用软件分类
  14. jsp实现简单购物车页面
  15. ICE 3.7.4 实现客户服务端hello world
  16. Python第九章 文件系统
  17. 细说pc端微信扫码登录
  18. 解决Attribute 'transaction-manager' is not allowed to appear
  19. bugku:简单的套娃
  20. 干货|什么是字节码?字节码扩展名是什么?

热门文章

  1. 韦东山嵌入式Linux学习——015 Nand Flash(2)-Nand Flash编程实现读地址信息
  2. Python 爬取zw年鉴
  3. Java菜鸟教程 if语句和switch语句
  4. Ubuntu18.04下WizNote为知笔记源码编译安装
  5. 计算机打字速录,速录员打字口诀有哪些
  6. 力软(.NET)敏捷开发框架,让开发变的更简单
  7. 002649:bootdo项目改造计划
  8. bing的翻译API 国际化
  9. python3视频教学_Python3入门基础视频课程(下)
  10. 太强了,头发丝完整保留!华盛顿大学研究员开源的实时视频抠图工具