SparseArray、SparseIntArray 和 SparseLongArray 的差异

序幕:传统HashMap图书馆的困扰

想象一下,我们有一个巨大的图书馆(内存),里面有很多书架(数组)。最传统的管理方式是 HashMap图书馆

  • 工作方式:每本书(Value)都有一个唯一的、可能是任意数字的索引号(Key)。图书管理员(HashMap)有一个"神奇目录",你告诉他索引号(比如 10001),他能瞬间(O(1)时间)算出这本书在哪个书架的第几个位置。
  • 优点:找书速度极快,无论图书馆多大。
  • 缺点 :为了建立这个"神奇目录",他需要准备很多"目录卡片"(HashMap.Entry 对象)。每张卡片不仅记录了索引号和书的位置,还记录了其他信息以便处理冲突。更麻烦的是,如果索引号是 int 类型,而书是 Integer 这类"精装书",管理员还得给每本"平装书"额外包上一个"精装书皮"(自动装箱),这非常浪费"包装材料"(内存)。

那么,当我们的索引号(Key)本身就是简单的整数(int),而且我们特别在意节省"包装材料"时,有没有更高效的管理方式呢?

答案就是我们的主角:SparseArray系列图书馆


第一幕:SparseArray图书馆 ------ 精益求精的"二分查找"大师

SparseArray 采用了一种截然不同但非常聪明的管理策略。

底层原理:两本紧密协作的登记簿

这位管理员有两本登记簿(两个数组):

  1. mKeys[]: 一本按顺序记录所有图书索引号(Key)的册子。
  2. mValues[]: 另一本册子,严格地、一对一地记录着对应索引号的图书(Value)所在的具体位置。

