ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Flutter开发必备:Dart Map完全指南与性能优化实践

Flutter开发必备:Dart Map完全指南与性能优化实践 1. 从一个小需求说起为什么每个Flutter开发者都绕不开Map不管你写Flutter多久只要你和数据打交道就一定会碰到Map。举个例子你在Flutter里调后端接口接口返回的JSON格式数据用jsonDecode解析之后拿到的就是一个MapString, dynamic。再比如你想把一组用户信息按ID存起来方便快速查找第一个想到的容器也是Map。可以说Map就是Dart这门语言里最常用的数据容器之一和List并列甚至使用频率更高。这篇笔记我本来是写给自己做复习用的因为我在学Dart的过程中发现Map这个知识点看着简单真正用起来却有不少细节值得整理。比如Map的多种构造方式性能差异、遍历时到底该用forEach还是for-in、map方法和Map类型之间的关系、嵌套Map和JSON的互相转换等。把这篇文章整理出来也是希望刚开始学Flutter的人能少走点弯路把这部分基础打得扎实一点。不管你是刚接触Dart的初学者还是已经写了一阵子Flutter但没系统整理过Map用法的开发者这篇笔记应该都能帮你把Map相关的知识点串起来。文中的所有示例代码我都实际在Dart环境里跑过可以直接复制到你的项目里验证。2. Map的底层逻辑与构造方式的正确选择2.1 先搞清楚Map在Dart里到底是个什么东西简单来说Map是一种键值对容器它维护的是一组“键到值”的映射关系。每个键在Map中是唯一的通过键可以直接取到对应的值查询效率比List线性扫描高得多时间复杂度通常是O(1)到O(log n)级别取决于具体实现。这里有个容易混淆的点Dart里的Map是一个抽象类而不是一个具体的实现类。平时我们写的MapString, String map {}实际上Dart会在底层帮我们创建一个LinkedHashMap实例。这个细节很多人不知道但它解释了为什么Map的遍历顺序通常保持插入顺序——因为LinkedHashMap内部是哈希表和双向链表的结合体哈希表负责快速查找链表负责记录插入顺序。用生活化的类比来理解Map就像是一个带有标签的储物柜每个格子上贴了一个标签键柜子里存放物品值。你要找什么东西不需要把所有格子翻一遍直接看标签就行了。而LinkedHashMap这个具体实现相当于每个格子还额外记录了“我前面是谁、后面是谁”这样你按顺序看柜子时看到的顺序就是当初东西放进去的顺序。2.2 不同构造方式的应用场景与性能对比创建Map的方式有很多种下面这几种是实际开发中最常见的我每个都跑过一遍说一下它们的区别和适用场景。第一种字面量方式。这是最推荐的日常写法var map String, int{ apple: 1, banana: 2, };第二种通过构造函数创建空Map再逐个添加var map String, int{}; map[apple] 1; map[banana] 2;这两种方式本质上是等价的。但有一种情况需要注意如果你写var map {}由于没有指定泛型类型Dart会把它推断为Mapdynamic, dynamic这在类型安全要求严格的Flutter项目里往往会引发静态检查警告。第三种从其他Map转换而来var original {name: 张三}; var copy1 Map.from(original); // 浅拷贝 var copy2 Map.of(original); // 类型保持更严格 var copy3 Map.fromEntries(original.entries); // 通过条目拷贝Map.from和Map.of都是浅拷贝但有一个重要区别Map.of要求源Map的类型参数和目标类型完全兼容否则编译期就报错Map.from则相对宽松它会在运行时尝试转换。我的建议是优先用Map.of把类型问题提前暴露在编译期不要等到线上运行才踩坑。第四种通过fromIterable从List生成Map。这在需要把一个列表转成以某个字段为键的Map时非常有用var users [ User(1, 张三), User(2, 李四), ]; var userMap Map.fromIterable( users, key: (user) user.id, value: (user) user.name, );这样做的好处是之后你想按ID快速找到某个用户的信息不再需要遍历整个列表直接用userMap[2]就能拿到李四。这个模式在内存中缓存用户数据、词典数据时非常实用。还有一个容易被忽略的构造方式Map.unmodifiable。它是用来创建只读Map的var config Map.unmodifiable({debug: true}); config[debug] false; // 运行时会抛UnsupportedError这个特性我建议在配置类、常量表这类“初始化之后就不允许任何人再改动”的场景里使用。它提供的是运行时保护不是编译期保护但它能防止开发过程中因为误操作改动了共享配置而引发的难排查Bug。2.3 默认实现之外HashMap与SplayTreeMap该不该用前文说过MapString, int这种写法创建的是LinkedHashMap。Dart标准库还提供了另外两个Map实现HashMap和SplayTreeMap。它们各有所长。LinkedHashMap的特点前面说过了保持插入顺序的同时兼顾查询性能。绝大多数业务数据结构都适合用它。HashMap的特点是不保证遍历顺序但它是纯哈希实现在频繁插入、删除场景下通常有最优的性能。如果你的业务不关心数据顺序又对性能敏感比如一个高频缓存表可以考虑它。SplayTreeMap的特点是键会按照排序规则排列。如果你需要遍历Map时按键的自然顺序获取数据比如按时间戳从小到大输出日志用SplayTreeMap就很方便import dart:collection; final sortedMap SplayTreeMapDateTime, String(); sortedMap[DateTime(2025, 1, 3)] 第三次事件; sortedMap[DateTime(2025, 1, 1)] 第一次事件; sortedMap[DateTime(2025, 1, 2)] 第二次事件; for (final entry in sortedMap.entries) { print(${entry.key}: ${entry.value}); } // 输出顺序永远是按键升序这里可以补充一个实用心得Dart内置的Map默认实现已经足够覆盖绝大多数场景我在实际项目里用到HashMap和SplayTreeMap的频率很低。如果仅仅是“需要按键排序”更简单的做法是把keys取出来排序后再遍历。尤其不要为了所谓性能盲目引入HashMap在小数据量下性能差异根本感知不到反而失去了插入顺序带来的可预测性。记住一条经验没有明确的性能瓶颈证据优先用默认的LinkedHashMap。3. Map的增删改查与函数式操作一次讲透3.1 最基础也最常用的增删改查Map的基本操作大多数人在写第一个Flutter项目时就会用到。这里我把它系统列出来并补充一些容易忽略的细节。添加和修改其实是同一个操作用map[key] value赋值的语法。如果key在Map里不存在它就是新增如果key已经存在它就是覆盖这个语法糖在Dart里处理得非常统一。var scores String, int{}; scores[张三] 90; // 新增 scores[李四] 85; // 新增 scores[张三] 95; // 修改张三的值变成95还有个putIfAbsent方法它实现了“仅当key不存在时才设置值”的原子逻辑但注意它只在单次调用内是原子的不是线程级原子操作Dart单线程模型下通常不需要考虑并发写问题。用法示例scores.putIfAbsent(王五, () 88);这段代码的意思是如果王五不存在就设置值为88如果存在什么都不做。写缓存的时候这个方法是神器能少写好几行判空逻辑。删除操作有三种方式scores.remove(张三); // 删除指定键并返回被删的值 scores.clear(); // 清空所有键值对 scores.removeWhere((key, value) value 60); // 按条件批量删除removeWhere按条件批量删是很多场景的解决方案比如淘汰成绩不及格的学生、剔除过期缓存等。注意remove方法会返回被删的值如果你删完之后还想对这个值做点处理比如通知监听的组件这个返回值就派上用场了。按照索引取值时直接使用中括号语法但要注意如果key不存在返回的是null。很多Flutter初学者在这里踩过大坑因为Dart有空安全机制取出来的null值被赋给非空类型变量时编译期就报错。遇到这种情况有三种安全的取值方式。var value scores[赵六]; // 可能是null var value1 scores[赵六] ?? 0; // 为null时返回0 var value2 scores[赵六] ?? scores[默认] ?? 0; // 支持链式兜底 var value3 scores.putIfAbsent(赵六, () 0); // 没有就插入默认值从可读性和安全性综合考虑我推荐在业务代码里多用??运算符做空值兜底它写起来简洁、意图清楚。putIfAbsent同时会改变Map本身有时候会带来副作用如果只想取值不想改Map??反而是更纯粹的选择。3.2 遍历Map的几种方式性能和场景对比遍历Map是我在Flutter开发中使用频率最高的操作之一。Dart里遍历Map的姿势很多我总结下来最常用的有四种各有合适场景。第一种forEach方法。它接收一个回调函数分别接收key和value。scores.forEach((key, value) { debugPrint($key: $value); });forEach写起来最简洁但有一点要注意回调内部不支持break和continue。如果你想“遍历到某个条件就提前终止”forEach做不到必须用for循环。第二种for-in加entries。这也是我在实际项目中最推荐的遍历方式因为它同时满足可读性和灵活性。for (final entry in scores.entries) { if (entry.value 60) { debugPrint(不及格${entry.key}); } }entries返回的是MapEntry对象的集合每个MapEntry同时包含key和value。这种方式的好处是你可以在循环体内自由使用break、continue、return对流程控制完全无限制。第三种keys和values单独遍历。如果你的业务只需要键或者只需要值直接用这两个属性。for (final name in scores.keys) { // 只处理key } var allValues scores.values.toList(); // 把值收集成List需要提醒的是keys和values返回的是视图view还是迭代器不同Dart版本可能存在差异。在较新版本中scores.values.toList()是复制出一份新列表对它的修改不会影响原Map这一点可以放心。第四种while 迭代器不太推荐日常使用但面试里常问。var iterator scores.entries.iterator; while (iterator.moveNext()) { debugPrint(${iterator.current.key}: ${iterator.current.value}); }这种写法和你手动用索引遍历List类似性能上没有额外优势代码却更冗长日常开发不推荐但理解迭代器原理对后续学习Stream和异步编程有帮助。3.3 别把Map类型和map方法搞混了Dart的学习者经常问一个问题map方法到底是干什么的这个困惑的根源在于map既是容器类型的名字又是Iterable接口里的一个方法名两者完全不冲突但刚接触时确实容易混淆。map方法定义在Iterable上Map本身也实现了Iterable接口确切说是IterableMapEntryK, V所以Map可以直接调用map方法。它接收一个转换函数把原来的MapEntry转换成新的对象返回的是一个新的Iterable。var numbers {a: 1, b: 2, c: 3}; var descriptions numbers.map((key, value) MapEntry(key, $value)); // 结果{a: 1, b: 2, c: 3}类型是MapString, String需要注意Map的map方法返回的依然是一个Map而不是List或Iterable。这一点和List.map不同后者返回Iterable。如果你想把Map.map的结果转成其他类型可以继续链式调用toList()或者toSet()。举一个实际业务中的例子后端返回的用户信息里年龄的单位是秒前端需要转成“X岁”如果不想改原始Map用map方法生成一个新的专供页面展示的Map就不会污染数据源var displayMap userRawData.map((key, value) { if (key age) { return MapEntry(key, ${value ~/ 31536000}岁); } return MapEntry(key, value); });有些教程会用Maps.map这种写法把Map的map方法表达成顶级函数的方式。这其实是Dart核心库里的一个工具函数它的调用方式和实例方法一样var result Map.fromEntries( numbers.entries.map((entry) MapEntry(entry.key, entry.value * 10)), );如果你只需要映射value更简洁的写法是用map配合MapEntry重建var doubleScores scores.map((key, value) MapEntry(key, value * 2));3.4 函数式操作where、fold、reduce、expand除了map方法函数式编程风格里还有几个常用方法在处理Map时同样好用。where用于筛选满足条件的键值对返回一个包含筛选结果的新的Mapvar passed scores.where((key, value) value 60);注意这里的where返回的是Map不是Iterable。这是Dart针对Map的一个特殊定义目的是让你可以直接用结果继续做Map操作非常方便。fold用于将所有键值对归约成一个值。计算总分、统计商品总价就靠它var totalScore scores.values.foldint(0, (sum, score) sum score);reduce和fold类似但reduce要求结果类型与集合元素类型一致且集合不能为空。fold可以指定初始值类型也没有限制所以日常中fold的适用面更广。expand用于一个键对应多个值的情况把它扁平展开var studentCourses { 张三: [语文, 数学], 李四: [英语, 物理], }; var allCourses studentCourses.values.expand((courses) courses).toSet(); // 结果{语文, 数学, 英语, 物理}这一组函数式操作方法配合使用能把很多几十行的命令式循环压缩成两三行声明式代码可读性好很多。4. Flutter实战Map和JSON、ListView、缓存的那点事4.1 JSON解析MapString, dynamic和模型类的爱恨情仇Flutter开发中最常见的Map使用场景就是把后端返回的JSON解析成Dart对象。jsonDecode函数返回的类型是dynamic实际运行时绝大多数情况下是MapString, dynamic。import dart:convert; void parseUser(String jsonString) { final data jsonDecode(jsonString) as MapString, dynamic; final name data[name] as String; final age data[age] as int; final hobbyList (data[hobbies] as List).castString(); }这里有两个关键细节值得展开说。第一as强转有风险。如果后端某个字段没有返回data[name]就是nullnull as String会直接抛出TypeError导致整个解析流程崩溃。更稳妥的写法是使用as String?或者配合??给默认值。final name data[name] as String? ?? 未知用户; final age data[age] as int? ?? 0;第二嵌套Map的处理。后端接口经常返回多层嵌套结构这时候你拿到的可能是MapString, dynamic里套着MapString, dynamic再套着List。逐层解析的代码非常容易写成长串的“三明治代码”。我的个人习惯是只要数据结构超过两层马上为它建立对应的Model类而不是继续用裸Map。class Address { final String city; final String street; Address({required this.city, required this.street}); factory Address.fromJson(MapString, dynamic json) { return Address( city: json[city] as String? ?? , street: json[street] as String? ?? , ); } } class User { final String name; final Address address; User({required this.name, required this.address}); factory User.fromJson(MapString, dynamic json) { return User( name: json[name] as String? ?? 未知用户, address: Address.fromJson(json[address] as MapString, dynamic? ?? {}), ); } }这样每个层级各司其职逻辑清晰也方便单元测试。不推荐为了图省事一直用嵌套Map因为只要字段名拼错一个字母排查起来会让人非常痛苦。4.2 ListView展示用Map构建列表项标记颜色方案Flutter里写列表经常需要根据数据状态展示不同样式。比如一个任务列表按状态分为进行中、已完成、已过期要给不同状态的任务行配不同的背景色。这个场景用Map来管理状态和样式的映射关系代码会非常清晰。const statusColors TaskStatus, Color{ TaskStatus.doing: Colors.blue, TaskStatus.done: Colors.green, TaskStatus.expired: Colors.red, }; class TaskItem extends StatelessWidget { final TaskStatus status; const TaskItem({super.key, required this.status}); override Widget build(BuildContext context) { final bgColor statusColors[status] ?? Colors.grey; return Container( color: bgColor, child: Text(任务状态$status), ); } }使用Map而不是连续的if-else或switch-case最大的好处是新增一个状态时只需要在Map里加一行不需要改动业务判断逻辑也降低了漏写分支导致UI异常的可能性。这种“配置驱动”的思路在UI样式随业务状态频繁变化的场景里非常实用。4.3 缓存与防抖Map在内存缓存场景的妙用写Flutter应用时经常会遇到“同一个请求短时间内不要重复发起”的需求比如用户频繁点击某个按钮触发网络请求。除了使用现成的防抖工具利用Map实现一个简单缓存也是不错的方案。class RequestCache { final MapString, dynamic _cache {}; dynamic get(String key) { if (_cache.containsKey(key)) { return _cache[key]; } return null; } void set(String key, dynamic value) { _cache[key] value; } bool contains(String key) _cache.containsKey(key); void invalidate(String key) _cache.remove(key); void clear() _cache.clear(); }这个例子很基础但它示范了Map作为内存索引的经典用法用唯一标识如请求URL、用户ID做key把结果、对象引用当value存起来。相比每次都去遍历或重新计算从Map里取数据的成本极低。如果在写状态管理你还可以在Controller里用一个MapString, bool记录哪些模块的数据正在加载中从而控制页面loading组件是否显示final loadingStates String, bool{}; void updateLoading(String module, bool isLoading) { loadingStates[module] isLoading; } bool isLoadingModule(String module) loadingStates[module] ?? false;这种代码写起来很轻量但效果非常直观在需要按模块控制界面状态的场景下比声明多个独立布尔变量干净得多。5. Map的不可变性与空安全写出更稳的代码5.1 不能改的Map也有大用场我在前面介绍Map.unmodifiable时提过只读Map。实际项目中把常量配置写成不可变Map是个非常好的实践。比如我们经常需要维护一份错误码到提示文案的映射const errorMessages int, String{ 400: 请求参数错误, 401: 未登录或登录已过期, 403: 没有权限访问, 404: 请求的资源不存在, 500: 服务器内部错误, }; String errorMessage(int code) { return errorMessages[code] ?? 未知错误; }这里的常量Map得益于Dart的整型常量折叠和编译期常量优化在性能上没有任何负担。把这类不变的映射关系定义成const还能让代码的意图表达得更清楚——后续维护代码的人一看就知道这些数据是只读的不会有人往里面乱塞值。5.2 空安全与Map的配合避免一整天的DebugDart的空安全机制和Map的配合有几个容易踩的坑。最经典的一个坑是从Map里取出的值永远是可空类型。即使你确定这个key一定存在Dart的静态类型系统也认为它可能是null除非你把containsKey的判断和取值绑定在一起if (scores.containsKey(张三)) { debugPrint(scores[张三].toString()); }在这个代码块内部Dart并不能通过containsKey推断出[]返回的值非空所以上面toString()前面的点号还是会飘红。正确的处理是if (scores[张三] ! null) { debugPrint(scores[张三].toString()); }或者final zhangsanScore scores[张三]; if (zhangsanScore ! null) { debugPrint(zhangsanScore.toString()); }第二种写法的好处是把值先存到局部变量里后续使用zhangsanScore时Dart的promotion机制会把它自动收窄为非空类型不需要每次都写!或者再做一次判空。这在复杂方法里能省掉大量无效的判空代码。另一个坑是MapString, dynamic的dynamic陷阱。从这种Map取出的值类型是dynamic意味着编译器完全放弃类型检查任何类型操作都可能在运行时才暴露问题。比如final data jsonDecode(jsonString) as MapString, dynamic; final age data[age]; // 类型是dynamic var newAge age 1; // 如果age实际是字符串运行时直接炸正确的做法是尽早做类型转换和类型检查把dynamic收缩到具体类型final rawAge data[age]; if (rawAge is int) { var newAge rawAge 1; } else { // 日志记录字段类型异常 }这是一个值得养成的好习惯不要试图依赖Dart运行时宽容地处理类型不匹配尽早、明确地进行类型收缩。5.3 深拷贝问题改了副本为什么原件也变了Map在赋值和传参时是引用传递不是值传递。这意味着把一个Map直接赋值给另一个变量两个变量指向的是同一个底层对象。var map1 {name: 张三}; var map2 map1; map2[name] 李四; print(map1[name]); // 输出的是李四这个特性经常让人猝不及防。想复制出一个真正独立的Map有几种方式var copy1 Map.of(map1); // 浅拷贝一层 var deepCopy jsonDecode(jsonEncode(map1)); // JSON序列化方式实现深拷贝Map.of只是浅拷贝如果Map的value本身又是一个Map或List内部的数据还是共享的。真正确保完全独立的做法是使用JSON序列化的方式做深拷贝。这个技巧在开发状态存储时特别重要——比如你不想让底层的临时修改污染上一份快照数据。下面这段代码演示了嵌套Map的浅拷贝问题var config1 { profile: {theme: dark}, }; var config2 Map.of(config1); config2[profile]?[theme] light; print(config1[profile]?[theme]); // 输出light被改了如果你需要完全隔离的副本请使用var config2Exact jsonDecode(jsonEncode(config1)) as MapString, dynamic;这个方法有一个限制Map里的value必须是可JSON序列化的类型。自定对象要先手动转成Map才能用这个方案。6. 常见问题与性能陷阱清单收藏这一节就够了6.1 Map相关的五个高频报错与排查思路我在写Flutter的过程中遇到过不少和Map相关的报错下面整理几个最高频的问题和排查方向。问题一Reading from map returns null despite key being present。运行时报错提示从Map里取出的值是null但我明明存过值。排查思路先检查key的类型是否一致。常见情况是把int和double混用1和1.0在Dart的Map里是两个不同的key。再检查是不是使用putIfAbsent时返回的默认值被当成了真实数据。问题二Unsupported operation: Cannot modify unmodifiable map。运行时抛异常提示无法修改只读Map。这个一般是给Map.unmodifiable或constMap执行了[]、remove等修改操作。排查思路检查是否不小心把常量Map当普通Map传给了下游方法或者用Map.of复制时忘了取消只读属性。问题三Type Null is not a subtype of type String in type cast。类型强转失败。几乎都是因为as String在值为null时直接抛异常。排查思路改用as String?并配合??给默认值如果一定要用as强转同时使用containsKey判断。问题四Map keys are not unique when using custom objects。使用自定义对象做key时出现key“重复”的假象总是取不到想要的值。排查思路自定义类必须重写和hashCode否则两个内容相同的对象也会被认为是不同的键。这是Map类型的经典陷阱要格外留心。问题五并发修改异常。遍历Map的同时往Map里添加或删除元素抛出并发修改相关异常。Dart的可变Map在遍历期间修改自己往往会出现不可预期的行为。排查思路先收集需要修改的key遍历结束后再统一处理。final keysToRemove String[]; scores.forEach((key, value) { if (value 60) keysToRemove.add(key); }); for (final key in keysToRemove) { scores.remove(key); }这种方法既安全又直观。6.2 性能小贴士Map高效使用的五个原则Map的性能对比List通常有优势但使用姿势不正确照样会出现性能问题。根据我的实际经验这里给出五条随手可用的建议。第一知道阈值适时切换。数据量小的时候几十条以内Map和List在性能上差别不大优先选择代码可读性更高的方案数据量到几千甚至上万时查找操作尽量用Map尽量不要用List的firstWhere或contains做线性扫描。第二谨慎使用containsValue。Map的containsKey是O(1)操作但containsValue需要遍历整个Map时间复杂度是O(n)。如果你需要频繁按值查找考虑额外维护一个反向Map来提高查找效率var forward {a: 1, b: 2}; var reverse forward.map((key, value) MapEntry(value, key));第三避免在循环内部反复创建新的Map。例如把Map构建挪到循环外用局部变量反复修改而不是循环内新建Map再合并。第四善用putIfAbsent而不是先判断再赋值。这个模式能少一次哈希查找代码也更简洁// 不推荐 if (!map.containsKey(key)) { map[key] computeValue(key); } // 推荐 map.putIfAbsent(key, () computeValue(key));第五合理利用Map.fromIterable做批量转换避免手动for循环一个个填值。批量转换代码更短而且在很老版本的Dart VM里内部实现经过优化整体耗时更少。这个问题在新版本中差异已不明显但代码可读性的提升是真切存在的。6.3 警惕Map里的动态类型一个真实的生产事故复盘去年我维护过一个统计模块后端返回的数据结构里有一个MapString, dynamic其中的value有时是int有时是String。初版代码直接用Map里的值做数值运算结果上线后偶发崩溃。定位到根因后修复方式是写了一个安全的取值工具函数int? toInt(dynamic value) { if (value is int) return value; if (value is double) return value.toInt(); if (value is String) return int.tryParse(value); return null; }这个问题的本质是MapString, dynamic中的dynamic类型放弃了好不容易建立起来的类型安全体系。所以只要条件允许请求完数据后立刻转换为强类型的Model对象避免dynamic在业务代码里到处流窜。6.4 一个小技巧用Map实现多条件匹配的switch替代方案当判断逻辑是“根据某个字段的值做不同处理”且分支较多时Map往往比大量switch-case更清晰、更容易维护。下面是个典型例子typedef ActionHandler void Function(); void handleByType(String type) { final handlers String, ActionHandler{ click: () debugPrint(处理点击), scroll: () debugPrint(处理滚动), input: () debugPrint(处理输入), }; final handler handlers[type] ?? () debugPrint(未知类型); handler(); }这种写法的价值在于后续新增类型时你只需要往Map里加一行映射不用改动函数控制流。如果分支逻辑简单直接这样写挺顺手如果分支逻辑里面还有复杂的嵌套判断就需要权衡是否拆函数了。作为一种“配置映射”的思想在UI样式映射、状态机迁移、事件处理分发等场景中都很实用。7. 写在最后的一点个人建议刚开始学Dart和Flutter的时候我总觉得Map无非就是键值对用起来没什么难度所以一直没有系统整理过。直到后来在项目里遇到类型转换崩溃、深拷贝改污染、遍历中修改容器报错这些坑才意识到基础掌握得牢不牢直接决定你在业务开发里踩坑的频率。这篇笔记是我对自己Map学习的一次梳理把构造方式、增删改查、遍历、函数式操作、JSON解析、缓存应用、类型安全和性能优化等内容全部串了一遍。这样整理完Map相关的知识体系基本就完整了以后再遇到相关需求脑子里能直接浮现出对应的写法。如果你也正在学习Dart建议你不要只满足于“能跑”可以多想想每一种写法背后的设计意图——为什么Map默认是LinkedHashMap、为什么Map取出的值总是可空类型、为什么深拷贝需要JSON转一遍。把这些问题想透你对语言本身的理解会提升一个台阶。Flutter的学习路径是连续的这篇Map笔记是这个系列的第三篇。后续我还会继续整理其他Dart核心内容到时候再和大家分享。
返回列表