集合(Collection)
Dart 中的集合(Collection)是什么
集合是 存放一组数据的容器,是 Dart 核心库 dart:core
提供的基础数据结构
Dart 集合的特点:强类型(除非用 dynamic
)、支持泛型、支持迭代
集合三大类
类型 | 描述 | 是否有序 | 是否允许重复元素 | 常见用途 |
---|---|---|---|---|
List | 有序集合,类似数组 | ✅ 有序 | ✅ 允许重复 | 顺序存储数据,如学生名单 |
Set | 无序且唯一元素集合 | ❌ 无序(Dart 默认 Set 是无序的,但 LinkedHashSet 可以有序) |
❌ 不允许重复 | 存放唯一值,如标签列表 |
Map | 键值对集合(key-value) | ❌ key 无序(但 LinkedHashMap 保序) |
key 唯一,value 可重复 | 存储关联数据,如配置表 |
三者的关系
dart:core
├── Iterable<E>
│ ├── List<E> (有序, 允许重复)
│ └── Set<E> (无序, 不重复)
└── Map<K, V> (键值对, key 唯一)
List、Set、Map 都属于 Dart 集合类型(Collection) 的实现
它们并不是继承自一个统一的 Collection
基类(Dart 没有 Collection
类),而是分别实现了不同的接口:
List<E>
实现了Iterable<E>
Set<E>
实现了Iterable<E>
Map<K, V>
并不继承Iterable
,但可以通过.entries
转成Iterable<MapEntry<K, V>>
共同点:
- 都支持泛型
- 都可用
for...in
或forEach
遍历(Map
需遍历entries
、keys
、values
) - 都是
dart:core
的核心数据结构
有序集合:List
什么是 List
List 是 有序集合,可以通过索引访问(从 0
开始)
Dart 中的 List 相当于 Java 里的 ArrayList
允许存储重复元素
定义 List 的方式
// 1. 自动推断类型
var numbers = [1, 2, 3];
// 2. 显式指定泛型
List<int> ages = [20, 25, 30];
// 3. 空列表(可变)
var emptyList = <String>[];
// 4. 不可变 List(常量)
const fixedList = [1, 2, 3];
定长List
固定长度 List(List.filled
):长度固定,不能 add/remove,但是可以改变里面的内容。
// 创建一个长度为 3 的 List,初始值为 0
// 第一个参数是长度,第二个参数是初始值
var l = List<int>.filled(3, 0);
l[0] = 1;
l[1] = 2;
l[2] = 3;
print(l); // [1, 2, 3]
不可变 List(const
):不能修改
const immutable = [1, 2, 3];
生成数据
基本语法
List.generate(
int length, // 要生成的长度
E generator(int index), // 根据索引生成元素的函数
{ bool growable = true } // 是否可变(默认 true)
)
length:生成的列表长度
generator:一个函数,接收当前索引 index
,返回该位置的值,**必须返回(return)**一个值(对应当前索引的元素)
growable:是否允许后续 add()
、remove()
(默认 true
)
// 生成一个 0, 1, 2, 3, 4 的列表
var list = List.generate(5, (index) => index);
print(list); // [0, 1, 2, 3, 4]
举例
// 生成 ["Item 0", "Item 1", ...]
var items = List.generate(5, (i) => 'Item $i');
print(items); // [Item 0, Item 1, Item 2, Item 3, Item 4]
上面的写法相当于下面:
var items = List.generate(5, (i) {
return 'Item $i';
});
print(items); // [Item 0, Item 1, Item 2, Item 3, Item 4]
为什么可以省略 return
- 规则:如果函数体是一个单一表达式,可以用
=>
省略{}
和return
,表达式结果会自动返回。 - 这是 Dart 语言的简化语法,不是说不需要返回值,而是 返回值写在箭头右边。
要生成一个包含时间戳、随机数和索引的列表:
import 'dart:math';
void main() {
var random = Random();
var list = List.generate(5, (index) {
var timestamp = DateTime.now().millisecondsSinceEpoch;
var rand = random.nextInt(100); // 0~99
return "Index:$index Time:$timestamp Rand:$rand";
});
print(list); // [Index:0 Time:169... Rand:42, Index:1 Time:169... Rand:87, ...]
}
常用属性
属性 | 说明 | 示例 |
---|---|---|
length |
元素数量 | list.length |
first |
第一个元素 | list.first |
last |
最后一个元素 | list.last |
isEmpty |
是否为空 | list.isEmpty |
isNotEmpty |
是否非空 | list.isNotEmpty |
reversed |
倒序(返回 Iterable) | list.reversed.toList() |
常用方法
方法 | 说明 | 示例 |
---|---|---|
add() |
追加元素 | list.add(4) |
addAll() |
追加多个元素 | list.addAll([5, 6]) |
insert(index, value) |
指定位置插入 | list.insert(1, 10) |
insertAll(index, list) |
插入多个 | list.insertAll(2, [7, 8]) |
remove(value) |
删除某值(只删第一个匹配) | list.remove(2) |
removeAt(index) |
删除指定位置 | list.removeAt(0) |
removeLast() |
删除最后一个 | list.removeLast() |
clear() |
清空 | list.clear() |
contains(value) |
是否包含 | list.contains(3) |
indexOf(value) |
查找索引 | list.indexOf(3) |
indexWhere(test,start) |
用来查找第一个满足条件的元素的 索引(位置),如果找不到满足条件的元素,返回 -1 |
list.indexWhere((item) => item.length > 5); |
sort() |
排序 | list.sort() |
var list = <int>[1, 2, 3];
// 常规写法(一步一步来)
list.add(4);
list.addAll([5, 6]);
list.insert(1, 10); // 在索引 1 插入 10
list.remove(2); // 删除值 2(只删第一个匹配)
list.sort(); // 排序
print(list); // [1, 3, 4, 5, 6, 10]
// 使用级联运算符 (..)
var anotherList = <int>[]
..add(7)
..addAll([8, 9])
..insert(0, 6)
..remove(9)
..sort();
print(anotherList); // [6, 7, 8]
级联运算符
级联运算符(Cascade Operator)是 Dart 语言里的一个特殊语法,写作 ..
,用于对同一个对象连续调用多个方法或访问多个属性,代码更简洁、易读。
- 允许你对同一个对象调用多个方法或属性赋值,而不需要重复写对象名
- 即使被调用的方法返回
void
,也能继续链式调用
var buffer = StringBuffer()
..write('Hello')
..write(' ')
..write('World')
..write('!');
print(buffer.toString()); // Hello World!
//======================等价于===============================
var buffer = StringBuffer();
buffer.write('Hello');
buffer.write(' ');
buffer.write('World');
buffer.write('!');
print(buffer.toString());
普通链式调用不能用,级联调用可以用
var list = <int>[];
// list.add(1).add(2); // ❌ 错误,add 返回 void,不能链式调用
list
..add(1)
..add(2)
..add(3);
print(list); // [1, 2, 3]
indexWhere
indexWhere
是 List 和 Iterable 类中常用的方法,用来查找第一个满足条件的元素的 索引(位置),如果找不到满足条件的元素,返回 -1
。
方法签名:
int indexWhere(bool test(E element), [int start = 0])
test
:一个返回 bool 的函数,传入当前元素,判断是否满足条件start
:可选参数,指定从哪个索引开始查找(默认从 0 开始)
使用示例:
void main() {
var list = ['apple', 'banana', 'orange', 'pineapple'];
// 找出第一个长度大于5的字符串的索引
int index = list.indexWhere((item) => item.length > 5);
print(index); // 1,"banana" 的长度是 6
}
找不到时返回 -1:
var list = ['a', 'b', 'c'];
var index = list.indexWhere((e) => e == 'z');
print(index); // -1,没有找到
从指定索引开始查找:
var list = ['a', 'b', 'c', 'b'];
var index = list.indexWhere((e) => e == 'b', 2);
print(index); // 3,从索引 2 开始找,找到索引 3
区间Range
fillRange
void fillRange(int start, int end, [E fillValue])
- 将指定范围
[start, end)
内的元素全部替换成同一个新值。 start
:起始索引(包含)end
:结束索引(不包含)fillValue
:填充值(默认null
)
void main() {
var list = [1, 2, 3, 4, 5];
list.fillRange(1, 4, 0); // 把索引 1 到 3 的元素替换成 0
print(list); // [1, 0, 0, 0, 5]
}
getRange
Iterable<E> getRange(int start, int end)
- 获取指定范围的元素子列表,返回一个
Iterable
,不修改原列表。 start
:起始索引(包含)end
:结束索引(不包含)
void main() {
var list = [10, 20, 30, 40, 50];
var sub = list.getRange(1, 4);
print(sub.toList()); // [20, 30, 40]
}
sublist
List<E> sublist(int start, [int? end])
sublist
是List
类的方法,用来 从原列表中截取指定范围的元素,返回一个新的列表(List)start
:起始索引(包含)end
:结束索引(不包含),可选,不写默认截取到末尾
void main() {
var list = [10, 20, 30, 40, 50];
var sub = list.sublist(1, 4);
print(sub); // [20, 30, 40]
}
sublist
返回的是一个新的 List 实例,是原列表指定范围元素的独立拷贝。因此,修改原列表的元素不会改变新列表内容,反之亦然。
void main() {
var list = [1, 2, 3, 4];
var sub = list.sublist(1, 3); // [2, 3]
list[1] = 99;
print(list); // [1, 99, 3, 4]
print(sub); // [2, 3],不变
}
相关方法
方法名 | 功能说明 | 备注 |
---|---|---|
replaceRange |
用另一个可迭代对象替换指定范围的元素 | 修改原列表 |
removeRange |
删除指定范围内的元素 | 修改原列表 |
setRange |
将指定范围内的元素替换为另一个列表的一部分 | 修改原列表 |
sublist |
返回指定范围的子列表,返回的是新的 List | 类似 getRange ,但返回 List |
void main() {
var list = [1, 2, 3, 4, 5];
// fillRange 替换元素
list.fillRange(1, 3, 9);
print(list); // [1, 9, 9, 4, 5]
// getRange 获取子集合(Iterable)
var range = list.getRange(1, 4);
print(range.toList()); // [9, 9, 4]
// replaceRange 替换范围
list.replaceRange(2, 4, [7, 8]);
print(list); // [1, 9, 7, 8, 5]
// removeRange 删除范围元素
list.removeRange(1, 3);
print(list); // [1, 8, 5]
// setRange 用另一个列表的元素替换指定范围
list.setRange(0, 2, [0, 0]);
print(list); // [0, 0, 5]
// sublist 返回新 List(拷贝)
var sublist = list.sublist(1, 3);
print(sublist); // [0, 5]
}
排序
shuffle
void shuffle([Random? random])
shuffle
用来 随机打乱列表中元素的顺序- 它是
List
类的方法,直接修改原列表,不会返回新列表 - 可以传入一个
Random
对象来自定义随机数源 - 如果不传,默认使用 Dart 内部的随机数生成器
import 'dart:math';
void main() {
var list = [1, 2, 3, 4, 5];
// 打乱列表中的元素顺序,每次调用都会生成一个新的随机顺序
// 因为使用了随机数生成器,所以每次运行结果可能不同
list.shuffle();
print(list); // 打乱后的顺序,例如 [3, 1, 5, 2, 4]
// 用自定义随机数种子
var rand = Random(42);
list.shuffle(rand); // 使用相同的种子,生成相同的随机顺序
print(list); // 总是生成相同的随机顺序,方便调试 [5, 3, 4, 2, 1]
}
sort
介绍
void sort([int compare(E a, E b)?])
sort
是 List 的一个方法,用于 对列表元素进行排序,直接修改原列表默认情况下,要求列表元素实现了
Comparable
接口(即支持比较大小)可选参数
compare
是一个比较函数,定义排序规则- 比较函数接收两个元素
a
和b
,返回整数:- 负数:
a
应该排在b
前面 - 0:两者相等
- 正数:
a
应该排在b
后面
- 负数:
- 比较函数接收两个元素
如果不传比较函数,元素必须实现
Comparable
接口,否则会报错内置类型(如
int
,double
,String
) 默认都实现了Comparable
,因此不传compare
也能正常排序,按它们的自然顺序(数字大小、字母顺序)排序。自定义类 如果没有实现
Comparable
,调用sort()
不传compare
会抛异常:Unhandled exception: type 'YourClass' is not a subtype of type 'Comparable'
如果自定义类实现了
Comparable
,就可以不传compare
,用类内定义的比较规则排序。
默认排序示例
void main() {
var list = [5, 3, 8, 1];
list.sort();
print(list); // [1, 3, 5, 8]
}
默认是升序排序,因为 int
实现了 Comparable
。
自定义排序示例
// 比如按字符串长度排序:
var list = ['apple', 'banana', 'kiwi', 'pear'];
list.sort((a, b) => a.length.compareTo(b.length));
print(list); // [kiwi, pear, apple, banana]
复杂对象排序
class Person {
String name;
int age;
Person(this.name, this.age);
}
void main() {
var people = [
Person('Alice', 30),
Person('Bob', 25),
Person('Charlie', 35),
];
// 按年龄升序排序
people.sort((a, b) => a.age.compareTo(b.age));
for (var p in people) {
print('${p.name}: ${p.age}');
}
}
输出:
Bob: 25
Alice: 30
Charlie: 35
实现Comparable
接口
var numbers = [3, 1, 4, 2];
numbers.sort(); // 正常,按数字升序排序
print(numbers); // [1, 2, 3, 4]
class Person {
String name;
int age;
Person(this.name, this.age);
}
var people = [
Person('Alice', 30),
Person('Bob', 25),
];
// people.sort(); // ❌ 报错,Person 没实现 Comparable
// 解决方案1:实现 Comparable
class Person2 implements Comparable<Person2> {
String name;
int age;
Person2(this.name, this.age);
@override
int compareTo(Person2 other) => age.compareTo(other.age);
}
var people2 = [
Person2('Alice', 30),
Person2('Bob', 25),
];
people2.sort(); // 正常,按年龄排序
操作符
索引操作符 []
和 []=
读取元素
var list = [10, 20, 30];
print(list[1]); // 20,访问索引为1的元素
赋值元素
list[1] = 25;
print(list); // [10, 25, 30]
+
操作符(连接两个列表)
作用是将两个列表连接成一个新列表,不修改原列表
var a = [1, 2];
var b = [3, 4];
var c = a + b;
print(c); // [1, 2, 3, 4]
print(a); // [1, 2],原列表不变
print(b); // [3, 4]
==
操作符(判断两个列表是否相等)
默认情况下,List
的 ==
是比较引用地址(是否是同一个对象)
要比较内容相等,可以用 ListEquality
(来自 collection
包)
var a = [1, 2];
var b = [1, 2];
print(a == b); // false,引用不同
import 'package:collection/collection.dart';
var eq = ListEquality();
print(eq.equals(a, b)); // true,内容相同
遍历 List
var list = ['a', 'b', 'c'];
// for 循环
for (int i = 0; i < list.length; i++) {
print(list[i]);
}
// for-in
for (var item in list) {
print(item);
}
// forEach
list.forEach((item) => print(item));
键值对集合:Map
Map 在 Dart 中就是 键值对(key-value pair) 的集合,类似于 Java 的 HashMap
,但 Dart 中的 Map 更加灵活。
定义 Map
字面量方式
var map1 = {
'name': 'Alice',
'age': 25,
};
// 空map
var map1 = {};
构造函数方式
var map2 = Map(); // 空 Map
map2['name'] = 'Bob';
map2['age'] = 30;
指定类型
var map3 = <String, int>{
'score1': 90,
'score2': 85,
};
和 Java 泛型类似,<String, int>
表示 Key 为 String,Value 为 int。
在 Dart 中,如果 Map 不指定类型,它会被推断为:Map<dynamic, dynamic>
。也就是说,key 和 value 都是 dynamic
,类型完全不受限制。
var map = {}; // Map<dynamic, dynamic>
map['name'] = 'Alice';
map[123] = true;
map[false] = 3.14;
print(map);
// 输出: {name: Alice, 123: true, false: 3.14}
常用属性
print(map1.length); // 键值对数量
print(map1.isEmpty); // 是否为空
print(map1.isNotEmpty); // 是否非空
print(map1.keys); // 所有键 (Iterable)
print(map1.values); // 所有值 (Iterable)
常用方法
方法 | 作用 |
---|---|
map[key] |
访问 key 对应的 value,key 不存在则返回 null |
map[key] = value |
设置/更新 key 对应的值 |
containsKey(key) |
是否包含某个 key |
containsValue(value) |
是否包含某个值 |
remove(key) |
删除指定 key |
removeWhere(condition) |
删除所有满足条件的键值对。 |
clear() |
清空 Map |
forEach((k, v) => {}) |
遍历键值对 |
putIfAbsent(key, () => value) |
如果 key 不存在则赋值 |
addAll(otherMap) |
合并另一个 Map |
addEntries(MapEntry) |
批量往 Map 里添加多个键值对(来自 Iterable<MapEntry<K, V>> )。如果 key 已存在,会直接覆盖原来的值。 |
update(key, (v) => newValue, ifAbsent: () => defaultValue) |
更新 key 对应的值 |
updateAll((v) => newValue) |
对 Map 中的所有 value 进行批量更新,key 不变。 |
var map = <String, dynamic>{
'name': 'Charlie',
'age': 28
};
// 添加/修改
map['age'] = 29;
// 判断
print(map.containsKey('name')); // true
// 删除
map.remove('age');
// 遍历
map.forEach((key, value) {
print('$key: $value');
});
// 如果 key 不存在才赋值
map.putIfAbsent('city', () => 'Beijing');
print(map); // {name: Charlie, city: Beijing}
// 合并 Map
map.addAll({'country': 'China', 'gender': 'Male'});
// 更新值
map.update('city', (value) => 'Shanghai');
map.update('zipcode', (value) => 10000, ifAbsent: () => 200000);
// 批量往 Map 里添加多个键值对
//适合把其他集合(List、Set 等)转成 MapEntry 再批量插入。
//避免多次 map[key] = value 的重复代码
var map2 = {'a': 1, 'b': 2};
map2.addEntries([
MapEntry('c', 3),
MapEntry('b', 20), // 覆盖 b 的值
]);
print(map2); // {a: 1, b: 20, c: 3}
// 删除所有符合条件的键值对:和 List.removeWhere 类似,只不过是对 Map。
// 参数是一个函数 (K key, V value) → bool,返回 true 的键值对会被移除。
var map3 = {'a': 1, 'b': 2, 'c': 3};
// 删除值大于 1 的键值对
map3.removeWhere((key, value) => value > 1);
print(map3); // {a: 1}
// 更新所有的键值对
var map4 = {'a': 1, 'b': 2, 'c': 3};
// 所有值 * 10
map4.updateAll((key, value) => value * 10);
print(map4); // {a: 10, b: 20, c: 30}
常用操作符
[]
(下标访问)
- 读取某个 key 对应的 value
- 写入某个 key 的 value(不存在会新增)
var map = {'a': 1, 'b': 2};
print(map['a']); // 1
map['c'] = 3; // 新增
print(map); // {a: 1, b: 2, c: 3}
==
(相等比较)
判断两个 Map 是否相等(key 和 value 都相等,且顺序无关)。
var m1 = {'a': 1, 'b': 2};
var m2 = {'b': 2, 'a': 1};
print(m1 == m2); // true
无序集合:Set
创建Set
使用字面量创建
var numbers = {1, 2, 3};
print(numbers); // {1, 2, 3}
使用 {}
且没有键值对时,Dart 会推断为 Set
。
Dart 在用字面量创建 Set
时会根据你放进去的元素 自动推断类型。
元素类型一致 → 推断为该类型的
Set
var names = {'Alice', 'Bob'}; // Set<String>
元素类型不同 → 推断为
Set<Object>
(或者更精确的公共父类型)var mixed = {1, 'two', 3.0}; print(mixed.runtimeType); // 输出:_Set<Object>
空字面量
{}
→ 默认推断为 Map,所以如果要创建空 Set 必须显式声明类型:var emptySet = <int>{}; // 空 Set<int>
构造函数
var emptySet = <int>{}; // 空 Set,指定类型
var stringSet = Set<String>(); // 另一种写法
// 如果不想指定类型
var stringSet = Set();
var a = <dynamic>{};
注意:
var s = {}; // 这是 Map,不是 Set
所以空 Set 要写成 <Type>{}
。
从 List 转为 Set
var list = [1, 2, 2, 3];
var uniqueSet = list.toSet();
print(uniqueSet); // {1, 2, 3} // 去重
常用属性
var numbers = {1, 2, 3};
print(numbers.length); // 元素个数
print(numbers.isEmpty); // 是否为空
print(numbers.isNotEmpty); // 是否非空
print(numbers.first); // 第一个元素
print(numbers.last); // 最后一个元素
常用方法
增加
方法 | 说明 | 示例 |
---|---|---|
add(element) |
添加单个元素(重复的会被忽略) | s.add(4); |
addAll(iterable) |
批量添加元素 | s.addAll([5, 6, 7]); |
删除
方法 | 说明 | 示例 |
---|---|---|
remove(element) |
删除指定元素 | s.remove(3); |
removeAll(elements) |
删除多个元素 | s.removeAll({1, 2}); |
removeWhere(predicate) |
按条件删除 | s.removeWhere((e) => e.isOdd); |
clear() |
清空 Set | s.clear(); |
查询
方法 | 说明 | 示例 |
---|---|---|
contains(element) |
判断是否包含元素 | s.contains(2) |
containsAll(elements) |
是否包含所有给定元素 | s.containsAll([1, 3]) |
遍历(Iterate)
for (var e in s) {
print(e);
}
s.forEach((e) => print(e));
交并差(Set 运算)
Dart Set
内置了集合运算方法(返回新的 Set,不改变原 Set):
var a = {1, 2, 3};
var b = {3, 4, 5};
print(a.union(b)); // 并集 {1, 2, 3, 4, 5}
print(a.intersection(b)); // 交集 {3}
print(a.difference(b)); // 差集 {1, 2}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com