您当前的位置:首页 > 电脑百科 > 程序开发 > 移动端 > Android

Android数据结构之SparseArray

时间:2019-09-27 14:24:04  来源:  作者:

 

一、前言

Android开发中,当需要存储键值对时,一般都是用JAVA自带的HashMap。但是细心的同学可能会发现,有时候如果实际的HashMap的key-vaule中的key是Integer时,AndroidStudio会提示一个warnning,具体是说推荐使用SparseArray替代HashMap:

Android数据结构之SparseArray

 

虽然说warnning不影响实际功能,但是有个warnning放在那里总让人不爽。因为是lint静态扫描报的,可以用@SuppressLint("UseSparseArrays")忽略掉。但是既然google特地出了这么一个类用来替代key为Integer的HashMap,那是不是真的比HashMap更好用?

二、优缺点

It is intended to be more memory efficient than using a HashMap to map Integers to Objects, both because it avoids auto-boxing keys and its data structure doesn't rely on an extra entry object for each mApping.

源码的注释除了提到SparseArray有节约自动装箱开销的优点外,还提到SparseArray因为少了需要Map.Entry<K, V>作为辅助的存储结构引入的内存开销。

因为Map<K, V>的泛型声明,key必须是Integer不能是int,所以确实会带来自动装箱的问题。

这两个优点都是让SparseArraymore memory efficient的,这是因为SparseArray的诞生就是针对某些Android设备内存比较紧张的情况的。

但是一般来说,SparseArray是比Hashmap慢的,在数据集大小只有上百个的时候,差别不大。

Android数据结构之SparseArray

 

三、使用

不管是HashMap还是SpareArray,他们的作用都是维护一组逻辑上的key-value的对应关系。那么,在这组关系上最常做的操作就是存和取了。

存/取

HashMap的存操作和取操作分别对应方法put(K key, V value)和get(Object key),大概用过HashMap的没有不知道这两个方法的。而SpareArray对的两个方法分别是put(int key, E value)和get(int key),和HashMap的方法看起来几乎没有区别,key为Integer的hashmap的相关代码可以无缝换成SpareArray。

Android数据结构之SparseArray

 

遍历

SpareArray的遍历要稍微麻烦些。

首先先建立一个概念,SparseArray执行put的时候其实是按照key的大小有序插入的。简单来说,SparseArray维护了各个键值对的排序关系,具体的规则是以key升序排列。所以不同于HashMap只能通过key查找value,Sparse还能通过index查找value(或者key),方法是valueAt(int index)(或者keyAt(int index))。这里的index是升序排序中键值对的位置,index是SparseArray相比Map多出来的概念,看了后面的源码实现分析就好理解了。

拿上面的代码举例,put了key为100和200的两个键值对,size为2,200-"firstValue"这对key-value对在index 0的位置,100-"secondValue"这对键值对在index 1的位置。顺序是根据key的大小排的,跟put的先后顺序无关。所以valueAt(0)拿到的是"secondValue"。

具体的遍历代码:

Android数据结构之SparseArray

 


Android数据结构之SparseArray

 

四、实现细节

和hashmap比较

大致讲下hashmao的原理。hashmap使用key的hashcode来决定entry的存放位置,解决hash冲突使用的开散列方法,所以hashmap的底层数据结构看起来是一个链表的数组,链表的节点是包含了key和value的Entry类。看起来就像下图:

Android数据结构之SparseArray

 

而SparseArray的底层数据结构更简单,只有int[] mKeys和Object[] mValues两个数组。那这里就有个问题了:不同于HashMap专门用一个Entry的类存放key跟value,SpareArray里key和value分别存放在两个数组里,那key和value是怎么对应上的?

答案就是,是根据index对应的,mKeys和mValues中index一样的key和value就是相互对应的。所以SparseArray实际存储的数据看起来是这样的:

Android数据结构之SparseArray

 

HashMap中基于Entry建立的key-value对应关系会导致Entry占用内存,而sparse基于index的对应关系是逻辑的,节省下了Entry类的内存,这又是SparseArray的一个优点。

存/取

