Dart: Dart 集合 — List / Set / Map 操作与实战

集合是数据的军队 — 列表有序、集合去重、字典索引,三者组合天下无敌。

1. 你将学到


2. 一个开发者的真实故事

(1) 痛点:手动分组聚合耗时且易错

Bob 需要将 1,200,000 条订单按类别分组,计算每类的总收入和订单数。他用 for 循环手动实现分组逻辑,写了 50 行代码,但遇到了重复键覆盖、排序不稳定等问题,而且运行 1,200,000 条数据需要 8 秒。

(2) 集合操作的解法

Dart 的集合操作结合高阶函数,让分组聚合只需 10 行代码,运行速度提升 40%。

DART
// 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));
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

(3) 收益


3. List 列表

(1) 创建与基本操作

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:List 创建

DART
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]
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:List 遍历与排序

DART
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]
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Spread 与 Collection-if/for

DART
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]
}
TEXT 📖 仅展示
> **输出:** 在本地 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) 创建与集合运算

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Set 去重与运算

DART
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
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Set 在去重场景的应用

DART
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
}
TEXT 📖 仅展示
> **输出:** 在本地 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) 创建与操作

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Map 创建与基本操作

DART
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'));
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:Map 遍历与聚合

DART
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'));
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

6. 集合类型推断与 const 集合

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:类型推断与 const 集合

DART
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'];
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
特性 var final const
可变内容 可(引用不变) 不可
类型推断 自动 自动 自动
编译时常量

7. Bob 场景:百万订单分组聚合

100%
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]
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

:分组聚合实战

DART
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');
  }
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

8. 完整示例:DataPipeline 集合分析器

DART
// ============================================
// 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();
}
TEXT 📖 仅展示
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。

输出:

TEXT 📖 仅展示
=== 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] 创建副本再排序。


📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个包含 10 个订单金额的 List,用 where 过滤出大于 500 的,用 map 转换为 USD 格式,用 reduce 求总和。
  2. 进阶题(难度⭐⭐):给定一组订单数据,用 Map 的 putIfAbsent 和 fold 实现按类别分组并计算每类的订单数和总收入,输出格式化报表。
  3. 挑战题(难度⭐⭐⭐):实现一个 CollectionPipeline 类,支持链式调用 where、map、sortBy、groupBy、aggregate 操作,最终输出分析结果。提示:每步返回新的集合或 Map。

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