前言

今天我们来了解一下与HashMap类似的数据结构SparseArray,并分析下它的源码实现。在分析源码的过程中,我们带着以下几个问题来看。

  • SparseArray底层数据结构是什么?
  • SparseArray如何通过key获得对应数组下标
  • SparseArray的扩容机制是什么?
  • SparseArray与HashMap有什么区别?

核心字段

  private static final Object DELETED = new Object();private boolean mGarbage = false;private int[] mKeys;private Object[] mValues;private int mSize;
复制代码

首先我们先来了解一下SparseArray类中声明的变量都是做什么的,如下

  • DELETED 表示删除状态(后面详细说明)
  • mGarbage 表示是否GC
  • mKeys 表示Key数组,SparseArray中专门存取Key的数组
  • mValues 表示Values数组,SparseArray中专门存取Value的数组
  • mSize 表示数组实际存储的元素大小

小结

通过了解以上几个变量,我们可以大概知道SparseArray底层是通过两个数组来实现的,一个int数组来存取Key,一个Object数组来存取Value。

构造方法

 public SparseArray() {this(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;}
复制代码

可以看出SparseArray默认容量为10,然后我们来看一下put()方法。

put()

 public void put(int key, E value) {//通过key获取对应的数组位置int i = ContainerHelpers.binarySearch(mKeys, mSize, key);//若i>=0,说明key存在,直接赋值if (i >= 0) {mValues[i] = value;} else {//此时i<0,然后对i取反i = ~i;//如果i<mSize并且i对应的Value已经是标记位删除状态,那么就复用这个位置if (i < mSize && mValues[i] == DELETED) {mKeys[i] = key;mValues[i] = value;return;}//如果需要GC并且mSize大于等于mKeys数组的长度,那么进行GC,并且重新查找iif (mGarbage && mSize >= mKeys.length) {gc();i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);}//最后分别插入key和value,并且判断是否需要扩容mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);mSize++;}}
复制代码

通过分析put()方法源码,我们可以知道SparseArray添加元素可以分为以下几步。

  • 获取key对应的数组位置i
  • 判断i是否大于0
    • 如果i>0,说明key存在,直接赋值
    • 如果i<0,说明key不存在
      • 如果i<mSize并且i对应的Value已经是标记位删除状态,那么就复用这个位置
      • 如果需要GC并且mSize大于等于mKeys数组的长度,那么进行GC,并且重新查找i
      • 最后分别插入key和value,并且判断是否需要扩容

查找key对应的数组下标

 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;  }
复制代码

可以看出通过二分查找的方式来获得key在数组中对应的下标,最后如果没找到,会对lo取反并返回。

GC相关方法

 private void gc() { //表示实际大小int n = mSize;int o = 0;int[] keys = mKeys;Object[] values = mValues;//遍历所有元素,如果某个元素标记为DELETED,那么就删除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++;}}//设为false,表示不需要GCmGarbage = false;mSize = o;}
复制代码

扩容机制

在插入key和value时调用了GrowingArrayUtils的insert()方法,然后我们来看一下SparseArray里如何进行扩容的,源码如下。

public static int[] insert(int[] array, int currentSize, int index, int element) {assert currentSize <= array.length;//不需要扩容if (currentSize + 1 <= array.length) {//从index以后的元素向后移一位System.arraycopy(array, index, array, index + 1, currentSize - index);//在index对应的位置赋值array[index] = element;return array;}//需要扩容,创建一个新数组int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));//将array数组中从0到index之间的元素(不包括index对应的元素)复制到新数组中System.arraycopy(array, 0, newArray, 0, index);newArray[index] = element;//再将index之后的元素复制到新数组中System.arraycopy(array, index, newArray, index + 1, array.length - index);return newArray;}public static int growSize(int currentSize) {//如果currentSize小于等于4,就为8,否则乘以2return currentSize <= 4 ? 8 : currentSize * 2;}
复制代码

通过分析以上方法的源码,我们知道了SparseArray的扩容机制,主要步骤如下。

  • 创建一个新数组,数组容量根据currentSize来判断
  • 将旧数组中,index之前的数组元素复制到新数组中
  • 对新数组中的index对应的元素进行赋值
  • 将旧数组中,index之后的数组元素复制到新数组中

删除操作

我们再来看一下SparseArray的删除方法,通过查看源码可以发现有多个删除方法,我们一个个的来看一下。

remove(int key)

public void remove(int key) {delete(key);}
public void delete(int key) {//通过key获得对应的数组下标iint i = ContainerHelpers.binarySearch(mKeys, mSize, key);if (i >= 0) {//如果mValues[i]没有被标记为DELETED,那么就进行标记,并设置mGarbage为true,表示需要GCif (mValues[i] != DELETED) {mValues[i] = DELETED;mGarbage = true;}}}
复制代码

可以看出该方法是通过key来进行删除,主要分为以下几步。

  • 获得key对应的数组下标
  • 如果i>=0,判断mValues[i]是否被标记为DELETED
    • 如果没有被标记为DELETED,那么就进行标记,并设置mGarbage为true,表示需要GC

removeAt(int index)

 public void removeAt(int index) {if (mValues[index] != DELETED) {mValues[index] = DELETED;mGarbage = true;}}
复制代码

通过index找到对应的位置进行删除操作。

查找

get(int key)

 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);if (i < 0 || mValues[i] == DELETED) {return valueIfKeyNotFound;} else {return (E) mValues[i];}}复制代码

从以上源码可以看出SparseArray通过key来查找对应的元素,主要有以下几步。

  • 获取key对应的数组下标
  • 如果i小于0或者mValues[i]已经标记为DELETED,返回valueIfKeyNotFound,也就是null
  • 否则就返回mValues[i]

SparseArray与HashMap区别

性能

在性能方面,SparseArray适合存储少量的数据。如果存储大量的数据,在进行扩容时,会有比较大的性能开销。

底层数据结构

SparseArray是由两个数组组成,一个数组负责存储key(int类型),一个数组负责存储value,类似于key为int类型的HashMap。

删除

SparseArray的删除操作先将要删除的数组元素标记为DELETED,然后当存储相同key对应的value时,可以进行复用。

总结

SparseArray在内存方面上比HashMap消耗更少,所以对于数据不大的情况下,优先使用SparseArray,毕竟在Android中内存是比较重要的。

转载于:https://juejin.im/post/5ca31d4bf265da30bf15ccdb

Java填坑系列之SparseArray相关推荐

  1. MySQL填坑系列--Linux平台下MySQL区分大小写问题

    问题引入 大家好,我是软件大盗(道),下面开始我们的<MySQL填坑系列>. 笔者最近又在MySQL的边缘试探,然后,试探着,试探着就报错了. 情景还原 书接上文,系统连接数据库时报错:找 ...

  2. java linkedlist 更新_Java填坑系列之LinkedList

    前言 今天我们通过分析LinkedList的源码,来学习一下它内部是如何添加.查询以及删除元素的.同时在阅读源码时,也要思考以下几个问题. LinkedList的底层数据结构是什么? 与ArrayLi ...

  3. android小米定位,Android填坑系列:在小米系列等机型上放开定位权限后的定位请求弹框示例...

    背景 近期因实际项目需要,在特定操作下触发定位请求,取到用户位置及附近位置. 问题: 经初步选型,最终决定接入百度定位,按照百度定位SDK Android文档,接入过程相对顺利. 但随后发现,在小米系 ...

  4. Android填坑系列:Android JSONObject 中对key-value为null的特殊处理

    在与服务端通过JSON格式进行交互过程中,不同版本的JSON库在对于key-value为null情况上的处理不同. Android自带的org.json对key-value都要求不能为null,对于必 ...

  5. 填坑系列- 微软用687亿购买暴雪公司,上车元宇宙,元宇宙概念起飞。

    1月18日消息,微软今日宣布,计划以每股 95 美元的现金收购游戏开发和互动娱乐内容发行商动视暴雪,其溢价达到了45%,交易总金额约为 687 亿美元.这笔交易将使动视暴雪的价值达到 700 亿美元, ...

  6. win10双系统ubuntu安装+卸载driver+cuda+cudnn+anaconda+mxnet编译(好文推荐+实际动手填坑系列)

    写在 2021.8.4 的话,最近刚弄的这些,许多东西还记得,如果大家有什么问题,可以在评论区留言,我应该能回答上来一些. 安装参考: (root空间分配那儿要注意,我一开始是分配20G,结果还在安装 ...

  7. 日常填坑系列-Maven-1、Maven的安装及setting.xml配置

    一.下载Maven 官方网站https://mirrors.tuna.tsinghua.edu.cn/apache/maven/,选择想要下载的版本 2.3.5.3版本的http://mirrors. ...

  8. 支付宝小程序填坑系列 之 自定义组件无法显示

    最近换了台新机器,重装了支付宝小程序IDE,发布了一个新版本的支付宝小程序,于是奇异的事情发生了... 之前使用很正常的支付宝小程序,突然间无法登录了,于是一顿排查猛如虎... 最后发现是卡在了登录环 ...

  9. Caffe2填坑系列(10)----编译成功,在python中使用时报”key already registered. Offending key: ImageInput“

    原因:ImageInput这个Op被注册了多次,我在编写自己的.cc文件时,是以image_input.cc为模板,前面我都改成了自己Op的名字,包括REGISTER_CPU_OPERATOR(),O ...

最新文章

  1. b-blkid查看磁盘设备文件系统类型
  2. 我的Rails笔记(1)
  3. NSUserDefaults 简介,使用 NSUserDefaults 存储自定义对象
  4. redis客户端连接数量_实战解析无所不知的Redis拓展应用——Info,进阶学习,无所不能...
  5. 编写jmeter测试用例_Jmeter | 实现接口自动化设计说明
  6. linux那些事之 page translation(硬件篇)
  7. 兔子问题JAVA编程题
  8. Tuxedo 8.110gR3 开发环境的安装与配置
  9. 【问题解决方案】anaconda-python在cmd-pip安装requests后依然提示No module named requests
  10. 软件工程-东北师大站-第六次作业PSP
  11. 【EF】EF框架 Code First Fluent API
  12. QueryDSL基本操作demo
  13. 2021最全HW蓝队指导手册
  14. kali之永恒之蓝使用流程(操作全套步骤)
  15. 探秘金山隐私保险箱 (解密出加密的数据)
  16. 台式计算机可以连接手机WiFi么,台式机能连wifi吗
  17. Win10、Win11打开远程桌面连接方法
  18. 微信小程序记事本+后台管理系统
  19. APS审核经验+审核资料汇总——计算机科学与技术专业上海德语审核
  20. Python 蓝桥杯试题 基础练习 数列排序

热门文章

  1. WEBGIS体系和OGC标准
  2. JavaScript的预编译及执行顺序
  3. 二、创作网站 (三) Creating custom content types
  4. fork()与pid
  5. python 二维list取列
  6. 发文越多,影响力会越大吗?
  7. 教练如何引导学员在线报名
  8. 使用 Nginx 代理 Socket.io/WebSocket 及 负载均衡配置
  9. %@ INCLUDE FILE=%与JSP:INCLUDE PAGE=/区别
  10. 100c之53:说谎族和诚实族