在 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(20)

// 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 = new ArrayList<>(20);

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(32)

// 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,初始容量 10(默认)

val sparseArray = SparseArray(10)

// 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,初始容量 10

val arrayMap = ArrayMap(10)

// 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(Comparator { s1, s2 ->

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 更省内存(避免 int → Integer 装箱)。

七、总结

Android 集合选型的核心是 “匹配场景”:

有序数据看操作类型(读多写少用 ArrayList,写多读少用 LinkedList);

键值对看键类型和数据量(int 键用 SparseArray,小数据量用 ArrayMap,大数据量用 HashMap);

去重数据看是否有序(无序用 HashSet,有序用 TreeSet)。

掌握不同集合的特性和性能差异,不仅能提升代码效率,还能减少内存占用 —— 在 Android 开发中,“小而优” 的集合选择往往是提升 App 流畅度的关键。