☰
仓颉语言 ArrayList 实战:初始化、增删切片与容量扩展的完整指南
2026/10/13 11:32:42 网站建设 项目流程
  • 文档
  • 知识库
  • 教程
  • OpenHarmony

【免费下载链接】CangjieCorpus

本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!

项目地址:https://gitcode.com/Cangjie/CangjieCorpus
点击查看免费下载

本文围绕仓颉标准库std.collection包中的ArrayList<T>动态数组展开,完整覆盖其初始化方式、元素访问、增删改、Range 切片、迭代与容量扩展等核心能力。读完本文,你可以熟练地在仓颉项目中选型和使用ArrayList,理解它与定长Array的关键差异,并掌握避免频繁内存重分配的性能技巧。

1. ArrayList 在仓颉集合体系中的定位

要使用ArrayList类型,需要先导入 collection 包:

import std.collection.*

ArrayList<T>是一种线性的可变长度动态数组,与定长的Array<T>不同,它可以根据需要自动调整大小,并且在创建时不需要指定元素个数。它的 API 定义如下(见 collection_package_class.md):

public class ArrayList<T> <: List<T> { public init() public init(capacity: Int64) public init(size: Int64, initElement: (Int64) -> T) public init(elements: Collection<T>) }

从类型层次看,ArrayList<T>是List<T>接口的实现类。仓颉使用ArrayList<T>表示该类型,T为元素类型,可以是任意类型。元素类型不同的两个ArrayList是不相同的类型,不能互相赋值。

在仓颉基础 Collection 类型中,各类型的选型依据可以概括为下表(引自 collection_overview.md):

类型名称元素可变增删元素元素唯一性有序序列
Array<T>YNNY
ArrayList<T>YYNY
HashSet<T>NYYN
HashMap<K, V>K: N, V: YYK: Y, V: NN

也就是说:不需要增删元素但需要修改元素时选Array;需要频繁增删查改时选ArrayList。ArrayList具备非常好的扩容能力,适合需要频繁增加和删除元素的场景;它既可以原地修改元素,也可以原地增加和删除元素。另外需要注意的是,std.collection包提供的集合类型均不支持并发安全,多线程并发访问应使用collection_concurrent包(参见 collection_concurrent_package_overview.md)。

2. 初始化:七种常见构造方式

ArrayList提供了多种初始化方式(核心文档 extra/ArrayList.md 与 collection_arraylist.md 中的内容):

let a = ArrayList<Int64>([1, 2, 3]) // 使用具体列表初始化 let c = ArrayList<Int64>(3, {i => i + 1}) // 使用 lambda 函数初始化,结果为 [1, 2, 3] let d = ArrayList<String>() // 创建元素类型为 String 的空 ArrayList let e = ArrayList<String>(100) // 创建元素类型为 String 的空 ArrayList,并预分配 100 的空间 let f = ArrayList<Int64>([0, 1, 2]) // 创建包含元素 0, 1, 2 的 ArrayList let g = ArrayList<Int64>(c) // 使用另一个 Collection 初始化 ArrayList let h = ArrayList<String>(2, {x: Int64 => x.toString()}) // 元素类型为 String、大小为 2,所有元素由规则函数初始化

结合 API 文档,可以进一步补充这些构造函数背后的细节(见 collection_package_class.md):

  • init():构造一个初始容量为默认值 16的ArrayList。即let d = ArrayList<String>()虽然size为 0,但底层容量已按 16 预留。
  • init(capacity: Int64):构造一个初始容量为指定大小的空ArrayList。当容量小于 0 时抛出IllegalArgumentException。这里的参数是容量(capacity)而非元素个数——ArrayList<String>(100)创建的是容量为 100 的空列表,而不是 100 个默认元素。
  • init(size: Int64, initElement: (Int64) -> T):构造具有指定元素个数并由规则函数生成每个元素的列表,该构造函数根据参数size设置容量;size小于 0 时抛出IllegalArgumentException。例如ArrayList<Int64>(3, {i => i + 1})得到[1, 2, 3],lambda 的入参就是元素下标。
  • init(elements: Collection<T>):以指定集合的所有元素构造,元素按该集合迭代器返回的顺序排列。
  • 此外还有一个静态工厂函数public static func of(elements: Array<T>): ArrayList<T>,用于从Array<T>快速构造ArrayList<T>。

需要区分两个容易混淆的概念:size是当前元素个数,capacity是底层预留的容量。API 文档中两者都是只读属性:

public prop size: Int64 // 返回此 ArrayList 中的元素个数 public prop capacity: Int64 // 返回此 ArrayList 的容量大小

3. 访问元素与 size

3.1 for-in 遍历全部元素

当需要访问ArrayList的所有元素时,可以使用for-in循环迭代:

import std.collection.* main() { let list = ArrayList<Int64>([0, 1, 2]) for (i in list) { println("The element is ${i}") } }

