- 在软件开发中,容器类库是用于存储和管理数据的重要工具。ArkTS作为HarmonyOS应用开发的主力语言,提供了丰富的容器类库,包括线性容器和非线性容器,这些容器可以帮助开发者更高效地处理数据,提升应用的性能。
- ArkTS的容器类库采用了类似静态语言的方式来实现,并通过对存储位置以及属性的限制,让每种类型的数据都能在完成自身功能的基础上去除冗余逻辑,保证了数据的高效访问,提升了应用的性能。当前,ArkTS的容器类库主要分为线性容器和非线性容器两大类。
线性容器
线性容器实现了能按顺序访问的数据结构,常见的线性容器包括ArrayList、Vector、List、LinkedList、Deque、Queue和Stack。
1. ArrayList
ArrayList是一种线性数据结构,底层基于数组实现。它可以存储任意类型的对象,包括基本数据类型的包装类。与普通数组相比,ArrayList的优势在于可以动态增加和缩减元素的数量,而不需要手动调整数组的大小。当需要添加或删除元素时,ArrayList会根据需要自动扩容或缩减。
特点
- 当需要频繁读取集合中的元素时,推荐使用ArrayList。
- 依据泛型定义,要求存储位置是一片连续的内存空间,初始容量大小为10,并支持动态扩容,每次扩容大小为原始容量的1.5倍。
示例
import { ArrayList } from '@kit.ArkTS';
let arrayList: ArrayList<string> = new ArrayList();
// 增加元素
arrayList.add('a');
arrayList.insert('b', 0);
// 访问元素
console.info(arrayList[0]);
arrayList.forEach((value, index) => {
console.info(`index: ${index}, value: ${value}`);
});
// 修改元素
arrayList[0] = 'one';
// 删除元素
arrayList.remove('a');
arrayList.removeByRange(0, 1);
2. Vector
Vector也是一种基于数组实现的线性数据结构,它和ArrayList相似,但提供了更多操作数组的接口。
特点
- Vector在支持操作符访问的基础上,还增加了get/set接口,提供更为完善的校验及容错机制。
- 依据泛型定义,要求存储位置是一片连续的内存空间,初始容量大小为10,并支持动态扩容,每次扩容大小为原始容量的2倍。
- 从API version 9开始,该接口不再维护,推荐使用ArrayList。
示例
import { Vector } from '@kit.ArkTS';
let vector: Vector<string> = new Vector();
// 增加元素
vector.add('a');
vector.insert('b', 0);
// 访问元素
console.info(vector.get(0));
console.info(vector.getFirstElement());
// 修改元素
vector.set(0, 'one');
// 删除元素
vector.remove('a');
vector.removeByRange(0, 1);
3. List
List可用来构造一个单向链表对象,即只能通过头结点开始访问到尾节点。
特点
- 依据泛型定义,在内存中的存储位置可以是不连续的。
- 与LinkedList相比,LinkedList是双向链表,可以快速地在头尾进行增删,而List是单向链表,无法双向操作。
- 当需要频繁的插入删除时,推荐使用List高效操作。
示例
import { List } from '@kit.ArkTS';
let list: List<string> = new List();
// 增加元素
list.add('a');
list.insert('b', 0);
// 访问元素
console.info(list.get(0));
console.info(list.getFirst());
console.info(list.getLast());
// 修改元素
list.set(0, 'one');
// 删除元素
list.remove('a');
list.removeByIndex(0);
4. LinkedList
LinkedList是一种双向链表,每个节点除了储存数据外,还有两个指针分别指向前一个节点和后一个节点。
特点
- 可以在某一节点向前或者向后遍历List。
- 与ArrayList相比,插入数据效率LinkedList优于ArrayList,而查询效率ArrayList优于LinkedList。
示例
import { LinkedList } from '@kit.ArkTS';
let linkedList: LinkedList<string> = new LinkedList();
// 增加元素
linkedList.add('a');
linkedList.addFirst('b');
linkedList.insert(0, 'c');
// 访问元素
console.info(linkedList.get(0));
console.info(linkedList.getFirst());
console.info(linkedList.getLast());
// 修改元素
linkedList.set(0, 'one');
// 删除元素
linkedList.remove('a');
linkedList.removeFirst();
linkedList.removeLast();
5. Queue
Queue是一种具有先进先出(FIFO)特性的数据结构。
特点
- 从容器尾部插入元素,从容器头部弹出元素,占用一片连续的内存空间。
- 一般符合先进先出的场景可以使用Queue。
示例
import { Queue } from '@kit.ArkTS';
let queue: Queue<string> = new Queue();
// 增加元素
queue.add('a');
// 删除元素
let element = queue.pop();
// 访问元素
console.info(queue.getFirst());
6. Deque
Deque是一种双端队列,它继承自Queue接口,提供了一些额外的方法来支持在队头和队尾进行添加和删除元素的操作。
特点
- 可以从容器头尾进行进出元素操作,占用一片连续的内存空间。
- 与Queue相比,Queue只能在一端删除一端增加,Deque可以两端增删。
- 与Vector相比,它们都支持在两端增删元素,但Deque不能进行中间插入的操作。对头部元素的插入删除效率高于Vector,而Vector访问元素的效率高于Deque。
- 需要频繁在集合两端进行增删元素的操作时,推荐使用Deque。
示例
import { Deque } from '@kit.ArkTS';
let deque: Deque<string> = new Deque();
// 增加元素
deque.insertFront('a');
deque.insertEnd('b');
// 访问元素
console.info(deque.getFirst());
console.info(deque.getLast());
// 删除元素
deque.popFirst();
deque.popLast();
7. Stack
Stack类可以用来创建栈对象,该栈对象按照先进后出的规则存储元素。
特点
- 基于泛型定义,要求存储位置需要是一片连续的内存空间,初始容量大小为8,并且支持动态扩容。每次扩容大小是原始容量的1.5倍。
- 底层基于数组实现,入栈和出栈操作都是在数组的一端进行。
示例
import { Stack } from '@kit.ArkTS';
let stack: Stack<string> = new Stack();
// 增加元素
stack.push('a');
// 删除元素
let element = stack.pop();
// 访问元素
console.info(stack[0]);
非线性容器详解
非线性容器实现了能快速查找的数据结构,其底层通过hash或者红黑树实现。非线性容器中的key及value的类型均满足ECMA标准。常见的非线性容器包括HashMap、HashSet、TreeMap、TreeSet、LightWeightMap、LightWeightSet和PlainArray。
1. HashMap
HashMap是一个很常用的集合类,用来存储键值对。
特点
- 存储具有关联关系的键值对集合,存储元素中键唯一,依据键的hash值确定存储位置,从而快速找到键值对。
- 访问速度较快,但不能自定义排序。
- 初始容量为16,当容量不足时,会自动进行扩容,每次扩容容量会变为原来的两倍。
- 底层基于HashTable实现,冲突策略采用链地址法。
示例
import { HashMap } from '@kit.ArkTS';
let hashMap: HashMap<string, number> = new HashMap();
// 增加元素
hashMap.set('a', 1);
// 访问元素
console.info(hashMap.get('a'));
console.info(hashMap.hasKey('a'));
// 修改元素
hashMap.replace('a', 2);
// 删除元素
hashMap.remove('a');
hashMap.clear();
2. HashSet
HashSet可用来存储一系列值的集合,存储元素中值唯一。
特点
- 依据值的hash确定存储位置,从而快速找到该值。
- 允许放入null值,但不能自定义排序。
- 基于HashMap实现,只对value对象进行处理。
示例
import { HashSet } from '@kit.ArkTS';
let hashSet: HashSet<number> = new HashSet();
// 增加元素
hashSet.add(1);
// 访问元素
console.info(hashSet.has(1));
// 删除元素
hashSet.remove(1);
hashSet.clear();
3. TreeMap
TreeMap可用于存储具有关联关系的key - value键值对集合,存储元素中key值唯一。
特点
- 底层使用红黑树实现,可以利用二叉树特性快速查找键值对。key值有序存储,可以实现快速的插入和删除。
- 允许用户自定义排序方法。
- 与HashMap相比,HashMap依据键的hashCode存取数据,访问速度较快。而TreeMap是有序存取,效率较低。
示例
import { TreeMap } from '@kit.ArkTS';
let treeMap: TreeMap<string, number> = new TreeMap();
// 增加元素
treeMap.set('a', 1);
// 访问元素
console.info(treeMap.get('a'));
console.info(treeMap.getFirstKey());
console.info(treeMap.getLastKey());
// 修改元素
treeMap.replace('a', 2);
// 删除元素
treeMap.remove('a');
treeMap.clear();
4. TreeSet
TreeSet可用于存储一系列值的集合,元素中value唯一且有序。
特点
- 基于TreeMap实现,在TreeSet中,只对value对象进行处理。
- 与HashSet相比,HashSet中的数据无序存放,而TreeSet是有序存放。它们集合中的元素都不允许重复,但HashSet允许放入null值,TreeSet不建议插入空值,可能会影响排序结果。
示例
import { TreeSet } from '@kit.ArkTS';
let treeSet: TreeSet<number> = new TreeSet();
// 增加元素
treeSet.add(1);
// 访问元素
console.info(treeSet.has(1));
console.info(treeSet.getFirstValue());
console.info(treeSet.getLastValue());
// 删除元素
treeSet.remove(1);
treeSet.clear();
5. LightWeightMap
LightWeightMap可用于存储具有关联关系的key - value键值对集合,存储元素中key值唯一。
特点
- 依据泛型定义,采用轻量级结构,初始默认容量大小为8,每次扩容大小为原始容量的两倍。
- 集合中key值的查找依赖于hash算法,通过一个数组存储hash值,然后映射到其他数组中的key值及value值。
- 与HashMap相比,LightWeightMap占用内存更小。
示例
import { LightWeightMap } from '@kit.ArkTS';
let lightWeightMap: LightWeightMap<string, number> = new LightWeightMap();
// 增加元素
lightWeightMap.set('a', 1);
// 访问元素
console.info(lightWeightMap.get('a'));
console.info(lightWeightMap.hasKey('a'));
// 修改元素
lightWeightMap.setValueAt(0, 2);
// 删除元素
lightWeightMap.remove('a');
lightWeightMap.clear();
6. LightWeightSet
LightWeightSet可用于存储一系列值的集合,存储元素中value值唯一。
特点
- 依据泛型定义,采用轻量级结构,初始默认容量大小为8,每次扩容大小为原始容量的两倍。
- 集合中value值的查找依赖于hash算法,通过一个数组存储hash值,然后映射到其他数组中的value值。
- 与HashSet相比,LightWeightSet的占用内存更小。
示例
import { LightWeightSet } from '@kit.ArkTS';
let lightWeightSet: LightWeightSet<number> = new LightWeightSet();
// 增加元素
lightWeightSet.add(1);
// 访问元素
console.info(lightWeightSet.has(1));
// 删除元素
lightWeightSet.remove(1);
lightWeightSet.clear();
7. PlainArray
PlainArray可用来存储具有关联关系的键值对集合,存储元素中key是唯一的,并且对于PlainArray来说,其key的类型为number类型。
特点
- 采用更加轻量级的结构,集合中的key值的查找依赖于二分查找算法,然后映射到其他数组中的value值。
- 初始默认容量大小为16,每次扩容大小为原始容量的2倍。
示例
import { PlainArray } from '@kit.ArkTS';
let plainArray: PlainArray<string> = new PlainArray();
// 增加元素
plainArray.add(1, 'a');
// 访问元素
console.info(plainArray.get(1));
console.info(plainArray.getKeyAt(1));
// 修改元素
plainArray.setValueAt(0, 'b');
// 删除元素
plainArray.remove(1);
plainArray.clear();
对比线性容器与非线性容器
访问速度
- 线性容器中的ArrayList和Vector在随机访问时速度较快,因为它们基于数组实现,可以通过索引直接访问元素。
- 非线性容器中的HashMap和HashSet通过hash值确定存储位置,在查找元素时速度较快。而TreeMap和TreeSet由于需要维护元素的有序性,查找速度相对较慢,但可以进行有序访问。
插入和删除效率
- 线性容器中的LinkedList在插入和删除元素时效率较高,因为只需要改变相邻节点的指针即可。而ArrayList和Vector在插入和删除元素时可能需要移动其他元素,效率较低。
- 非线性容器中的HashMap、HashSet、TreeMap和TreeSet在插入和删除元素时效率较高,因为它们基于hash或红黑树实现。
内存占用
- 线性容器中的ArrayList和Vector需要连续的内存空间,当元素数量较多时,可能会导致内存碎片。
- 非线性容器中的LightWeightMap和LightWeightSet采用轻量级结构,占用内存较小。
使用场景
- 线性容器适用于需要按顺序访问元素的场景,例如遍历列表、实现队列和栈等。
- 非线性容器适用于需要快速查找元素的场景,例如存储键值对、去重等。