HarmonyOs应用《重要日》开发第24篇 - 日期映射表 dayMap 的构建与应用
本文深入解析 ImportantDays 项目中dayMap的设计包括其数据结构、构建算法、在日历标记中的查询应用、与allDays的关系以及为什么选择 Map 而非数组来管理日期索引。一、dayMap 的设计动机问题日历视图中需要频繁查询某一天是否有重要日某天有哪些重要日如果每次查询都遍历allDays数组// 不使用 dayMap 的查询getDaysByDate(dateStr:string):ImportantDay[]{returnthis.allDays.filter(dDateUtil.buildKeyByDate(d.targetDate)dateStr);}对于 42 格的月视图每个格子都要查询一次42 格 × 遍历 allDays假设 50 条 2100 次比较解决方案预先构建日期 → 重要日的映射表查询复杂度从 O(n) 降为 O(1)// 使用 dayMap 的查询getDaysByDate(dateStr:string):ImportantDay[]{returnthis.dayMap.get(dateStr)||[];}42 格 × Map.get() 42 次 O(1) 查询二、dayMap 的数据结构dayMap:Mapstring,ImportantDay[]newMap();类型解析Mapstring, ImportantDay[] │ │ │ └── 某天的所有重要日列表 └── 日期字符串如 2026-07-17示例dayMapMap{2026-07-17→[ImportantDay(妈妈生日),ImportantDay(开会)],2026-08-15→[ImportantDay(旅行出发)],2026-12-25→[ImportantDay(圣诞节)]}为什么不用 Trace// allDays 被 Trace 修饰TraceallDays:ImportantDay[][];// dayMap 不被 TracedayMap:Mapstring,ImportantDay[]newMap();dayMap不需要直接驱动 UI 更新UI 通过allDays和refreshNum的Trace变化触发更新dayMap是内部索引UI 更新后通过查询获取最新数据标记Trace会增加不必要的观察开销三、dayMap 的构建rebuildDayMap()privaterebuildDayMap():void{this.dayMap.clear();for(constdayofthis.allDays){constkeyDateUtil.buildKeyByDate(day.targetDate);if(!this.dayMap.has(key)){this.dayMap.set(key,[]);}this.dayMap.get(key)!.push(day);}}构建流程allDays: [day1(7/17), day2(8/15), day3(7/17), day4(12/25)] 遍历 day1(7/17): key 2026-07-17 dayMap.set(2026-07-17, []) dayMap.get(2026-07-17).push(day1) → dayMap: { 2026-07-17 → [day1] } 遍历 day2(8/15): key 2026-08-15 dayMap.set(2026-08-15, []) dayMap.get(2026-08-15).push(day2) → dayMap: { 2026-07-17 → [day1], 2026-08-15 → [day2] } 遍历 day3(7/17): key 2026-07-17 dayMap.has(2026-07-17) true dayMap.get(2026-07-17).push(day3) → dayMap: { 2026-07-17 → [day1, day3], 2026-08-15 → [day2] } 遍历 day4(12/25): key 2026-12-25 → dayMap: { 2026-07-17 → [day1, day3], 2026-08-15 → [day2], 2026-12-25 → [day4] }构建时机rebuildDayMap()在以下操作后调用// 初始化init():void{this.allDaysPreferenceUtil.loadImportantDays();this.rebuildDayMap();// ← 初始化时构建}// 添加asyncaddDay(day:ImportantDay):Promisevoid{this.allDays.push(day);this.rebuildDayMap();// ← 添加后重建// ...}// 删除asyncdeleteDay(dayID:string):Promisevoid{this.allDays.splice(index,1);this.rebuildDayMap();// ← 删除后重建// ...}// 更新asyncupdateDay(day:ImportantDay):Promisevoid{this.allDays[index]day;this.rebuildDayMap();// ← 更新后重建// ...}为什么每次都全量重建当前的实现每次增删改都全量重建dayMap优点实现简单不容易出错确保数据一致性对于少量数据几十条性能开销可忽略缺点全量重建有 O(n) 开销大量数据时可能有性能问题增量更新方案优化方向// 概念性优化增量更新asyncaddDay(day:ImportantDay):Promisevoid{this.allDays.push(day);// 增量添加到 dayMapconstkeyDateUtil.buildKeyByDate(day.targetDate);if(!this.dayMap.has(key)){this.dayMap.set(key,[]);}this.dayMap.get(key)!.push(day);// ...}asyncdeleteDay(dayID:string):Promisevoid{constdaythis.allDays.find(dd.dayIDdayID);this.allDays.splice(index,1);// 从 dayMap 中移除constkeyDateUtil.buildKeyByDate(day.targetDate);constdaysthis.dayMap.get(key);if(days){constdayIndexdays.findIndex(dd.dayIDdayID);if(dayIndex0)days.splice(dayIndex,1);if(days.length0)this.dayMap.delete(key);}// ...}四、dayMap 的查询方法hasImportantDayhasImportantDay(dateStr:string):boolean{returnthis.dayMap.has(dateStr)this.dayMap.get(dateStr)!.length0;}判断某天是否有重要日。先检查 Map 中是否有 key再检查列表是否非空。getDaysByDategetDaysByDate(dateStr:string):ImportantDay[]{returnthis.dayMap.get(dateStr)||[];}获取某天的所有重要日。如果没有返回空数组。getDaysByDateObjgetDaysByDateObj(date:Date):ImportantDay[]{constkeyDateUtil.buildKeyByDate(date);returnthis.getDaysByDate(key);}接受Date对象的便捷方法内部转为日期字符串后查询。五、dayMap 在日历中的应用MonthWeekCalendar 中的使用privatehasImportantDay(dateModel:DateModel):boolean{returnthis.manager.hasImportantDay(dateModel.dateStr);}privategetImportantDays(dateModel:DateModel):ImportantDay[]{returnthis.manager.getDaysByDate(dateModel.dateStr);}privategetFirstDayColor(dateModel:DateModel):string{constdaysthis.getImportantDays(dateModel);if(days.length0){returndays[0].color;}return#FF6B6B;}月视图渲染中的查询// 每个日期格渲染时查询ForEach(data,(dateModel:DateModel){if(this.hasImportantDay(dateModel)){// 显示颜色圆点ForEach(this.getImportantDays(dateModel).slice(0,3),(day){Circle({width:5,height:5}).fill(day.color)})// 显示标题预览Text(this.getFirstDayShortText(dateModel)).fontColor(this.getFirstDayColor(dateModel))}})BaseCalendar 中的使用privatehasImportantDay(dateModel:DateModel):boolean{returnthis.manager.hasImportantDay(dateModel.dateStr);}privategetFirstDayColor(dateModel:DateModel):string{constdaysthis.manager.getDaysByDate(dateModel.dateStr);if(days.length0){returndays[0].color;}return#FF6B6B;}CalendarVM 中的使用getSelectedDateDays():ImportantDay[]{constkeythis.selectDate.format(YYYY-MM-DD);returnImportantDayManager.getInstance().getDaysByDate(key);}六、日期键的一致性键的生成// DateUtil.buildKeyByDatestaticbuildKeyByDate(date:Date|dayjs.Dayjs):string{returndayjs(date).format(YYYY-MM-DD);}一致性保证场景键生成方式示例dayMap 构建DateUtil.buildKeyByDate(day.targetDate)2026-07-17日历查询dateModel.dateStr2026-07-17CalendarVMselectDate.format(YYYY-MM-DD)2026-07-17所有地方都使用YYYY-MM-DD格式确保键的一致性。DateModel.dateStr 的定义constructor(dayjsObj:dayjs.Dayjs,isCurrentMonth:booleantrue){this.dayjsObjdayjsObj;this.daydayjsObj.date();this.dateStrdayjsObj.format(YYYY-MM-DD);// 预生成日期键// ...}DateModel在构造时就预生成了dateStr避免日历渲染时重复格式化。七、Map vs Array 的选择为什么用 Map 而不是对象// Map项目采用dayMap:Mapstring,ImportantDay[]// 普通对象dayMap:Recordstring,ImportantDay[]特性MapObject键类型任意包括字符串仅字符串/Symbol键顺序插入顺序不保证size 属性有无需 Object.keys迭代直接迭代需 Object.entries性能频繁增删更优静态数据更优项目选择 Map 的原因频繁的增删改操作需要使用has()检查键存在需要使用get()获取值Map 的 API 更语义化为什么不用二维数组// 不采用的方式dayArray:ImportantDay[][]// 按日期索引日期不连续使用数组索引会浪费大量空间多数天没有重要日。Map 只存储有重要日的日期空间效率更高。八、dayMap 的内存分析内存占用dayMap: Map { 2026-07-17 → [day1, day2] // 引用非拷贝 2026-08-15 → [day3] }dayMap中存储的是对ImportantDay对象的引用不是拷贝。因此dayMap的额外内存开销主要是 Map 结构本身和日期字符串。与 allDays 的关系allDays: [day1, day2, day3] // 主数据源 dayMap: { // 索引 2026-07-17 → [day1, day2] // 引用 allDays 中的对象 2026-08-15 → [day3] }dayMap是allDays的索引视图两者引用相同的对象。修改对象属性会同时影响两者。九、总结dayMap的设计体现了以下原则空间换时间预建索引将 O(n) 查询降为 O(1)数据一致性每次增删改后重建确保索引与数据同步引用共享Map 中存储引用不增加显著内存键一致性全项目使用YYYY-MM-DD格式不标记 Trace作为内部索引不需要直接驱动 UIdayMap是性能优化的经典案例——通过简单的数据结构变换将日历标记的查询效率提升了数量级。