1. 写在前面:为什么选择Dart?

作为Google亲儿子的编程语言,Dart凭借其在Flutter生态中的核心地位,近年来越来越受开发者关注。但抛开Flutter的"明星光环",Dart本身也是一门值得探索的现代编程语言。今天我们就从最基础的数据结构入手,看看这个在2011年诞生的语言,如何用简洁的语法帮我们解决实际问题。

2. 基础数据结构三剑客

2.1 List:你的万能口袋

就像哆啦A梦的次元袋,List能装下各种类型的数据。Dart中的List有两种打开方式:

// 固定长度的List(就像定制的收纳盒)
List<int> fixedList = List<int>.filled(3, 0); // 创建包含3个0的列表
fixedList[1] = 42; // 修改第二个元素

// 可变长度的List(像可伸缩的橡皮袋)
var dynamicList = [1, '二', 3.0]; // 混合类型也可以
dynamicList.add('新成员'); // 动态添加元素
dynamicList.removeAt(0); // 移除第一个元素

// 特别提示:Dart的List支持扩展运算符
var combined = [...fixedList, ...dynamicList]; // 合并两个列表

典型应用场景

  • 需要保持元素顺序的集合(如聊天记录)
  • 需要频繁通过索引访问元素
  • 需要实现类似栈(后进先出)的结构

2.2 Set:拒绝重复的洁癖患者

如果你需要保证元素的唯一性,Set就是你的最佳拍档。Dart使用{}语法糖让Set操作更直观:

var uniqueNumbers = {1, 2, 3};
var duplicateCheck = {1, 2, 2}; // 自动去重,实际存储{1, 2}

// 集合运算演示
var setA = {1, 2, 3};
var setB = {3, 4, 5};

print(setA.union(setB));     // 并集:{1,2,3,4,5}
print(setA.intersection(setB)); // 交集:{3}
print(setA.difference(setB));  // 差集:{1,2}

// 快速去重小技巧
var duplicatedList = [1, 2, 2, 3, 3];
var uniqueList = duplicatedList.toSet().toList(); // [1,2,3]

使用场景

  • 用户标签系统
  • IP地址白名单
  • 需要快速判断元素是否存在

2.3 Map:键值配对的百宝箱

当需要建立明确的对应关系时,Map就该闪亮登场了。Dart的Map语法简洁得让人感动:

// 创建方式一:字面量语法
var userInfo = {
  'name': '张三',
  'age': 28,
  'isVIP': true
};

// 创建方式二:构造函数
var config = Map<String, dynamic>();
config['theme'] = 'dark';
config['fontSize'] = 16;

// 安全访问技巧
print(userInfo['name'] ?? '未知用户'); // 使用空安全运算符
print(userInfo.containsKey('age') ? userInfo['age'] : 0);

// 遍历的三种姿势
userInfo.forEach((key, value) => print('$key: $value'));
for (var entry in userInfo.entries) {
  print('${entry.key} -> ${entry.value}');
}

典型用例

  • 用户配置存储
  • 数据缓存系统
  • API响应解析

3. 进阶数据结构挑战

3.1 Queue:排队专业户

当需要先进先出(FIFO)结构时,Dart的Queue就派上用场了:

import 'dart:collection';

void main() {
  var ticketQueue = Queue<String>();
  
  // 排队买票
  ticketQueue.add('王五');
  ticketQueue.add('赵六');
  ticketQueue.addFirst('VIP客户'); // 插队到队首
  
  print(ticketQueue.removeFirst()); // 输出VIP客户
  print(ticketQueue.length); // 剩余2人
  
  // 批量操作
  ticketQueue.addAll(['孙七', '周八']);
  ticketQueue.removeWhere((name) => name.length > 2); // 过滤长名字
}

适用场景

  • 消息队列处理
  • 浏览器历史记录
  • 订单处理系统

3.2 LinkedList:自定义链表

当需要高效插入删除时,Dart提供了LinkedList的解决方案:

import 'dart:collection';

class Student extends LinkedListEntry<Student> {
  final String name;
  int score;
  
  Student(this.name, this.score);
  
  @override
  String toString() => '$name: $score';
}

void main() {
  var classList = LinkedList<Student>();
  
  // 添加学生
  classList.add(Student('小明', 90));
  var xiaohong = Student('小红', 85);
  classList.add(xiaohong);
  
  // 插入操作
  classList.first.insertAfter(Student('小刚', 88));
  
  // 修改成绩
  xiaohong.score += 5;
  
  // 遍历链表
  for (var student in classList) {
    print(student);
  }
}

优势场景

  • 需要频繁插入删除的日志系统
  • 实现LRU缓存淘汰算法
  • 音乐播放列表

4. 性能对比与选择指南

4.1 数据结构性能矩阵

操作 List Set Map Queue LinkedList
插入 O(n) O(1) O(1) O(1) O(1)
删除 O(n) O(1) O(1) O(1) O(1)
查找 O(n) O(1) O(1) O(n) O(n)
随机访问 O(1) 不支持 不支持 不支持 不支持
内存占用

4.2 选择策略

  • 数据量小(<100):随意选择,开发效率优先
  • 需要唯一性:首选Set,次选Map
  • 键值对关系:无脑选Map
  • 频繁增删:LinkedList或Queue
  • 排序需求:List配合sort()方法
  • 多类型存储:List或自定义类

5. 常见坑点预警

5.1 类型安全陷阱

// 危险操作:动态类型
var mixedList = [1, '二', 3.0]; 
mixedList.add(true); // 编译通过但存在隐患

// 推荐做法:明确类型
List<num> safeList = [1, 2, 3];
safeList.add(3.14); // 合法
// safeList.add('字符串'); // 编译报错

5.2 迭代器失效问题

var numbers = [1, 2, 3, 4, 5];

// 错误示范:遍历时修改
for (var num in numbers) {
  if (num % 2 == 0) {
    numbers.remove(num); // 可能引发并发修改异常
  }
}

// 正确做法:使用removeWhere
numbers.removeWhere((num) => num % 2 == 0);

5.3 相等性判断玄学

// 对象比较示例
class Point {
  final int x, y;
  Point(this.x, this.y);
}

void main() {
  var p1 = Point(1, 2);
  var p2 = Point(1, 2);
  
  print(p1 == p2); // 输出false!需要重写==运算符
}

6. 未来扩展:与Flutter的化学反应

虽然本文聚焦Dart原生数据结构,但在实际Flutter开发中,这些基础结构会与框架深度结合:

// 在StatefulWidget中的应用
class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  List<int> _history = []; // 记录计数历史
  
  void _increment() {
    setState(() {
      _history.add(_history.length + 1);
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('历史记录:$_history'),
        ElevatedButton(
          onPressed: _increment,
          child: Text('+1'),
        ),
      ],
    );
  }
}

7. 总结:选择即艺术

通过本文的探索,我们发现Dart的数据结构设计处处体现着实用主义哲学。没有银弹数据结构,只有最适合当前场景的选择。记住几个黄金法则:

  1. 明确需求:先想清楚需要什么操作(增删改查?排序?)
  2. 量体裁衣:根据数据规模选择合适结构
  3. 类型安全:尽可能使用泛型约束
  4. 性能意识:大数据量时关注时间复杂度
  5. 保持灵活:Dart的集合转换非常方便,不要过度设计

最后送给各位Dart新人的建议:多读官方文档(特别是dart:collection库),多写测试用例,在实践中感受不同数据结构的微妙差异。Happy coding!