编译并执行上面的代码,会输出:

The element is 0 The element is 1 The element is 2

for-in的底层机制由iterator()方法支撑(见 collection_package_class.md):

public func iterator(): Iterator<T>

它返回一个Iterator<T>元素迭代器。除for-in外,仓颉的pipeline表达式也能与集合操作函数组合使用,例如:

import std.collection.* main() { let arr = [-1, 2, 3, 4, 5, 6, 7, 8, 9] arr |> filter{a: Int64 => a > 0} |> // filter -1 step<Int64>(2) |> // [2, 4, 6, 8] skip<Int64>(2) |> // [6, 8] forEach<Int64>(println) let str = arr |> filter{a: Int64 => a % 2 == 1} |> collectString<Int64>(delimiter: ">") println(str) println(arr |> contains(6_i64)) return 0 }

运行结果为6、8、3>5>7>9、true(完整示例见 sample_iterator.md)。

3.2 下标访问单个元素

当需要访问单个指定位置的元素时,可以使用下标语法(下标类型必须是Int64)。非空ArrayList的第一个元素从位置 0 开始,可以从 0 访问到最后一个位置(size - 1)。使用负数或大于等于size的索引会触发运行时异常:

let a = list[0] // a == 0 let b = list[1] // b == 1 let c = list[-1] // Runtime Exceptions

从 API 文档看,下标操作是[]操作符重载(get 与 set 两个版本),索引越界时抛出IndexOutOfBoundsException:

public operator func [](index: Int64): T // 读取 public operator func [](index: Int64, value!: T): Unit // 写入

如果希望安全地读取某个位置(越界时不抛异常),可以使用get(index: Int64): ?T方法:索引小于 0 或大于等于元素数量时返回None,配合getOrThrow()取出值。仓库示例 sample_arraylist_get_set.md 演示了这一点:

import std.collection.* main() { var list = ArrayList<Int64>([97, 100]) // list: [97, 100] list[1] = 120 // 通过下标修改元素,list: [97, 120] var b = list.get(1) // 通过 get 安全获取,返回 Option<Int64> print("b=${b.getOrThrow()}") return 0 }

运行结果:b=120。

除下标外,ArrayList还暴露了两个便捷属性,返回值是Option类型,空列表时返回None:

public prop first: ?T // 返回第一个元素,如果没有则返回 None public prop last: ?T // 返回最后一个元素,如果没有则返回 None

3.3 size 属性

获取ArrayList的元素个数,直接使用size属性(是属性而非函数,后面不带括号):

import std.collection.* main() { let list = ArrayList<Int64>([0, 1, 2]) if (list.size == 0) { println("This is an empty arraylist") } else { println("The size of arraylist is ${list.size}") } }

编译并执行上面的代码,会输出:

The size of arraylist is 3

判断是否为空也可以直接调用isEmpty(): Bool方法。

4. 添加元素:add 与 add(all:)

4.1 末尾追加单个元素

向ArrayListarr的末尾添加元素i,使用arr.add(i),其签名为:

public func add(element: T): Unit

4.2 末尾追加整个集合

如果希望一次性把同类型的一组元素追加到末尾,可以使用带all!命名参数的add函数,它接受其他相同元素类型的Collection类型(例如Array),并按迭代器顺序把所有元素插入尾部:

public func add(all!: Collection<T>): Unit
let arr = ArrayList<Int64>([1, 2, 3]) let arr2 = [3, 4, 5] arr.add(all: arr2) // arr 变为 [1, 2, 3, 3, 4, 5]

一个更完整的仓库示例(来自 sample_arraylist_add.md)展示了add、下标修改与get的组合:

import std.collection.* main() { var list: ArrayList<Int64> = ArrayList<Int64>(10) // 创建一个容量为 10 的 ArrayList var arr: Array<Int64> = [1, 2, 3] list.add(all: arr) // list: [1, 2, 3] list[1] = 120 // list: [1, 120, 3] var b = list.get(2) print("b=${b.getOrThrow()},") list.add(12, at: 1) // list: [1, 12, 120, 3] var c = list.get(2) print("c=${c.getOrThrow()},") var arr1: Array<Int64> = [1, 2, 3] list.add(all: arr1, at: 1) // list: [1, 1, 2, 3, 12, 120, 3] var d = list.get(2) print("d=${d.getOrThrow()}") return 0 }

运行结果:

b=3,c=120,d=2

5. 在指定位置插入元素

add系列函数还支持在指定索引处插入。单元素版本与集合版本的签名分别为:

public func add(element: T, at!: Int64): Unit public func add(all!: Collection<T>, at!: Int64): Unit

插入时,at索引处的元素及其后面的元素会整体后移以腾出空间。注意at超出范围时会抛出IndexOutOfBoundsException。

let list = ArrayList<Int64>([0, 1, 2]) // list 包含元素 0, 1, 2 list.add(4, at: 1) // list 变为 0, 4, 1, 2 list.add(all: [1, 2, 3], at: 4) // list 变为 0, 4, 1, 2, 1, 2, 3

从源码结构看,集合版本会按迭代器顺序遍历入参集合,逐个插入到指定位置,原先在该位置及其后的元素依次后移。这与末尾追加版本的区别仅在于插入锚点不同,但中间插入需要搬移后续元素,因此在中部频繁插入的成本高于尾部追加——这也是动态数组这类结构的基本特性。

6. 删除元素:remove、remove 区间、removeIf 与 clear

6.1 按索引删除

删除单个元素使用remove函数,需要指定被删除元素的索引,该索引后面的元素会前移以填充空位。与get不同,按索引删除的remove会返回被删除的元素:

public func remove(at!: Int64): T
let list = ArrayList<String>(["a", "b", "c", "d"]) // list 包含 "a", "b", "c", "d" let removed = list.remove(at: 1) // 删除下标 1 的元素 // removed == "b",list 现在包含 "a", "c", "d"

at越界时同样抛出IndexOutOfBoundsException。

6.2 按 Range 删除

删除一段连续元素使用接受Range<Int64>的重载:

public func remove(range: Range<Int64>): Unit
let list = ArrayList<String>(["a", "b", "c", "d"]) list.remove(1..2) // 删除 [1, 2) 区间的元素,list 变为 "a", "c", "d" list.remove(1..=2) // 删除 [1, 2] 闭区间的元素,list 变为 "a"

其中l..r表示左闭右开区间[l, r),l..=r表示左闭右闭区间[l, r]。API 文档还提示了两个异常边界:当range的step不等于 1 时抛出IllegalArgumentException;当range的start或end小于 0,或end大于数组长度时抛出IndexOutOfBoundsException。

6.3 按条件删除与清空

如果需要按条件批量删除,可以使用removeIf,它接受一个(T) -> Bool的判定函数,删除所有满足条件的元素:

public func removeIf(predicate: (T) -> Bool): Unit

需要注意的是,如果在predicate中对ArrayList内部元素进行增删或修改,会抛出ConcurrentModificationException(并发修改异常,定义见 collection_package_exception.md)。

清空全部元素则使用clear(),它删除所有元素但保留已分配的容量:

public func clear(): Unit

remove/clear/slice组合的完整仓库示例见 sample_arraylist_remove_clear_slice.md:

import std.collection.* main() { var list: ArrayList<Int64> = ArrayList<Int64>([97, 100, 99]) list.remove(at: 1) // list: [97, 99] var b = list.get(1) print("b=${b.getOrThrow()},") list.clear() list.add(11) // list: [11] var arr: Array<Int64> = [1, 2, 3] list.add(all: arr, at: 0) // list: [1, 2, 3, 11] var g = list.get(0) print("g=${g.getOrThrow()},") let r: Range<Int64> = 1..=2 : 1 var sublist: ArrayList<Int64> = list.slice(r) // sublist: [2, 3] var m = sublist.get(0) print("m=${m.getOrThrow()}") return 0 }

运行结果:

b=99,g=1,m=2

7. ArrayList 的切片:Range 语义与 Array 的关键差异

ArrayList的slice函数签名为:

public func slice(range: Range<Int64>): ArrayList<T>

它接收一个Range 参数:l..r表示左闭右开区间[l, r),[l, r]或l..=r表示左闭右闭区间[l, r]。这里有一个必须记住的差异——Array的slice接受起始下标和长度两个参数,而ArrayList的slice接受 Range(对比见 extra/Array.md):

// Array:第二个参数是切片的“长度” public func slice(start: Int64, len: Int64): Array<T> // ArrayList:参数是 Range 区间 public func slice(range: Range<Int64>): ArrayList<T>

ArrayList也支持下标中直接使用Range的运算符重载,等价于切片操作:

let list = ArrayList<Int64>([0, 1, 2, 3, 4, 5, 6]) let part = list[1..4] // 等价于 list.slice(1..4),包含元素 1, 2, 3

两个值得注意的行为细节(来自 API 文档对slice与[](Range<Int64>)的说明):

  1. 若range由Range构造函数构造且hasEnd为false,则end值不生效,切片会一直取到原数组最后一个元素;start的取值也不受hasStart影响;
  2. 切片返回的是全新的ArrayList<T>对象,与原ArrayList没有引用关系,对切片的修改不会影响原列表。

同样地,range.step不等于 1 时抛IllegalArgumentException,range无效时抛IndexOutOfBoundsException。

8. 迭代细节:for-in 与元组元素

迭代ArrayList元素的标准写法是:

for (element in arr) { ... }

注意in之前没有模式匹配(不能写成for ((x, y) in arr)这样的形式)。如果元素本身是元组,需要逐个按下标访问元组成员:

var a = ArrayList<(Int64, Int64)>([(2, 3), (3, 4)]) for (pair in a) { println("(${pair[0]}, ${pair[1]})") }

运行输出为(2, 3)与(3, 4)两行。

9. 容量扩展策略与性能建议

每个ArrayList都需要特定数量的内存来保存其内容。当添加元素使列表超出当前保留容量时,ArrayList会分配更大的内存区域并把所有元素复制到新内存中——这种增长策略意味着:触发重新分配的添加操作有性能成本,但随着保留内存变大,这种重新分配发生的频率会越来越低。

如果已知大约需要添加多少个元素,最好在添加之前预留足够的容量以避免中间的重分配:

import std.collection.* main() { let list = ArrayList<Int64>(100) // 一次性分配 100 的空间 for (i in 0..100) { list.add(i) // 不会触发空间重分配 } list.reserve(100) // 再预留 100 的空间 for (i in 0..100) { list.add(i) // 不会触发空间重分配 } }

reserve的签名与扩容规则在 API 文档中有明确定义(见 collection_package_class.md):

public func reserve(additional: Int64): Unit
  • additional小于等于 0 时不发生扩容;
  • 剩余容量(capacity - size)已经大于等于additional时不发生扩容;
  • 剩余容量小于additional时,取「原始容量的 1.5 倍向下取整」与「additional + 已使用容量」两个值中的最大值进行扩容;
  • 当additional + 已使用容量超过Int64.Max时抛出OverflowException。

从 1.5 倍的扩容因子可以推断:连续尾部追加的均摊成本是常数级的,但每一次真正触发重分配时都是 O(n) 的整体搬移。因此「先预估、后reserve」是ArrayList最重要的性能实践。

10. 引用语义:共享数据而非拷贝

ArrayList是引用类型:作为表达式使用时不会拷贝副本,同一个ArrayList实例的所有引用共享同样的数据,对元素的修改会反映到所有引用上。这与定长Array的行为一致:

let list1 = ArrayList<Int64>([0, 1, 2]) let list2 = list1 list2[0] = 3 // list1 包含元素 3, 1, 2 // list2 包含元素 3, 1, 2

如果确实需要一份独立副本,API 提供了浅拷贝方法clone():

public func clone(): ArrayList<T> // 返回此 ArrayList 实例的拷贝(浅拷贝)

此外还有几个实用成员函数值得了解:

  • toArray(): Array<T>:返回一个包含按正确顺序排列的所有元素的Array<T>;
  • reverse(): Unit:原地反转元素顺序,返回Unit;
  • getRawArray(): Array<T>:unsafe接口,返回底层实现数组。由于容量大于等于元素数量,索引大于等于size的位置可能包含未初始化元素,访问会产生未定义行为,只能在 unsafe 上下文中谨慎使用;
  • ==/!=:ArrayList<T>通过扩展实现了Equatable<ArrayList<T>>接口(要求T <: Equatable<T>),判等语义是对应位置的元素分别相等,而非引用同一性;
  • sortBy(comparator)/sortBy(stable, comparator):自定义比较函数排序,文档标注为 deprecated,建议改用std.sort包的sort函数(见 sort_package_funcs.md)。

11. 小结

  • 导入与选型:import std.collection.*;需要定长、只改元素选Array,需要频繁增删选ArrayList;
  • 初始化:空列表(默认容量 16)、预分配容量、lambda 按规则生成、从既有Collection复制四类入口,容量或size为负会抛IllegalArgumentException;
  • 访问:for-in遍历、[]下标(越界抛IndexOutOfBoundsException)、get(i): ?T安全读取、first/last/size/capacity/isEmpty();
  • 增:add(element)尾部追加、add(all:)集合追加、add(element, at:)与add(all:, at:)按索引插入(后续元素后移);
  • 删:remove(at:)按索引删除并返回被删元素、remove(range)按区间删除、removeIf(predicate)按条件删除、clear()清空;
  • 切片:slice(range)与list[range],注意与Array.slice(start, len)的参数语义不同,切片结果是全新独立对象;
  • 性能:重分配是扩容成本所在,预估规模后用构造参数或reserve(additional)预留容量(扩容取 1.5 倍与需求量最大值);
  • 语义:ArrayList是引用类型,赋值与传参共享同一份数据,需要独立副本时调用clone()。

仓库中相关的延伸阅读:ArrayList完整 API 与字段说明见 collection_package_class.md,中文手册章节见 collection_arraylist.md,定长数组Array的使用见 array.md。

  • 文档
  • 知识库
  • 教程
  • OpenHarmony

【免费下载链接】CangjieCorpus

本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!

项目地址:https://gitcode.com/Cangjie/CangjieCorpus
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询