在 Android 开发中,集合是处理数据的核心工具 —— 从列表展示(如 RecyclerView 数据源)到键值缓存(如用户信息存储),几乎所有场景都离不开集合。但 Android 集合体系不仅包含 Java 标准集合(如 ArrayList、HashMap),还有专为移动设备优化的 Android 特有集合(如 SparseArray、ArrayMap)。本文将从 特性、区别、优势、调用示例、性能 五个维度,系统梳理 Android 常用集合,帮你在开发中精准选型。
一、集合体系总览:先搞懂分类
Android 集合本质是对 Java 集合框架的扩展,核心分为三大类,每类对应不同的数据结构和使用场景:
集合类型
核心作用
常用实现类
关键特点
List
有序、可重复的数据存储
ArrayList、LinkedList、Vector
按索引访问,元素可重复
Map
键值对(Key-Value)存储
HashMap、SparseArray、ArrayMap、TreeMap
键唯一,值可重复
Set
无序、不可重复的数据存储
HashSet、TreeSet、LinkedHashSet
元素唯一,基于 Map 实现
核心差异点:
List 强调 “顺序”,支持按索引操作;
Map 强调 “映射”,通过键快速查找值;
Set 强调 “唯一性”,本质是 “只存键不存值” 的 Map。
二、List 集合家族:有序数据的选择
List 是 Android 中最常用的集合(如 RecyclerView 的 adapter 数据源),核心解决 “有序数据的存储与访问” 问题。重点分析 ArrayList 和 LinkedList(Vector 因线程安全开销大,已很少用)。
2.1 ArrayList:动态数组,随机访问王者
特性
底层基于 动态数组 实现,初始化时默认容量为 10(可指定初始容量);
当容量不足时,自动扩容为 原容量的 1.5 倍(计算方式:newCapacity = oldCapacity + (oldCapacity >> 1));
支持快速 随机访问(通过索引 get(int index)),但增删中间元素时需移动数组元素,效率较低。
优势与区别
对比维度
ArrayList
LinkedList
数据结构
动态数组
双向链表
随机访问(get)
快(O (1))
慢(O (n),需遍历链表)
增删中间元素
慢(O (n),移动数组)
快(O (1),仅需修改指针)
内存占用
连续内存,可能有扩容浪费
非连续内存,每个元素含指针
适用场景
频繁读、少量增删
频繁增删、少量读
调用示例(Kotlin/Java)
// Kotlin 示例
fun useArrayList() {
// 1. 创建:指定初始容量为 20(避免频繁扩容)
val arrayList = ArrayList
// 2. 添加元素(末尾添加:O(1),中间添加:O(n))
arrayList.add("Apple") // 末尾添加
arrayList.add(1, "Banana") // 索引 1 处添加(需移动后续元素)
// 3. 删除元素(按索引:O(n),按元素:O(n))
arrayList.removeAt(0) // 删除索引 0 元素
arrayList.remove("Banana") // 删除指定元素(需先查找)
// 4. 遍历(推荐 for-each 或索引遍历,效率高)
// 方式1:索引遍历(随机访问优势体现)
for (i in arrayList.indices) {
Log.d("ArrayList", "元素:${arrayList[i]}")
}
// 方式2:for-each 遍历
for (fruit in arrayList) {
Log.d("ArrayList", "元素:$fruit")
}
// 5. 其他常用操作
arrayList.contains("Apple") // 判断是否包含(O(n))
arrayList.clear() // 清空集合(O(n))
}
// Java 示例(关键差异:需指定泛型,方法调用语法)
List
arrayList.add("Apple");
for (int i = 0; i < arrayList.size(); i++) {
Log.d("ArrayList", "元素:" + arrayList.get(i));
}
性能分析
时间复杂度:
随机访问(get(int)):O (1)(直接通过数组索引定位);末尾添加(add(E)):O (1)( amortized,多数情况无需扩容,扩容时为 O (n));中间增删(add(int, E)/removeAt(int)):O (n)(需移动数组元素);查找(contains(E)):O (n)(需遍历数组)。内存优化建议:
初始化时指定 预估容量(如已知存储 100 条数据,初始容量设为 100),避免频繁扩容导致的内存浪费和性能开销。
2.2 LinkedList:双向链表,增删能手
特性
底层基于 双向链表 实现,每个元素(Node)包含 prev(前驱指针)、next(后继指针)和 item(元素值);
不支持随机访问,需从链表头 / 尾遍历到目标索引;
增删元素时仅需修改指针指向,无需移动大量数据,效率高。
优势与区别
见 2.1 节的对比表,核心优势是 频繁增删中间元素时性能优于 ArrayList。
调用示例(Kotlin)
fun useLinkedList() {
val linkedList = LinkedList
// 1. 添加元素(头部/尾部/中间,O(1) 或 O(n))
linkedList.addFirst("Head") // 头部添加(O(1))
linkedList.addLast("Tail") // 尾部添加(O(1))
linkedList.add(1, "Middle") // 中间添加(需先遍历到索引 1,O(n))
// 2. 删除元素(头部/尾部 O(1),中间 O(n))
linkedList.removeFirst() // 头部删除(O(1))
linkedList.removeLast() // 尾部删除(O(1))
// 3. 遍历(不推荐索引遍历,O(n²) 性能差!)
// 推荐方式:for-each 或迭代器(O(n))
for (item in linkedList) {
Log.d("LinkedList", "元素:$item")
}
// 4. 特有方法(利用链表特性)
linkedList.peekFirst() // 获取头部元素(不删除,O(1))
linkedList.pollLast() // 获取并删除尾部元素(O(1))
}
性能分析
时间复杂度:
头部 / 尾部增删(addFirst/removeLast):O (1)(直接修改首尾指针);中间增删(add(int, E)/removeAt(int)):O (n)(需遍历到目标索引);随机访问(get(int)):O (n)(需从头部 / 尾部遍历);查找(contains(E)):O (n)(需遍历链表)。注意避坑:
绝对不要用 for (i in 0 until linkedList.size) 遍历 LinkedList—— 每次 linkedList.get(i) 都会从头遍历,总时间复杂度为 O (n²),数据量大时会严重卡顿!
三、Map 集合家族:键值对存储的最优解
Map 用于 “通过键快速查找值”,是 Android 中缓存数据的核心工具(如存储用户信息、配置参数)。重点分析 Java 标准 Map(HashMap)和 Android 特有优化 Map(SparseArray、ArrayMap),这三类是开发中最常用的。
3.1 HashMap:Java 标准键值对,通用性强
特性
底层基于 数组 + 链表 / 红黑树 实现(JDK 1.8+),数组存储 Node(键值对),冲突时用链表解决,链表长度超过 8 时转为红黑树;
键(Key)允许为 null(仅一个),值(Value)允许为 null;
无序存储(迭代顺序与插入顺序无关),默认初始容量 16,负载因子 0.75(容量达到 16*0.75=12 时扩容为 32)。
优势与区别(对比 Android 特有 Map)
对比维度
HashMap
SparseArray
ArrayMap
键类型
任意对象(如 String)
仅 int
任意对象
装箱开销
有(如 int → Integer)
无(直接存 int)
有(但比 HashMap 小)
内存占用
高(Entry 对象 + 链表)
低(双数组存储)
中(双数组存储)
时间复杂度
get/put:O(1)
get/put:O(log n)
get/put:O(log n)
适用场景
键非 int、数据量大
键为 int、数据量中小
键任意、数据量小(<1000)
调用示例(Kotlin)
fun useHashMap() {
// 1. 创建:初始容量 32,负载因子 0.75(默认)
val hashMap = HashMap
// 2. 添加键值对(O(1),冲突时 O(log n))
hashMap["Apple"] = 10 // 键:String,值:Int
hashMap["Banana"] = 5
hashMap[null] = 3 // 键为 null(仅允许一个)
// 3. 获取值(O(1),通过键的 hash 定位)
val appleCount = hashMap["Apple"] ?: 0 // 无值时默认 0
// 4. 删除键值对(O(1))
hashMap.remove("Banana")
// 5. 遍历(三种方式)
// 方式1:遍历键
for (key in hashMap.keys) {
Log.d("HashMap", "键:$key,值:${hashMap[key]}")
}
// 方式2:遍历键值对(推荐)
for ((key, value) in hashMap) {
Log.d("HashMap", "键:$key,值:$value")
}
// 方式3:遍历值(不推荐,无法获取键)
for (value in hashMap.values) {
Log.d("HashMap", "值:$value")
}
}
性能分析
时间复杂度:
put(K, V)/get(K):O (1)(理想情况,无 hash 冲突);冲突时:链表 O (n),红黑树 O (log n);remove(K):O (1) 或 O (log n)(同 put/get)。Android 中的问题:
键为 int 时(如存储 ID 对应的数据),会产生 自动装箱开销(int → Integer),且每个 Entry 对象会占用额外内存(存储 hash、key、value、next 指针),在内存紧张的移动设备上不够优化。
3.2 SparseArray:Android 特有,int 键的最优解
特性
专为 int 类型键 优化的 Map,底层基于 两个数组 实现:int[] keys(存储键,有序)、Object[] values(存储值,与键索引对应);
无自动装箱开销(直接存储 int 键,无需转为 Integer);
通过 二分查找 定位键的索引(O (log n)),内存占用仅为 HashMap 的 1/3 左右。
优势
内存更优:无 Entry 对象开销,双数组存储更紧凑;
无装箱开销:键为 int 时避免 int → Integer 的转换;
API 简洁:针对 int 键设计,无需泛型声明键类型。
调用示例(Kotlin)
fun useSparseArray() {
// 1. 创建:SparseArray
val sparseArray = SparseArray
// 2. 添加键值对(O(log n),二分查找键位置)
sparseArray.put(1, "Apple") // 键:int,值:String
sparseArray.put(2, "Banana")
sparseArray.put(3, "Cherry")
// 3. 获取值(O(log n))
val value = sparseArray.get(2) // 直接传 int 键,无需装箱
val defaultValue = sparseArray.get(4, "Default") // 无键时返回默认值
// 4. 删除键值对(O(log n) 查找 + O(n) 移动数组)
sparseArray.delete(3) // 按键删除
sparseArray.removeAt(0) // 按索引删除(更高效)
// 5. 遍历(两种方式,均为 O(n))
// 方式1:索引遍历(推荐,效率高)
for (i in 0 until sparseArray.size()) {
val key = sparseArray.keyAt(i)
val value = sparseArray.valueAt(i)
Log.d("SparseArray", "键:$key,值:$value")
}
// 方式2:迭代器(API 24+ 支持)
val iterator = sparseArray.valueIterator()
while (iterator.hasNext()) {
Log.d("SparseArray", "值:${iterator.next()}")
}
}
性能分析
时间复杂度:
put(int, V)/get(int):O (log n)(二分查找键的位置);delete(int):O (log n) 查找 + O (n) 移动数组(删除后需整理数组);遍历:O (n)(直接遍历数组,无额外开销)。适用场景:
键为 int 类型、数据量 中小(<10000)的场景,如 RecyclerView 的 item 数据缓存、ID 与对象的映射(如用户 ID 对应用户信息)。
避坑:
数据量 极大(>10000)时,O(log n) 的时间复杂度会比 HashMap 的 O(1) 慢,此时推荐用 HashMap。
3.3 ArrayMap:Android 特有,小数据量通用键值对
特性
底层基于 两个数组 实现:int[] hashes(存储键的 hash 值,有序)、Object[] array(存储键值对,按 “键 1、值 1、键 2、值 2” 顺序排列);
支持 任意类型键(如 String、Integer),无 Entry 对象开销,内存比 HashMap 节省约 50%;
通过二分查找 hash 值定位键(O (log n)),适合数据量小的场景。
优势与区别(对比 HashMap)
内存更优:无 Entry 对象,双数组存储,适合内存紧张的移动设备;
扩容成本低:扩容时仅需复制两个小数组,比 HashMap 复制大数组开销小;
缺点:数据量超过 1000 时,O(log n) 的查找效率不如 HashMap 的 O(1)。
调用示例(Kotlin)
fun useArrayMap() {
// 1. 创建:ArrayMap
val arrayMap = ArrayMap
// 2. 添加键值对(O(log n))
arrayMap["Apple"] = 10
arrayMap["Banana"] = 5
// 3. 获取值(O(log n))
val count = arrayMap["Apple"] ?: 0
// 4. 删除键值对(O(log n) 查找 + O(n) 移动数组)
arrayMap.remove("Banana")
// 5. 遍历(同 SparseArray,索引遍历效率高)
for (i in 0 until arrayMap.size) {
val key = arrayMap.keyAt(i)
val value = arrayMap.valueAt(i)
Log.d("ArrayMap", "键:$key,值:$value")
}
}
性能分析
时间复杂度:与 SparseArray 一致,put/get/delete 均为 O (log n);
适用场景:任意类型键、数据量 小(<1000)的场景,如配置参数存储、临时缓存(如页面状态);
不适用场景:数据量大(>1000)或频繁查找的场景,此时 HashMap 更优。
四、Set 集合家族:不可重复数据的存储
Set 本质是 “仅存储键的 Map”—— 通过 Map 的 “键唯一性” 实现元素不可重复。核心实现类与 Map 一一对应,特性和性能高度一致。
Set 实现类
底层依赖的 Map
核心特性
适用场景
HashSet
HashMap
无序、元素可 null、效率高
无需有序,仅需去重
TreeSet
TreeMap
有序(自然排序)、无 null
需要按元素顺序存储
LinkedHashSet
LinkedHashMap
有序(插入顺序)、无 null
需要保留插入顺序的去重
4.1 HashSet:最常用的去重集合
特性
底层基于 HashMap 实现(将元素作为 HashMap 的键,值为固定的 PRESENT 对象);
无序(迭代顺序与插入顺序无关);
元素可 null(仅一个),不可重复(通过 equals() 和 hashCode() 判断唯一性)。
调用示例(Kotlin)
fun useHashSet() {
val hashSet = HashSet
// 添加元素(重复元素会自动去重)
hashSet.add("Apple")
hashSet.add("Apple") // 重复添加,无效果
hashSet.add(null) // 允许一个 null
// 删除元素
hashSet.remove("Apple")
// 遍历(for-each 或迭代器)
for (item in hashSet) {
Log.d("HashSet", "元素:$item")
}
// 关键方法
hashSet.contains("Banana") // 判断是否包含(O(1))
hashSet.isEmpty() // 判断是否为空
}
性能分析
时间复杂度与 HashMap 一致:add/remove/contains 均为 O (1);
适用场景:仅需去重、无需有序的场景(如存储用户标签、去重列表)。
4.2 TreeSet:有序的去重集合
特性
底层基于 TreeMap 实现,元素按 自然排序(如 String 按字母序,Integer 按数值序)或自定义排序;
无序(插入顺序),但迭代时按排序后的顺序输出;
元素不可 null,不可重复。
调用示例(Kotlin)
fun useTreeSet() {
// 1. 自然排序(Integer 按数值升序)
val treeSet1 = TreeSet
treeSet1.add(3)
treeSet1.add(1)
treeSet1.add(2)
Log.d("TreeSet", treeSet1.toString()) // 输出 [1, 2, 3]
// 2. 自定义排序(String 按长度降序)
val treeSet2 = TreeSet
s2.length - s1.length // 降序:长的在前
})
treeSet2.add("Apple") // 5 个字符
treeSet2.add("Banana") // 6 个字符
treeSet2.add("Cherry") // 5 个字符
Log.d("TreeSet", treeSet2.toString()) // 输出 [Banana, Apple, Cherry]
}
性能分析
时间复杂度与 TreeMap 一致:add/remove/contains 均为 O (log n);
适用场景:需要按元素顺序存储的去重场景(如排行榜、有序标签列表)。
五、Android 集合选择指南:精准选型不踩坑
结合前面的分析,总结不同场景下的最优集合选择:
场景需求
推荐集合
不推荐集合
列表展示(频繁读、少量增删)
ArrayList
LinkedList
频繁增删中间元素(如队列)
LinkedList
ArrayList
键为 int、数据量中小
SparseArray
HashMap
键任意、数据量小(<1000)
ArrayMap
HashMap
键任意、数据量大(>1000)
HashMap
ArrayMap
仅需去重、无需有序
HashSet
TreeSet
需要有序去重(自然排序)
TreeSet
HashSet
需要保留插入顺序的去重
LinkedHashSet
HashSet
六、性能优化小贴士:让集合更高效
1.初始化指定容量:
如 ArrayList(100)、HashMap(200),避免频繁扩容导致的内存浪费和性能开销。
2.优先用 Android 特有集合:
键为 int 时用 SparseArray 替代 HashMap,数据量小时用 ArrayMap 替代 HashMap,减少内存占用和装箱开销。
3.避免遍历性能坑:
不使用索引遍历 LinkedList(O(n²));遍历 SparseArray/ArrayMap 时用索引遍历(keyAt(i)/valueAt(i)),而非 for-each(底层仍需遍历索引,但代码更简洁)。
4.集合判空用 isEmpty():
避免用 size() == 0,isEmpty() 是更高效的判断方式(多数集合直接返回内部标志,无需计算大小)。
5.大数据量避免自动装箱:
如 SparseIntArray(存储 int 值)、SparseLongArray(存储 long 值),比 SparseArray
七、总结
Android 集合选型的核心是 “匹配场景”:
有序数据看操作类型(读多写少用 ArrayList,写多读少用 LinkedList);
键值对看键类型和数据量(int 键用 SparseArray,小数据量用 ArrayMap,大数据量用 HashMap);
去重数据看是否有序(无序用 HashSet,有序用 TreeSet)。
掌握不同集合的特性和性能差异,不仅能提升代码效率,还能减少内存占用 —— 在 Android 开发中,“小而优” 的集合选择往往是提升 App 流畅度的关键。