本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
在鸿蒙(HarmonyOS)应用开发中,集合与数组容器是处理数据结构的核心工具,它们提供了高效的数据存储和操作方法。本文将全面介绍鸿蒙开发中的各类容器,包括线性容器、非线性容器以及原生数组API的使用方法。
一、容器类概述与分类
鸿蒙开发框架提供了丰富的容器类API,主要分为线性容器和非线性容器两大类,共14种容器类型,每种容器都有其特定的使用场景和性能特点。
1.1 线性容器类
线性容器底层主要通过数组或链表实现,包括:
- 基于数组实现:ArrayList、Vector、Queue、Deque、Stack
- 基于链表实现:List、LinkedList
1.2 非线性容器类
非线性容器底层通过哈希表或红黑树实现,包括:
- 基于哈希表实现:HashMap、HashSet、LightWeightMap、LightWeightSet、PlainArray
- 基于红黑树实现:TreeMap、TreeSet
1.3 原生数组API
除了专门的容器类,鸿蒙也支持标准的JavaScript/TypeScript数组操作API,如push、pop、slice等方法。
二、线性容器详解
2.1 ArrayList(动态数组)
ArrayList是最常用的线性容器,基于动态数组实现,支持快速随机访问。
核心特性:
- 初始容量为10,动态扩容时每次增加原始容量的1.5倍
- 内存空间连续,访问效率高(O(1))
- 插入删除操作可能引起元素移动(O(n))
常用API:
import ArrayList from '@ohos.util.ArrayList';
// 创建ArrayList
let arrayList = new ArrayList<string>();
// 添加元素
arrayList.add("a"); // 尾部添加
arrayList.insert("b", 0); // 指定位置插入
// 查询元素
let hasElement = arrayList.has("a"); // 判断是否存在
let index = arrayList.getIndexOf("a"); // 获取首次出现位置
let element = arrayList.get(0); // 根据索引获取
// 删除元素
let removed = arrayList.removeByIndex(0); // 根据索引删除
let cleared = arrayList.clear(); // 清空
示例场景:需要频繁读取元素且数据量变化不大的场景,如配置项管理、静态数据缓存等。
2.2 Vector(向量)
Vector与ArrayList类似,但扩容策略不同,每次扩容为原始容量的2倍。
与ArrayList的区别:
- 扩容更快(2倍 vs 1.5倍),适合数据添加频繁的场景
- 提供更完善的校验和容错机制
- 线程安全(部分操作)
import Vector from '@ohos.util.Vector';
let vector = new Vector<number>();
vector.add(1);
vector.insert(2, 0);
let element = vector.get(0);
2.3 List(单向链表)
List基于单向链表实现,适合频繁插入删除但随机访问较少的场景。
核心特性:
- 内存空间可以不连续
- 插入删除效率高(O(1))
- 随机访问效率低(O(n))
- 允许元素为null
import { List } from '@kit.ArkTS';
let list = new List<string>();
list.add("first");
list.insert("second", 1);
let hasElement = list.has("first");
let element = list.get(1);
let removed = list.remove("second");
注意事项:
- 避免使用[index]方式直接访问,推荐使用get()方法
- 查询操作需要从头遍历,效率较低
2.4 LinkedList(双向链表)
LinkedList是List的双向版本,可以从任意节点向前或向后遍历。
与List的区别:
- 可以双向遍历
- 头尾操作效率更高
- 内存占用略高(需要存储前后指针)
import LinkedList from '@ohos.util.LinkedList';
let linkedList = new LinkedList<number>();
linkedList.add(1);
linkedList.addFirst(0); // 头部添加
linkedList.addLast(2); // 尾部添加
let first = linkedList.getFirst();
let last = linkedList.getLast();
2.5 Queue(队列)与Deque(双端队列)
Queue是先进先出(FIFO)的队列,Deque支持两端操作
import Queue from '@ohos.util.Queue';
import Deque from '@ohos.util.Deque';
// Queue示例
let queue = new Queue<string>();
queue.add("task1"); // 入队
let task = queue.poll(); // 出队
// Deque示例
let deque = new Deque<number>();
deque.addFirst(1); // 前端入队
deque.addLast(2); // 后端入队
let first = deque.popFirst(); // 前端出队
let last = deque.popLast(); // 后端出队
2.6 Stack(栈)
Stack实现后进先出(LIFO)的数据结构
import Stack from '@ohos.util.Stack';
let stack = new Stack<string>();
stack.push("page1"); // 入栈
stack.push("page2");
let page = stack.pop(); // 出栈("page2")
let top = stack.peek(); // 查看栈顶("page1")
应用场景:页面导航、撤销操作、表达式求值等。
三、非线性容器详解
3.1 HashMap(哈希映射)
HashMap存储键值对,基于哈希表实现,查找效率高(O(1))。
核心特性:
- 初始容量16,扩容为2倍
- 键唯一,允许null键值
- 冲突解决:链地址法
import HashMap from '@ohos.util.HashMap';
let map = new HashMap<string, number>();
map.set("apple", 5);
map.set("banana", 3);
let count = map.get("apple"); // 5
let hasKey = map.hasKey("banana");
let removed = map.remove("apple");
let keys = map.keys(); // 获取所有键
let values = map.values(); // 获取所有值
注意事项:
- 键对象应正确实现hashCode和equals方法
- 遍历顺序不确定
3.2 HashSet(哈希集合)
HashSet基于HashMap实现,存储唯一值
import { HashSet } from '@kit.ArkTS';
let set = new HashSet<string>();
set.add("a");
set.add("b");
let hasElement = set.has("a");
let removed = set.remove("b");
let size = set.size;
set.clear();
应用场景:去重、集合运算、存在性检查等。
3.3 TreeMap(树映射)与TreeSet(树集合)
基于红黑树实现,元素保持有序
import TreeMap from '@ohos.util.TreeMap';
import TreeSet from '@ohos.util.TreeSet';
// TreeMap示例
let treeMap = new TreeMap<string, number>();
treeMap.set("a", 1);
treeMap.set("b", 2);
let firstKey = treeMap.getFirstKey(); // "a"
let lastValue = treeMap.getLastValue(); // 2
// TreeSet示例
let treeSet = new TreeSet<number>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
let first = treeSet.getFirst(); // 1
特点:
- 元素自动排序(基于自然顺序或Comparator)
- 查询效率O(log n)
- 不支持null键(可能影响排序)
3.4 LightWeightMap与LightWeightSet
轻量级容器,内存占用更少
import LightWeightMap from '@ohos.util.LightWeightMap';
import LightWeightSet from '@ohos.util.LightWeightSet';
// LightWeightMap示例
let lwMap = new LightWeightMap();
lwMap.set("key", "value");
let value = lwMap.get("key");
// LightWeightSet示例
let lwSet = new LightWeightSet();
lwSet.add("item");
let has = lwSet.has("item");
适用场景:内存敏感的环境,存储简单数据类型。
四、原生数组API详解
鸿蒙开发中也可以使用标准的JavaScript/TypeScript数组操作。
4.1 基本操作
// 创建数组
let arr: number[] = [1, 2, 3];
let arr2: Array<string> = new Array("a", "b");
// 访问元素
let first = arr[0]; // 1
arr[1] = 4; // [1, 4, 3]
// 长度属性
let len = arr.length; // 3
4.2 常用方法
添加/删除元素
// 尾部操作
arr.push(5); // [1, 4, 3, 5]
let last = arr.pop(); // 5, [1, 4, 3]
// 头部操作
arr.unshift(0); // [0, 1, 4, 3]
let first = arr.shift(); // 0, [1, 4, 3]
// 任意位置操作
arr.splice(1, 1, 2, 2); // [1, 2, 2, 3] (从索引1开始删除1个元素,插入2,2)
查找与遍历
// 查找元素
let index = arr.indexOf(2); // 1
let lastIndex = arr.lastIndexOf(2); // 2
// 遍历数组
arr.forEach((item, idx) => {
console.log(`arr[${idx}] = ${item}`);
});
// 映射新数组
let doubled = arr.map(x => x * 2); // [2, 4, 4, 6]
// 过滤数组
let filtered = arr.filter(x => x > 2); // [3]
排序与转换
// 排序
arr.sort((a, b) => a - b); // [1, 2, 2, 3]
arr.reverse(); // [3, 2, 2, 1]
// 数组转字符串
let str = arr.join("-"); // "3-2-2-1"
// 数组合并
let newArr = arr.concat([4, 5]); // [3, 2, 2, 1, 4, 5]
4.3 高级方法
// 测试所有元素是否满足条件
let allPass = arr.every(x => x > 0); // true
// 测试是否有元素满足条件
let anyPass = arr.some(x => x > 2); // true
// 查找元素
let found = arr.find(x => x > 1); // 3
let foundIndex = arr.findIndex(x => x > 1); // 0
// 归约计算
let sum = arr.reduce((acc, cur) => acc + cur, 0); // 8
五、ArkTS容器与原生容器的区别
在HarmonyOS Next系统中,ArkTS提供了自己的容器实现,与JavaScript原生容器存在行为差异:
特性 | ArkTS容器 | 原生容器 |
---|---|---|
遍历时修改 | 不允许 | 允许 |
计算属性名 | 不支持 | 支持 |
线程安全 | 非线程安全,需异步锁 | 非线程安全 |
初始化 | 需要构造函数 | 直接字面量 |
并发传递 | 可安全传递 | 需序列化 |
六、性能优化
6.1 容器选择
- 频繁读取:ArrayList/HashMap
- 频繁插入删除:LinkedList
- 需要排序:TreeMap/TreeSet
- 内存敏感:LightWeightMap/LightWeightSet
- 简单数据:原生数组
- 唯一性要求:HashSet/TreeSet
- 键值对存储:HashMap/TreeMap
6.2 性能优化建议
-
预估容量:初始化时设置合理容量避免频繁扩容
let list = new ArrayList<string>(100); // 初始容量100
2. 批量操作:优先使用addAll/setAll等方法
let data = [/* 大量数据 */];
list.addAll(data);
3. 遍历优化:
// 不好的做法
for (let i = 0; i < list.length; i++) {
let item = list.get(i);
// ...
}
// 推荐做法
let iterator = list[Symbol.iterator]();
let next = iterator.next();
while (!next.done) {
let item = next.value;
// ...
next = iterator.next();
}
4.避免装箱拆箱:为数值类型使用专用容器
import LightWeightMap from '@ohos.util.LightWeightMap';
let intMap = new LightWeightMap(); // 比HashMap更节省内存
6.3 线程安全策略
ArkTS容器采用fail-fast机制,并发环境下需要自行保证线程安全:
import { TaskPool } from '@kit.ArkTS';
let map = new collections.Map<string, number>();
let mutex = new TaskPool.Mutex();
async function safeUpdate() {
await mutex.lock();
try {
map.set("key", map.get("key") + 1);
} finally {
mutex.unlock();
}
}
七、常见问题与解决
-
容器选择困难:
- 问题:不确定该用ArrayList还是LinkedList
- 方案:根据操作类型选择 - 频繁随机访问用ArrayList,频繁插入删除用LinkedList
-
性能问题:
- 问题:大数据量时操作变慢
- 方案:检查是否频繁扩容,初始化时设置合理容量;考虑使用更高效的容器如LightWeight系列
-
并发问题:
- 问题:多线程环境下容器操作异常
- 方案:使用TaskPool.Mutex等同步机制
-
内存泄漏:
- 问题:容器持有对象导致无法释放
- 方案:及时清理不再使用的容器;WeakMap/WeakSet等弱引用容器
-
类型安全问题:
- 问题:运行时类型错误
- 方案:严格使用泛型类型约束;必要时进行类型检查