Dart: Dart 集合 — List / Set / Map 操作与实战
集合是数据的军队 — 列表有序、集合去重、字典索引,三者组合天下无敌。
1. 你将学到
- List:创建 / 遍历 / 排序 / 展开(spread
...)与 if-for 集合元素 - Set:去重与集合运算(交集 / 并集 / 差集)
- Map:键值操作与遍历
- 集合类型推断与 const 集合
- Bob 场景:百万订单数据分组聚合
2. 一个开发者的真实故事
(1) 痛点:手动分组聚合耗时且易错
Bob 需要将 1,200,000 条订单按类别分组,计算每类的总收入和订单数。他用 for 循环手动实现分组逻辑,写了 50 行代码,但遇到了重复键覆盖、排序不稳定等问题,而且运行 1,200,000 条数据需要 8 秒。
(2) 集合操作的解法
Dart 的集合操作结合高阶函数,让分组聚合只需 10 行代码,运行速度提升 40%。
// Group orders by category and aggregate
final byCategory = <String, List``<Order>``>{};
for (final order in orders) {
byCategory.putIfAbsent(order.category, () => []).add(order);
}
// Or use fold for aggregation
final revenueByCategory = orders.fold<Map<String, double>>(
{}, (acc, o) => acc..update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount));
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(3) 收益
- 代码量减少 80%,逻辑更清晰
- Set 去重避免重复数据,Map 分组替代手动索引
- spread 和 collection-if 让集合构建更声明式
3. List 列表
(1) 创建与基本操作
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:List 创建
void main() {
// Literal creation
List``<int>`` counts = [1, 2, 3, 4, 5];
var amounts = ``<double>``[1500.0, 3200.0, 890.0];
// Growable list
var orders = ``<String>``['ORD-001', 'ORD-002'];
orders.add('ORD-003');
// Fixed-length list
var buffer = List``<double>``.filled(5, 0.0);
buffer[0] = 100.0;
// Generate list
var batch = List.generate(10, (i) => 'Batch-${i + 1}');
print(counts); // [1, 2, 3, 4, 5]
print(orders); // [ORD-001, ORD-002, ORD-003]
print(buffer); // [100.0, 0.0, 0.0, 0.0, 0.0]
print(batch); // [Batch-1, ..., Batch-10]
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:List 遍历与排序
void main() {
var amounts = [1500.0, 890.0, 3200.0, 50.0];
// Sort in-place
amounts.sort();
print(amounts); // [50.0, 890.0, 1500.0, 3200.0]
// Sort descending (create new list)
var descending = [...amounts]..sort((a, b) => b.compareTo(a));
print(descending); // [3200.0, 1500.0, 890.0, 50.0]
// Access elements
print(amounts.first); // 50.0
print(amounts.last); // 3200.0
print(amounts[2]); // 1500.0
// Sublist
print(amounts.sublist(1, 3)); // [890.0, 1500.0]
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Spread 与 Collection-if/for
void main() {
var base = ['ORD-001', 'ORD-002'];
var extra = ['ORD-003', 'ORD-004'];
// Spread operator
var combined = [...base, ...extra];
print(combined); // [ORD-001, ORD-002, ORD-003, ORD-004]
// Null-aware spread
List``<String>``? maybeList;
var safe = ['header', ...?maybeList, 'footer'];
print(safe); // [header, footer]
// Collection-if
bool includePremium = true;
var tiers = [
'Standard',
if (includePremium) 'Premium',
'Enterprise',
];
print(tiers); // [Standard, Premium, Enterprise]
// Collection-for
var batch = [
for (int i = 1; i <= 3; i++) 'Batch-$i',
];
print(batch); // [Batch-1, Batch-2, Batch-3]
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 操作 | 语法 | 说明 |
|---|---|---|
| spread | ...list |
展开元素 |
| null-aware spread | ...?list |
展开可空列表 |
| collection-if | if (cond) expr |
条件包含 |
| collection-for | for (var x in list) expr |
循环生成 |
4. Set 集合
(1) 创建与集合运算
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Set 去重与运算
void main() {
// Create from list (removes duplicates)
var allOrders = ['ORD-001', 'ORD-002', 'ORD-001', 'ORD-003'];
var uniqueOrders = ``<String>``{...allOrders};
print(uniqueOrders); // {ORD-001, ORD-002, ORD-003}
// Set operations
var setA = {1, 2, 3, 4, 5};
var setB = {4, 5, 6, 7, 8};
print(setA.intersection(setB)); // {4, 5} - Intersection
print(setA.union(setB)); // {1, 2, 3, 4, 5, 6, 7, 8} - Union
print(setA.difference(setB)); // {1, 2, 3} - Difference
// Membership test
print(setA.contains(3)); // true
print(setA.containsAll({1, 2})); // true
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Set 在去重场景的应用
void main() {
// Deduplicate categories from order list
var orders = [
{'id': 'ORD-001', 'category': 'Electronics'},
{'id': 'ORD-002', 'category': 'Books'},
{'id': 'ORD-003', 'category': 'Electronics'},
{'id': 'ORD-004', 'category': 'Clothing'},
{'id': 'ORD-005', 'category': 'Books'},
];
var categories = orders.map((o) => o['category'] as String).toSet();
print(categories); // {Electronics, Books, Clothing}
print('Unique categories: ${categories.length}'); // 3
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 运算 | 方法 | 结果 |
|---|---|---|
| 交集 | a.intersection(b) |
共有元素 |
| 并集 | a.union(b) |
全部元素 |
| 差集 | a.difference(b) |
a 中不在 b 的 |
| 包含 | a.contains(e) |
bool |
| 全包含 | a.containsAll(b) |
bool |
5. Map 字典
(1) 创建与操作
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Map 创建与基本操作
void main() {
// Literal creation
var config = <String, dynamic>{
'appName': 'DataPipeline',
'maxRecords': 1000000,
'taxRate': 0.08,
'verbose': true,
};
// Access values
print(config['appName']); // DataPipeline
print(config['missing']); // null
print(config['missing'] ?? 'N/A'); // N/A
// Add/update entries
config['outputPath'] = '/tmp/reports'; // Add
config['taxRate'] = 0.10; // Update
// Safe update
config.update('maxRecords', (v) => v * 2, ifAbsent: () => 500000);
// Iterate
config.forEach((key, value) => print(' $key: $value'));
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:Map 遍历与聚合
void main() {
var orders = [
{'id': 'ORD-001', 'category': 'Electronics', 'amount': 1500.0},
{'id': 'ORD-002', 'category': 'Books', 'amount': 50.0},
{'id': 'ORD-003', 'category': 'Electronics', 'amount': 3200.0},
{'id': 'ORD-004', 'category': 'Clothing', 'amount': 890.0},
{'id': 'ORD-005', 'category': 'Books', 'amount': 120.0},
];
// Group by category
final byCategory = <String, List<Map<String, dynamic>>>{};
for (final order in orders) {
final cat = order['category'] as String;
byCategory.putIfAbsent(cat, () => []).add(order);
}
// Aggregate revenue by category
final revenue = <String, double>{};
for (final entry in byCategory.entries) {
final total = entry.value.fold``<double>``(
0, (sum, o) => sum + (o['amount'] as double));
revenue[entry.key] = total;
}
// Print results
revenue.forEach((cat, total) =>
print('$cat: \$${total.toStringAsFixed(2)} USD'));
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
6. 集合类型推断与 const 集合
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:类型推断与 const 集合
void main() {
// Type inference
var list1 = [1, 2, 3]; // List``<int>``
var list2 = [1, 2, 3.0]; // List``<num>`` (mixed int and double)
var list3 = ``<String>``[]; // Explicit generic for empty list
// Const collections - deeply immutable
const categories = ['Electronics', 'Books', 'Clothing'];
const config = <String, int>{'batchSize': 10000, 'timeout': 30};
const flags = {true, false};
// categories.add('Sports'); // Error! Cannot modify const
// Const in class
const defaultFormats = ['json', 'csv', 'html'];
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 特性 | var | final | const |
|---|---|---|---|
| 可变内容 | 可 | 可(引用不变) | 不可 |
| 类型推断 | 自动 | 自动 | 自动 |
| 编译时常量 | 否 | 否 | 是 |
7. Bob 场景:百万订单分组聚合
flowchart LR A[List: 1.2M Orders] --> B[where: Filter] B --> C[groupBy: Category] C --> D[Map: Category → Orders] D --> E[fold: Aggregate] E --> F[Output: Statistics]
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:分组聚合实战
class Order {
final String id;
final String category;
final double amount;
final String status;
Order({required this.id, required this.category, required this.amount, required this.status});
}
Map<String, Map<String, dynamic>> analyzeOrders(List``<Order>`` orders) {
// Filter completed orders
final completed = orders.where((o) => o.status == 'completed').toList();
// Group by category
final grouped = <String, List``<Order>``>{};
for (final order in completed) {
grouped.putIfAbsent(order.category, () => []).add(order);
}
// Aggregate per category
final result = <String, Map<String, dynamic>>{};
for (final entry in grouped.entries) {
final total = entry.value.fold``<double>``(0, (s, o) => s + o.amount);
final avg = total / entry.value.length;
result[entry.key] = {
'count': entry.value.length,
'total': total,
'average': avg,
};
}
return result;
}
void main() {
final orders = [
Order(id: 'ORD-001', category: 'Electronics', amount: 1500.0, status: 'completed'),
Order(id: 'ORD-002', category: 'Books', amount: 50.0, status: 'completed'),
Order(id: 'ORD-003', category: 'Electronics', amount: 3200.0, status: 'pending'),
Order(id: 'ORD-004', category: 'Clothing', amount: 890.0, status: 'completed'),
Order(id: 'ORD-005', category: 'Books', amount: 120.0, status: 'completed'),
];
final analysis = analyzeOrders(orders);
print('=== Category Analysis ===');
for (final entry in analysis.entries) {
final stats = entry.value;
print('${entry.key}:');
print(' Orders: ${stats['count']}');
print(' Total: \$${(stats['total'] as double).toStringAsFixed(2)} USD');
print(' Average: \$${(stats['average'] as double).toStringAsFixed(2)} USD');
}
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
8. 完整示例:DataPipeline 集合分析器
// ============================================
// DataPipeline Collection Analyzer
// Full demonstration of List, Set, Map operations
// ============================================
class Order {
final String id;
final String category;
final double amount;
final String status;
final String region;
const Order({
required this.id,
required this.category,
required this.amount,
required this.status,
this.region = 'US',
});
}
class OrderAnalyzer {
final List``<Order>`` orders;
OrderAnalyzer(this.orders);
// Unique categories (Set)
Set``<String>`` get categories => orders.map((o) => o.category).toSet();
// Unique regions (Set)
Set``<String>`` get regions => orders.map((o) => o.region).toSet();
// Completed orders (List filter)
List``<Order>`` get completed =>
orders.where((o) => o.status == 'completed').toList();
// Total revenue (fold)
double get totalRevenue =>
completed.fold(0.0, (sum, o) => sum + o.amount);
// Revenue by category (Map grouping)
Map<String, double> get revenueByCategory {
return completed.fold<Map<String, double>>({}, (acc, o) {
acc.update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount);
return acc;
});
}
// Order count by region and status
Map<String, Map<String, int>> get countByRegionStatus {
final result = <String, Map<String, int>>{};
for (final order in orders) {
result.putIfAbsent(order.region, () => {});
result[order.region]!.update(
order.status, (v) => v + 1, ifAbsent: () => 1);
}
return result;
}
// Top N categories by revenue
List<MapEntry<String, double>> topCategories(int n) {
final sorted = revenueByCategory.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
return sorted.take(n).toList();
}
void printReport() {
print('=== DataPipeline Analytics Report ===');
print('Total orders: ${orders.length}');
print('Completed: ${completed.length}');
print('Unique categories: ${categories.length}');
print('Regions: ${regions}');
print('Total revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
print('\n--- Revenue by Category ---');
for (final entry in revenueByCategory.entries) {
print(' ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
}
print('\n--- Top Categories ---');
for (final entry in topCategories(3)) {
print(' ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
}
print('\n--- Orders by Region & Status ---');
for (final region in countByRegionStatus.entries) {
print(' ${region.key}: ${region.value}');
}
}
}
void main() {
final orders = [
Order(id: 'ORD-001', category: 'Electronics', amount: 1500.0, status: 'completed', region: 'US'),
Order(id: 'ORD-002', category: 'Books', amount: 50.0, status: 'completed', region: 'EU'),
Order(id: 'ORD-003', category: 'Electronics', amount: 3200.0, status: 'pending', region: 'US'),
Order(id: 'ORD-004', category: 'Clothing', amount: 890.0, status: 'completed', region: 'EU'),
Order(id: 'ORD-005', category: 'Books', amount: 120.0, status: 'completed', region: 'US'),
Order(id: 'ORD-006', category: 'Electronics', amount: 2100.0, status: 'completed', region: 'AP'),
];
OrderAnalyzer(orders).printReport();
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
输出:
=== DataPipeline Analytics Report ===
Total orders: 6
Completed: 5
Unique categories: 3
Regions: {US, EU, AP}
Total revenue: $4660.00 USD
--- Revenue by Category ---
Electronics: $3600.00 USD
Clothing: $890.00 USD
Books: $170.00 USD
--- Top Categories ---
Electronics: $3600.00 USD
Clothing: $890.00 USD
Books: $170.00 USD
--- Orders by Region & Status ---
US: {completed: 2, pending: 1}
EU: {completed: 2}
AP: {completed: 1}
❓ 常见问题
Q:List 和 Set 的性能差异? A:List 按插入顺序存储,O(1) 索引访问,O(n) 查找;Set 基于哈希,O(1) 查找/包含检查,无索引访问。去重场景用 Set,有序场景用 List。
Q:Map 的键可以是任意类型吗? A:可以是任何类型,但自定义类作为键时必须重写
==和hashCode,否则 Map 无法正确查找。推荐用 String 或 int 作为键。
**Q:空 List 用
[]还是<Type>``[]`?** A:在有类型推断上下文时用 `[]` 即可;独立声明时用[]`,否则推断为 `List```。
Q:collection-if 和 for 只能在 List 字面量中使用吗? A:不是,List、Set、Map 字面量都支持 collection-if 和 collection-for。如
{for (var i in items) i.name: i.value}。
Q:Map 的 putIfAbsent 和 update 有什么区别? A:putIfAbsent 只在键不存在时设置值;update 只在键存在时更新值(可通过 ifAbsent 参数处理不存在的情况)。
Q:const 集合和 final 集合的区别? A:const 集合是编译时常量,内容完全不可变(深度不可变);final 集合引用不可变,但内容可变(如 final List 可 add)。
Q:如何高效地对大 List 排序? A:List.sort() 是原地排序,比 [...list].sort()(创建副本)更高效。如果不想修改原列表,先用 [...list] 创建副本再排序。
📖 小节
- List 有序可重复,支持 spread(...)、collection-if/for 声明式构建
- Set 无序不重复,O(1) 查找,支持交集/并集/差集运算
- Map 键值对存储,putIfAbsent/update 是分组聚合的利器
- const 集合深度不可变,final 集合引用不可变但内容可变
- 高阶函数 + 集合操作让数据处理从 50 行缩减到 10 行
📝 作业
- 基础题(难度⭐):创建一个包含 10 个订单金额的 List,用 where 过滤出大于 500 的,用 map 转换为 USD 格式,用 reduce 求总和。
- 进阶题(难度⭐⭐):给定一组订单数据,用 Map 的 putIfAbsent 和 fold 实现按类别分组并计算每类的订单数和总收入,输出格式化报表。
- 挑战题(难度⭐⭐⭐):实现一个
CollectionPipeline类,支持链式调用 where、map、sortBy、groupBy、aggregate 操作,最终输出分析结果。提示:每步返回新的集合或 Map。