前面提到,SparseArray中实际存储的数据是有序的。那么保证有序的关键就在每次的存和删操作中:在原本有序的情况下,保证存和删操作后还是有序的。

看存操作的实现,注释说明了关键点:

Android数据结构之SparseArray

 

所以保证所有存储的数据都是有序排列的关键就在于每次插入的时候如何确定插入的新数据插入的位置。上面看到每次确定实际插入的位置是基于二分查找确定的。举个例子:

  • 原先的数据是mIndexs = {1, 4, 6, 8},size为4
  • 要插入的key是7
  • 第一次二分查找返回的index是-3,说明现在的数据中没有这个index,这个key应该被插入index为3的位置
  • 调用GrowingArrayUtils.insert将7插入index为3的位置,实际会引发mKeys扩容到8,原先的key8往右移
  • 最后的数据是mIndexs = {1, 4, 6 ,7, 8},保持了有序

其实实际插入数据的过程类似于优化后的插入排序,确定了插入的位置后把这个位置后面的数据移动一位,然后把新数据放入空出来的位置。

取的过程很简单,同样是根据二分查找找到如果有这个key的话它应该在哪个位置,如果找到的index<0反过来就证明了没有这个key:

Android数据结构之SparseArray

 

HashMap的取操作在hash分桶时时间复杂度为O(1),但是在发生hash冲突的时候最后会在链表中顺序查找,而SparseArray的取操作完全依赖于二分查找,时间复杂度理论上总是O(nlogn),没有hash冲突导致访问慢的问题;不过HashMap的hash冲突一般很少,总体来说SparseArray总是比HashMap慢些;而且二分查找的时间复杂度也决定了SparseArray不适合大量数据的场景。

删/gc

SparseArray删除数据是通过delete(int key)方法删除的。在删除一条数据的时候,不是直接执行实际的删除操作,而是先把要删除的数据标记为DELETE状态,在需要获取size、扩容、取数据的时候,会执行gc,一次性真正把前面标记的所有删除数据删掉。

Android数据结构之SparseArray

 

gc的过程有点类似虚拟机的gc中的标记整理算法。具体就是遍历所有数据,收集所有没有被删除的数据移动到最前面。

Android数据结构之SparseArray

 

这样做的好处有两个:

  • 如果在刚delete了一条数据后又放了一条相同key的数据进来,这条数据因为被覆盖了后面也不用执行真正的gc了,节省了操作时间
  • 如果一次性delete多条数据,可以把真正的删除操作放在一次gc中而不是多次gc中,节省时间
Android数据结构之SparseArray

 

扩容/缩容

前面提到,在put数据的时候可能会引发扩容。扩容的时机很简单,当底层的数组没有空余的空间存放新的数据时就会引发扩容。扩容的算法很简单,基本上就是翻倍,GrowingArrayUtils#growSize:

Android数据结构之SparseArray

 

不过需要注意的是,growSize算出来size不一定是扩容操作后真正的size,因为扩容时新的数组是调用ArrayUtils#newUnpaddedArray生成新数组的,这个方法涉及内存对齐,实际返回的数组的size一般比要求的大小要大。

SparseArray是没有缩容机制的。假如前面存了大量的数据导致数组扩容到了1024,哪怕调用clear清空所有数据底层数组的大小还是1024。所以先存放大量数据在删到只剩少量需要长期持有的数据场景下,用SpareArray可能会导致空间的浪费。

总结

  • 建议使用SparseArray替换HashMap是因为得益于下面几点,SparseArray可能比HashMap更节省内存,而某些android设备上内存是紧缺资源:
  • 避免了Integer的自动装箱
  • 基于index建立的key和value的映射关系相比Map基于Entry结构建立key和value的映射关系节约内存
  • 某些场景如hash冲突下访问速度可能优于hashmap;不适合数据集比较大的场景。
  • SparseArray没有缩容机制。某些场景下不适合使用,比如:大量地put后大量地delete,然后长久持有SparseArray,导致大量的空位置没法被虚拟机gc,浪费内存
  • SparseArray一般来说比Hashmap慢,因为二分查找比较慢,而且插入删除数据涉及数组的copy。在数据集不大时不明显
  • SparseArray每次插入删除数据都保证了所有存储数据的排列有序
  • SparseArray可以通过index定位数据,Hashmap不行

