Harmony os 技术实战|拼豆制图42:用 ID 索引与受控缓存终结重复重建图纸

📅 2026/8/25 13:27:32
Harmony os 技术实战|拼豆制图42:用 ID 索引与受控缓存终结重复重建图纸
当列表只有几十条数据时线性查找看起来几乎没有成本。问题在于《拼豆制图》的getPatternById()不只是遍历 ID它会重新创建 50 份种子再为命中的图纸构造完整格子。一次点击触发的并不是“找对象”而是一段被查找接口隐藏起来的重计算。这篇文章围绕一个具体目标展开让 Harmony os 本地图纸仓库做到一次建索引、按 ID 精确命中、受控缓存详情同时避免把可变页面状态泄漏进全局仓库。一、先识别查找接口里的隐藏工作当前写法容易让调用方误判成本staticgetPatternById(id:string):Pattern|null{constseedsPatternRepository.createSeeds();for(leti0;iseeds.length;i){if(seeds[i].idid){returnPatternRepository.createPattern(seeds[i],true);}}returnnull;}表面复杂度是 O(n)真正值得关注的是循环命中后的createPattern()。它会解析字符矩阵、生成格子、统计色数和豆子数。调用方若在页面重组或派生方法里使用它成本会被放大。二、启动时只建立 Seed 索引索引的值不应直接是重型Pattern而应该是轻量PatternSeedprivatestaticreadonlyseedById:Mapstring,PatternSeedPatternRepository.buildSeedIndex();privatestaticbuildSeedIndex():Mapstring,PatternSeed{constindexnewMapstring,PatternSeed();constseedsPatternRepository.createSeeds();for(leti0;iseeds.length;i){constseedseeds[i];if(index.has(seed.id)){thrownewError(重复图纸 ID${seed.id});}index.set(seed.id,seed);}returnindex;}构建阶段顺便拦截重复 ID比后续让收藏、路由和缓存随机指向其中一条更容易排查。三、查找与物化拆成两步让方法名表达成本可以降低误用概率privatestaticfindSeed(id:string):PatternSeed|null{constseedPatternRepository.seedById.get(id);returnseedundefined?null:seed;}privatestaticmaterialize(seed:PatternSeed):Pattern{returnPatternRepository.createPattern(seed,true);}页面代码看到materialize()自然会意识到这不是一个随处可调用的普通 getter。接口命名本身就是性能边界的一部分。四、给详情缓存定义清晰契约缓存应该回答三个问题缓存什么、何时失效、最多保留多少。interfaceCachedPattern{value:Pattern;lastAccess:number;}privatestaticreadonlycachenewMapstring,CachedPattern();privatestaticreadonlymaxCacheSize8;staticgetDetail(id:string):Pattern|null{constcachedPatternRepository.cache.get(id);if(cached!undefined){cached.lastAccessDate.now();returnPatternRepository.clonePattern(cached.value);}constseedPatternRepository.findSeed(id);if(seednull){returnnull;}constvaluePatternRepository.materialize(seed);PatternRepository.putCache(value);returnPatternRepository.clonePattern(value);}这里返回副本而不是缓存对象本身是为了防止页面把收藏标志、临时标题或选中态写进共享对象。五、实现最小可用的 LRU 淘汰50 张图纸不需要复杂缓存框架一个基于lastAccess的小实现就够用privatestaticputCache(pattern:Pattern):void{PatternRepository.cache.set(pattern.id,{value:pattern,lastAccess:Date.now()});if(PatternRepository.cache.sizePatternRepository.maxCacheSize){return;}letoldestId;letoldestTimeNumber.MAX_VALUE;PatternRepository.cache.forEach((entry,id){if(entry.lastAccessoldestTime){oldestTimeentry.lastAccess;oldestIdid;}});if(oldestId.length0){PatternRepository.cache.delete(oldestId);}}对于小容量缓存O(k) 找最旧项完全可以接受k是 8而不是图库总量。六、为什么要克隆返回值ArkTS 页面状态经常会替换数组但对象内部仍可能被修改。最低限度应复制数组和格子privatestaticclonePattern(source:Pattern):Pattern{return{...source,previewCells:source.previewCells.map((cell)({...cell})),chartCells:source.chartCells.map((cell)({...cell})),colorStats:source.colorStats.map((stat)({...stat}))};}如果对象始终按只读约定使用也可以直接返回缓存实例以节省复制成本但这必须成为明确团队约束并由类型与审查共同保证。七、缓存失效不要依赖“以后再说”内置资源在当前版本不变缓存可随进程生命周期存在。若后续支持资源包更新应把版本加入键privatestaticcacheKey(id:string,assetVersion:number):string{return${assetVersion}:${id};}升级后不需要逐条删除旧对象只要新版本使用新键即可。应用进入后台并不等于必须清空是否释放应由内存预算决定。八、用计数器确认是否重复计算调试构建可以加入轻量探针privatestaticmaterializeCount:number0;privatestaticmaterialize(seed:PatternSeed):Pattern{PatternRepository.materializeCount;returnPatternRepository.createPattern(seed,true);}验证路径连续打开 A、B、A物化次数应为 2再打开超过缓存容量的不同图纸回到最早图纸时次数才允许增加。九、常见问题与修复问题原因修复有 Map 仍然卡顿Map 只索引了重对象启动时仍全构造索引轻 Seed详情按需生成返回缓存后页面互相影响多页面共享可变对象返回副本或使用只读类型LRU 永不淘汰命中时没有更新访问时间每次命中刷新lastAccess资源升级显示旧图缓存键没有版本使用version:id组合键定位卡顿时应同时观察索引命中次数和物化次数。前者证明查询没有退回线性扫描后者证明缓存确实阻止重复重建两个计数中任意一个异常都能把问题缩小到查找层或详情构造层而不是笼统归因于页面渲染。十、验证清单重复 ID 会在索引构建阶段被拒绝。找不到 ID 时返回null不会落到默认图。连续打开同一图纸只物化一次。缓存数量不会超过上限。修改页面副本不会污染下一次读取。资源版本变化后不会命中旧详情。验证不能只执行一次命中。建议固定走 A、B、A、C、D、E、F、A 的序列同时记录缓存键顺序、物化计数和返回对象内容既覆盖重复访问也覆盖超容量淘汰与再次构造才能证明 LRU 契约完整成立。十一、总结高效的 ID 查找不只是把数组换成 Map。真正的改进是把“定位 Seed”“物化详情”“缓存详情”“隔离页面状态”拆成四个明确步骤。这样 Harmony os 页面拿到的是可预测数据仓库也不会在一个看似普通的 getter 里反复重建整张图。标签Harmony os、ArkTS、Map、LRU 缓存、Repository