工作流程("增删查改"的故事)

  • 查(get :当你来找索引号为 10001 的书时,管理员不会用"神奇目录",而是会打开 mKeys[] 登记簿,使用二分查找法 快速地翻阅。因为登记簿是有序的,他很快就能确定 10001 这个号在不在簿子上,以及在第几行(索引位置)。一旦找到行数,他立刻去 mValues[] 登记簿的同一行,找到书的存放位置。

    • 专家解读:查找时间复杂度是 O(log n),对于数据量小(例如千级以内)的情况,这和 HashMap 的 O(1) 差距微乎其微,但却换来了巨大的内存节省。
  • 增(put :你要存入一本索引号为 10001 的新书。管理员同样会用二分查找法确定 10001 这个号应该按顺序插入到 mKeys[] 登记簿的哪个位置(比如第5行和第6行之间)。然后,他会在 mKeys[]mValues[]同一个位置 (比如第6行)同时插入新的索引号和图书信息。这可能需要移动后面所有的记录,所以他更擅长处理不频繁插入的情况。

  • 删(delete :这里是最精妙的设计!当需要删除一本书时,管理员并不会立刻把登记簿上的这条记录划掉并把后面的记录前移(那太耗时了)。他有一个更巧妙的办法:他只是在 mValues[] 登记簿的那一行上,贴一个特殊的标签------DELETED

    • 这行记录就变成了一个"空位"。下次要插入新书时,他会优先复用这些贴有 DELETED 标签的空位,而不是去登记簿末尾添加新行。这避免了频繁的数组拷贝。
    • 当然,如果空位太多,他也会在合适的时候(比如垃圾回收gc()时)来一次大扫除,把所有被删除的记录彻底清理掉,让两本登记簿重新变得紧凑。

核心优势

  1. 极度节省内存 :因为它没有额外的"目录卡片"(Entry对象),避免了Map的条目开销。对于SparseArray,它的Value是Object,但Key是基本类型int,所以至少避免了Key的自动装箱
  2. 针对Android优化 :在早期Android设备上,内存非常宝贵,GC(垃圾回收)对应用性能影响很大。SparseArray 的延迟删除机制(标记DELETED)减少了数组结构的修改,从而减少了触发GC的次数,使应用更流畅。

第二幕 & 第三幕:SparseIntArray & SparseLongArray ------ 专一高效的"特种"图书馆

SparseArray 已经很棒了,但它的 mValues[] 登记簿记录的是"图书的位置"(Object类型)。这意味着,如果你存的本来就是一本"平装书"(比如 int, long 这些基本类型),管理员还是得给它包上一个"精装书皮"(自动装箱Integer, Long),这又产生了不必要的浪费。

于是,两位更专一的专家登场了:

  • SparseIntArray : 它专门管理 索引号(Key)是int,图书(Value)也是int 的图书馆。
  • SparseLongArray : 它专门管理 索引号(Key)是int,图书(Value)是long 的图书馆。

底层原理差异

它们的核心思想和工作流程(二分查找、延迟删除)和 SparseArray 完全一样

唯一的、也是最重要的区别 在于那本 mValues[] 登记簿:

  • SparseArraymValues[] 是一个 Object[] 数组。
  • SparseIntArraymValues[] 是一个 int[] 数组。
  • SparseLongArraymValues[] 是一个 long[] 数组。

优势升华

这意味着,SparseIntArraySparseLongArray 连Value的自动装箱也彻底避免了!它们直接用原生数组存储值,内存利用率达到了极致。

  • SparseArray: 避免了Key的装箱。
  • SparseIntArray: 同时避免了Key和Value的装箱。

终幕:如何选择你的图书馆管理员?(使用场景总结)

特性 HashMap<Integer, Object> SparseArray<Object> SparseIntArray SparseLongArray
Key类型 Integer (装箱) int (基本类型) int (基本类型) int (基本类型)
Value类型 Object Object int (基本类型) long (基本类型)
内存开销 (有Entry对象,Key+Value可能双装箱) (无Entry,Key无装箱) 极低 (无Entry,Key+Value均无装箱) 极低 (无Entry,Key+Value均无装箱)
查找效率 O(1) (极快) O(log n) (快,小数据量下无感) O(log n) (快,小数据量下无感) O(log n) (快,小数据量下无感)
插入/删除效率 O(1) / O(1) O(n) (可能需移动元素) O(n) (可能需移动元素) O(n) (可能需移动元素)
数据量建议 大小皆可 千级以下 千级以下 千级以下

选择指南:

  1. 什么时候用 SparseArray

    • 当你的Key是int类型,而Value是非基本类型 (如 Object, String, 自定义对象等)时。
    • 例如:Map<Integer, User> 可以用 SparseArray<User> 替代。
  2. 什么时候用 SparseIntArray / SparseLongArray

    • 当你的Key是int类型,且Value正好也是intlong基本类型 时。这是它们的绝对主场,效率最高。
    • 例如:Map<Integer, Integer> (value是resId等) 用 SparseIntArray
    • 例如:Map<Integer, Long>SparseLongArray
  3. 什么时候坚持用 HashMap

    • 数据量非常大(数千以上)时,HashMap的O(1)查找优势会碾压二分查找的O(log n)。
    • Key不是int类型 时(如String, Long等)。Sparse系列只认int key。

核心思想总结

SparseArray家族的本质是:用时间换空间 。它通过二分查找延迟删除 这两种算法,牺牲了一点查询和插入的时间性能,换来了远超HashMap的内存效率 。而SparseIntArraySparseLongArray则是将这个理念发挥到了极致,是特定场景下最极致的优化选择。

所以,下次在Android开发中,如果你的Map键是int类型,不妨先想想:"我是否需要请这几位高效又节省的特种图书馆管理员来帮忙?"

相关推荐
2501_916013743 小时前
App 上架全流程指南,iOS App 上架步骤、App Store 应用发布流程、uni-app 打包上传与审核要点详解
android·ios·小程序·https·uni-app·iphone·webview
牛蛙点点申请出战3 小时前
仿微信语音 WaveView 实现
android·前端·ios
用户093 小时前
Android View 事件分发机制详解及应用
android·kotlin
ForteScarlet3 小时前
Kotlin 2.2.20 现已发布!下个版本的特性抢先看!
android·开发语言·kotlin·jetbrains
诺诺Okami3 小时前
Android Framework-Input-8 ANR相关
android
法欧特斯卡雷特3 小时前
Kotlin 2.2.20 现已发布!下个版本的特性抢先看!
android·前端·后端
人生游戏牛马NPC1号3 小时前
学习 Android (二十一) 学习 OpenCV (六)
android·opencv·学习
用户2018792831673 小时前
Native 层 Handler 机制与 Java 层共用 MessageQueue 的设计逻辑
android
lichong9513 小时前
【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之android 把assert里的dist.zip 包解压到sd卡里
android·vue.js·iphone