最后

如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。感兴趣的小伙伴可以点一下关注哦。谢谢!

转载自今日头条     Android架构师丨小熊
 



Tags:Android SparseArray   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一、前言在Android开发中,当需要存储键值对时,一般都是用java自带的HashMap。但是细心的同学可能会发现,有时候如果实际的HashMap的key-vaule中的key是Integer时,AndroidStudio...【详细内容】
2019-09-27  Tags: Android SparseArray  点击:(258)  评论:(0)  加入收藏
▌简易百科推荐
今天面试遇到同学说做过内存优化,于是我一般都会问那 Bitmap 的像素内存存在哪?大多数同学都回答在 java heap 里面,就比较尴尬,理论上你做内存优化,如果连图片这个内存大户内存...【详细内容】
2021-12-23  像程序那样思考    Tags:Android开发   点击:(8)  评论:(0)  加入收藏
Android logcat日志封装logcat痛点在Android开发中使用logcat非常频繁,logcat能帮我们定位问题,但是在日常使用中发现每次使用都需要传递tag,并且会遇到输出频率很高的log,在多...【详细内容】
2021-12-22  YuCoding    Tags:Android   点击:(8)  评论:(0)  加入收藏
对项目的基本介绍 1.整个框架主要是给MVVM框架使用的,自己写完interface接口后,通过自定义的注解就能自动生成接口方法 2.用Kotlin的Flow去代替Rxjava,因为我发现RxJava功能很...【详细内容】
2021-12-08  网易Leo    Tags:Android开发   点击:(17)  评论:(0)  加入收藏
前言在Android开发过程中,有些时候会根据需要引用别的项目到当前项目里面,而且以Module形式引用。所以本篇博文就来分享一下怎么以Module形式引用别的项目到当前项目中,方便开...【详细内容】
2021-12-07  网易Leo    Tags:Android开发   点击:(22)  评论:(0)  加入收藏
作者:fundroid这篇文章偏阅读一些,大家可以了解下 Android 的一些最新动向。每年9/10月份 Google 都会举行约为期2天的 Android Dev Summit,在活动上 Google 的技术专家们会分...【详细内容】
2021-11-30  像程序那样思考    Tags:Android开发   点击:(15)  评论:(0)  加入收藏
一、 准备工作1、安装JDK,下载地址(可能需要一个oracle账号,大家百度一下或者自行注册一个就行。尽可能选择8或者11,这两个是长期版本)Java SE | Oracle Technology Network | Or...【详细内容】
2021-11-23  永沧    Tags:Android   点击:(28)  评论:(0)  加入收藏
使用Maven Publish Plugin插件。(官方支持)一、在Library的build.gradle中配置plugins { id &#39;com.android.library&#39; id &#39;kotlin-android&#39; id &#39;k...【详细内容】
2021-11-05  羊城小阳    Tags:Android   点击:(37)  评论:(0)  加入收藏
谷歌离推出Play Store应用程序的新数据隐私部分又近了一步。应用程序开发人员现在可以通过谷歌在Play控制台的新 "数据安全表 "填写相关细节。该公司表示,所需信息将从2022年...【详细内容】
2021-10-20    中关村在线  Tags:安卓   点击:(58)  评论:(0)  加入收藏
架构究竟是什么?如何更好的理解架构?我们知道一个APP通常是由class组成,而这些class之间如何组合,相互之间又如何产生作用,就是影响这个APP的关键点。细分的话我们可以将其分为类...【详细内容】
2021-09-17  像程序那样思考    Tags:Android架构   点击:(52)  评论:(0)  加入收藏
概述当Android应用程序需要访问设备上的敏感资源时,应用程序开发人员会使用权限模型。虽然该模型使用起来非常简单,但开发人员在使用权限时容易出错,从而导致安全漏洞。本文中,...【详细内容】
2021-09-07  SecTr安全团队    Tags:Android开发   点击:(66)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条