Java填坑系列之SparseArray
前言
今天我们来了解一下与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相关推荐
- MySQL填坑系列--Linux平台下MySQL区分大小写问题
问题引入 大家好,我是软件大盗(道),下面开始我们的<MySQL填坑系列>. 笔者最近又在MySQL的边缘试探,然后,试探着,试探着就报错了. 情景还原 书接上文,系统连接数据库时报错:找 ...
- java linkedlist 更新_Java填坑系列之LinkedList
前言 今天我们通过分析LinkedList的源码,来学习一下它内部是如何添加.查询以及删除元素的.同时在阅读源码时,也要思考以下几个问题. LinkedList的底层数据结构是什么? 与ArrayLi ...
- android小米定位,Android填坑系列:在小米系列等机型上放开定位权限后的定位请求弹框示例...
背景 近期因实际项目需要,在特定操作下触发定位请求,取到用户位置及附近位置. 问题: 经初步选型,最终决定接入百度定位,按照百度定位SDK Android文档,接入过程相对顺利. 但随后发现,在小米系 ...
- Android填坑系列:Android JSONObject 中对key-value为null的特殊处理
在与服务端通过JSON格式进行交互过程中,不同版本的JSON库在对于key-value为null情况上的处理不同. Android自带的org.json对key-value都要求不能为null,对于必 ...
- 填坑系列- 微软用687亿购买暴雪公司,上车元宇宙,元宇宙概念起飞。
1月18日消息,微软今日宣布,计划以每股 95 美元的现金收购游戏开发和互动娱乐内容发行商动视暴雪,其溢价达到了45%,交易总金额约为 687 亿美元.这笔交易将使动视暴雪的价值达到 700 亿美元, ...
- win10双系统ubuntu安装+卸载driver+cuda+cudnn+anaconda+mxnet编译(好文推荐+实际动手填坑系列)
写在 2021.8.4 的话,最近刚弄的这些,许多东西还记得,如果大家有什么问题,可以在评论区留言,我应该能回答上来一些. 安装参考: (root空间分配那儿要注意,我一开始是分配20G,结果还在安装 ...
- 日常填坑系列-Maven-1、Maven的安装及setting.xml配置
一.下载Maven 官方网站https://mirrors.tuna.tsinghua.edu.cn/apache/maven/,选择想要下载的版本 2.3.5.3版本的http://mirrors. ...
- 支付宝小程序填坑系列 之 自定义组件无法显示
最近换了台新机器,重装了支付宝小程序IDE,发布了一个新版本的支付宝小程序,于是奇异的事情发生了... 之前使用很正常的支付宝小程序,突然间无法登录了,于是一顿排查猛如虎... 最后发现是卡在了登录环 ...
- Caffe2填坑系列(10)----编译成功,在python中使用时报”key already registered. Offending key: ImageInput“
原因:ImageInput这个Op被注册了多次,我在编写自己的.cc文件时,是以image_input.cc为模板,前面我都改成了自己Op的名字,包括REGISTER_CPU_OPERATOR(),O ...
最新文章
- b-blkid查看磁盘设备文件系统类型
- 我的Rails笔记(1)
- NSUserDefaults 简介,使用 NSUserDefaults 存储自定义对象
- redis客户端连接数量_实战解析无所不知的Redis拓展应用——Info,进阶学习,无所不能...
- 编写jmeter测试用例_Jmeter | 实现接口自动化设计说明
- linux那些事之 page translation(硬件篇)
- 兔子问题JAVA编程题
- Tuxedo 8.110gR3 开发环境的安装与配置
- 【问题解决方案】anaconda-python在cmd-pip安装requests后依然提示No module named requests
- 软件工程-东北师大站-第六次作业PSP
- 【EF】EF框架 Code First Fluent API
- QueryDSL基本操作demo
- 2021最全HW蓝队指导手册
- kali之永恒之蓝使用流程(操作全套步骤)
- 探秘金山隐私保险箱 (解密出加密的数据)
- 台式计算机可以连接手机WiFi么,台式机能连wifi吗
- Win10、Win11打开远程桌面连接方法
- 微信小程序记事本+后台管理系统
- APS审核经验+审核资料汇总——计算机科学与技术专业上海德语审核
- Python 蓝桥杯试题 基础练习 数列排序