HarmonyOS开发中缓存策略设计:内存缓存与磁盘缓存

发布时间:2026/6/29 12:06:14

HarmonyOS开发中缓存策略设计:内存缓存与磁盘缓存 HarmonyOS开发中缓存策略设计内存缓存与磁盘缓存一、小知识你有没有遇到过这种情况——应用启动时加载一堆数据用户切换个页面又重新请求一遍网络稍微慢点就白屏转圈圈说白了这就是缓存没做好。缓存这东西听着简单做起来全是坑。光有内存缓存吧应用一杀数据就没了光有磁盘缓存吧每次读取还得IO性能上不去。真正靠谱的做法是多级缓存——内存做一级磁盘做二级两层配合才能既快又持久。更麻烦的是缓存淘汰。内存不是无限大的你总不能让缓存把应用撑爆吧这时候就得用LRULeast Recently Used算法把那些八百年没人用的数据清理掉给新数据腾地方。HarmonyOS里做缓存你既可以用Preferences做轻量级存储也能用RDB存结构化数据还能直接操作文件系统。选哪种方案得看你的数据特点和性能要求——咱们这篇就把这些方案掰开揉碎讲清楚。二、核心原理2.1 多级缓存架构多级缓存的核心思想是读写分离、逐级降级。读取时先查内存命中就返回没命中再查磁盘磁盘有就回填内存并返回都没有才走网络请求。写入时则要同步更新所有层级。是否是否读取请求内存缓存命中?返回内存数据磁盘缓存命中?回填内存缓存网络请求写入磁盘缓存写入内存缓存2.2 LRU淘汰算法LRU的核心是维护一个访问顺序链表。每次访问数据时把它移到链表头部缓存满了要淘汰时直接删掉链表尾部的数据——因为尾部肯定是最久未使用的。实现上有两种常见方案LinkedHashMap利用其accessOrder特性自动维护顺序双向链表HashMapO(1)时间复杂度的经典实现HarmonyOS的ArkTS里没有现成的LinkedHashMap咱们得自己实现一个双向链表版本。LRU缓存结构HashMapKey→Node双向链表维护访问顺序访问KeyANodeA移至链表头部缓存已满删除链表尾部Node从HashMap移除对应Key2.3 缓存策略模式实际业务中不同数据对缓存的要求差异很大用户头像可以缓存很久淘汰优先级低新闻列表需要定时刷新过期时间短临时数据用完就删不需要持久化所以咱们要设计一个灵活的策略模式让每种数据都能配置自己的缓存行为。三、代码实战3.1 LRU内存缓存实现先实现一个通用的LRU缓存类这是整个缓存体系的核心/** * LRU缓存节点 - 双向链表结构 */ class LRUNodeK, V { key: K; value: V; prev: LRUNodeK, V | null null; next: LRUNodeK, V | null null; constructor(key: K, value: V) { this.key key; this.value value; } } /** * LRU内存缓存实现 * 基于双向链表HashMapO(1)时间复杂度 */ export class LRUCacheK, V { private capacity: number; // 最大容量 private cache: MapK, LRUNodeK, V; // HashMap存储 private head: LRUNodeK, V | null; // 链表头最近使用 private tail: LRUNodeK, V | null; // 链表尾最久未使用 private size: number 0; // 当前大小 constructor(capacity: number) { this.capacity capacity; this.cache new Map(); this.head null; this.tail null; } /** * 获取缓存值并移动到链表头部 */ get(key: K): V | undefined { const node this.cache.get(key); if (!node) { return undefined; } // 移动到头部表示最近访问 this.moveToHead(node); return node.value; } /** * 设置缓存值 */ set(key: K, value: V): void { const existingNode this.cache.get(key); if (existingNode) { // 已存在更新值并移到头部 existingNode.value value; this.moveToHead(existingNode); } else { // 新节点 const newNode new LRUNode(key, value); this.cache.set(key, newNode); this.addToHead(newNode); this.size; // 超过容量淘汰尾部 if (this.size this.capacity) { this.removeTail(); } } } /** * 删除缓存 */ delete(key: K): boolean { const node this.cache.get(key); if (!node) { return false; } this.removeNode(node); this.cache.delete(key); this.size--; return true; } /** * 清空缓存 */ clear(): void { this.cache.clear(); this.head null; this.tail null; this.size 0; } /** * 获取当前缓存大小 */ getSize(): number { return this.size; } /** * 将节点移到链表头部 */ private moveToHead(node: LRUNodeK, V): void { this.removeNode(node); this.addToHead(node); } /** * 添加节点到头部 */ private addToHead(node: LRUNodeK, V): void { node.prev null; node.next this.head; if (this.head) { this.head.prev node; } this.head node; if (!this.tail) { this.tail node; } } /** * 从链表中移除节点 */ private removeNode(node: LRUNodeK, V): void { if (node.prev) { node.prev.next node.next; } else { this.head node.next; } if (node.next) { node.next.prev node.prev; } else { this.tail node.prev; } } /** * 淘汰尾部节点最久未使用 */ private removeTail(): void { if (!this.tail) { return; } this.cache.delete(this.tail.key); this.removeNode(this.tail); this.size--; } }3.2 磁盘缓存实现磁盘缓存咱们用RDB数据库来存比Preferences更适合存大量结构化数据import relationalStore from ohos.data.relationalStore; import { LRUCache } from ./LRUCache; /** * 磁盘缓存配置 */ export interface DiskCacheConfig { dbName: string; // 数据库名 tableName: string; // 表名 maxAge: number; // 默认过期时间毫秒 } /** * 缓存数据实体 */ interface CacheEntity { key: string; value: string; // JSON序列化后的值 timestamp: number; // 写入时间戳 expireTime: number; // 过期时间 } /** * 磁盘缓存 - 基于RDB实现 */ export class DiskCache { private rdbStore: relationalStore.RdbStore | null null; private config: DiskCacheConfig; private context: Context; constructor(context: Context, config: DiskCacheConfig) { this.context context; this.config config; } /** * 初始化数据库 */ async init(): Promisevoid { const config: relationalStore.StoreConfig { name: this.config.dbName, securityLevel: relationalStore.SecurityLevel.S1 }; this.rdbStore await relationalStore.getRdbStore(this.context, config); // 创建缓存表 const createSql CREATE TABLE IF NOT EXISTS ${this.config.tableName} ( key TEXT PRIMARY KEY, value TEXT NOT NULL, timestamp INTEGER NOT NULL, expireTime INTEGER NOT NULL ) ; await this.rdbStore.executeSql(createSql); } /** * 写入缓存 */ async setT(key: string, value: T, maxAge?: number): Promisevoid { if (!this.rdbStore) { throw new Error(DiskCache not initialized); } const now Date.now(); const expireTime now (maxAge ?? this.config.maxAge); const entity: CacheEntity { key, value: JSON.stringify(value), timestamp: now, expireTime }; // 使用replace语义存在则更新 const valueBucket: relationalStore.ValuesBucket { key: entity.key, value: entity.value, timestamp: entity.timestamp, expireTime: entity.expireTime }; await this.rdbStore.insert(this.config.tableName, valueBucket, relationalStore.ConflictResolution.ON_CONFLICT_REPLACE); } /** * 读取缓存 */ async getT(key: string): PromiseT | null { if (!this.rdbStore) { throw new Error(DiskCache not initialized); } const predicates new relationalStore.RdbPredicates(this.config.tableName); predicates.equalTo(key, key); const resultSet await this.rdbStore.query(predicates); try { if (resultSet.goToFirstRow()) { const expireTime resultSet.getLong(resultSet.getColumnIndex(expireTime)); // 检查是否过期 if (Date.now() expireTime) { // 异步删除过期数据 this.delete(key); return null; } const valueStr resultSet.getString(resultSet.getColumnIndex(value)); return JSON.parse(valueStr) as T; } return null; } finally { resultSet.close(); } } /** * 删除缓存 */ async delete(key: string): Promisevoid { if (!this.rdbStore) { return; } const predicates new relationalStore.RdbPredicates(this.config.tableName); predicates.equalTo(key, key); await this.rdbStore.delete(predicates); } /** * 清理所有过期缓存 */ async clearExpired(): Promisenumber { if (!this.rdbStore) { return 0; } const predicates new relationalStore.RdbPredicates(this.config.tableName); predicates.lessThan(expireTime, Date.now()); return await this.rdbStore.delete(predicates); } /** * 清空所有缓存 */ async clear(): Promisevoid { if (!this.rdbStore) { return; } await this.rdbStore.executeSql(DELETE FROM ${this.config.tableName}); } }3.3 多级缓存管理器把内存缓存和磁盘缓存组合起来形成完整的多级缓存体系import { LRUCache } from ./LRUCache; import { DiskCache } from ./DiskCache; /** * 缓存策略配置 */ export interface CachePolicy { memoryCache: boolean; // 是否启用内存缓存 diskCache: boolean; // 是否启用磁盘缓存 memoryMaxSize: number; // 内存缓存最大条目数 diskMaxAge: number; // 磁盘缓存过期时间毫秒 } /** * 默认缓存策略 */ const DEFAULT_POLICY: CachePolicy { memoryCache: true, diskCache: true, memoryMaxSize: 100, diskMaxAge: 7 * 24 * 60 * 60 * 1000 // 7天 }; /** * 多级缓存管理器 * 内存缓存L1 磁盘缓存L2 */ export class CacheManager { private memoryCache: LRUCachestring, any | null null; private diskCache: DiskCache | null null; private policy: CachePolicy; private initialized: boolean false; constructor(private context: Context, policy: PartialCachePolicy {}) { this.policy { ...DEFAULT_POLICY, ...policy }; } /** * 初始化缓存管理器 */ async init(): Promisevoid { if (this.initialized) { return; } // 初始化内存缓存 if (this.policy.memoryCache) { this.memoryCache new LRUCachestring, any(this.policy.memoryMaxSize); } // 初始化磁盘缓存 if (this.policy.diskCache) { this.diskCache new DiskCache(this.context, { dbName: MultiCache.db, tableName: cache_data, maxAge: this.policy.diskMaxAge }); await this.diskCache.init(); } this.initialized true; } /** * 获取缓存数据 * 查找顺序内存 → 磁盘 → 返回null */ async getT(key: string): PromiseT | null { this.ensureInitialized(); // 1. 先查内存缓存 if (this.memoryCache) { const memoryValue this.memoryCache.get(key); if (memoryValue ! undefined) { console.debug([Cache] Hit memory: ${key}); return memoryValue as T; } } // 2. 再查磁盘缓存 if (this.diskCache) { const diskValue await this.diskCache.getT(key); if (diskValue ! null) { console.debug([Cache] Hit disk: ${key}); // 回填内存缓存 if (this.memoryCache) { this.memoryCache.set(key, diskValue); } return diskValue; } } console.debug([Cache] Miss: ${key}); return null; } /** * 设置缓存数据 * 同时写入内存和磁盘 */ async setT(key: string, value: T, maxAge?: number): Promisevoid { this.ensureInitialized(); // 写入内存缓存 if (this.memoryCache) { this.memoryCache.set(key, value); } // 写入磁盘缓存 if (this.diskCache) { await this.diskCache.set(key, value, maxAge); } console.debug([Cache] Set: ${key}); } /** * 获取或创建缓存 * 如果缓存不存在执行factory函数获取数据并缓存 */ async getOrSetT( key: string, factory: () PromiseT, maxAge?: number ): PromiseT { const cached await this.getT(key); if (cached ! null) { return cached; } // 执行factory获取数据 const value await factory(); await this.set(key, value, maxAge); return value; } /** * 删除缓存 */ async delete(key: string): Promisevoid { if (this.memoryCache) { this.memoryCache.delete(key); } if (this.diskCache) { await this.diskCache.delete(key); } } /** * 清空所有缓存 */ async clear(): Promisevoid { if (this.memoryCache) { this.memoryCache.clear(); } if (this.diskCache) { await this.diskCache.clear(); } } /** * 清理过期缓存 */ async cleanup(): Promisenumber { if (!this.diskCache) { return 0; } return await this.diskCache.clearExpired(); } /** * 获取缓存统计信息 */ getStats(): { memorySize: number; policy: CachePolicy } { return { memorySize: this.memoryCache?.getSize() ?? 0, policy: this.policy }; } /** * 确保已初始化 */ private ensureInitialized(): void { if (!this.initialized) { throw new Error(CacheManager not initialized. Call init() first.); } } }3.4 完整使用示例import { CacheManager } from ./CacheManager; import http from ohos.net.http; Entry Component struct CacheDemoPage { State message: string 缓存策略演示; State cacheStats: string ; private cacheManager: CacheManager | null null; async aboutToAppear(): Promisevoid { // 初始化缓存管理器 this.cacheManager new CacheManager(getContext(this), { memoryMaxSize: 50, // 内存缓存最多50条 diskMaxAge: 24 * 60 * 60 * 1000 // 磁盘缓存1天过期 }); await this.cacheManager.init(); this.updateStats(); } /** * 模拟网络请求获取用户信息 */ async fetchUserInfo(userId: string): PromiseUserInfo { // 使用缓存的getOrSet方法 // 缓存命中直接返回未命中则执行网络请求 return await this.cacheManager!.getOrSetUserInfo( user_${userId}, async () { // 实际的网络请求 const httpRequest http.createHttp(); const response await httpRequest.request( https://api.example.com/users/${userId}, { method: http.RequestMethod.GET } ); httpRequest.destroy(); return JSON.parse(response.result as string) as UserInfo; }, 30 * 60 * 1000 // 缓存30分钟 ); } /** * 测试缓存效果 */ async testCache(): Promisevoid { const userId 12345; // 第一次请求 - 会走网络 console.info(第一次请求:); const user1 await this.fetchUserInfo(userId); console.info(用户: ${user1.name}); // 第二次请求 - 命中内存缓存 console.info(第二次请求:); const user2 await this.fetchUserInfo(userId); console.info(用户: ${user2.name}); // 清空内存缓存第三次请求会命中磁盘缓存 this.cacheManager!.clear(); console.info(清空内存后第三次请求:); const user3 await this.fetchUserInfo(userId); console.info(用户: ${user3.name}); this.updateStats(); } /** * 更新统计信息 */ updateStats(): void { const stats this.cacheManager?.getStats(); this.cacheStats 内存缓存: ${stats?.memorySize} 条; } build() { Column() { Text(this.message) .fontSize(24) .fontWeight(FontWeight.Bold) .margin({ bottom: 20 }) Text(this.cacheStats) .fontSize(16) .fontColor(#666666) .margin({ bottom: 20 }) Button(测试缓存效果) .width(80%) .onClick(() this.testCache()) Button(清理过期缓存) .width(80%) .margin({ top: 10 }) .onClick(async () { const count await this.cacheManager?.cleanup() ?? 0; this.message 已清理 ${count} 条过期缓存; }) Button(清空所有缓存) .width(80%) .margin({ top: 10 }) .onClick(async () { await this.cacheManager?.clear(); this.updateStats(); this.message 缓存已清空; }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .padding(20) } } /** * 用户信息类型定义 */ interface UserInfo { id: string; name: string; avatar: string; email: string; }四、踩坑与注意事项4.1 内存缓存容量设置容量设置太小缓存命中率低设置太大可能OOM。建议根据数据大小动态计算// 错误示范固定容量 const cache new LRUCachestring, any(1000); // 可能存大量图片导致OOM // 正确做法根据预估数据大小计算 const avgDataSize 50 * 1024; // 假设平均50KB const maxMemory 50 * 1024 * 1024; // 分配50MB内存 const capacity Math.floor(maxMemory / avgDataSize); // 约1000条4.2 磁盘缓存序列化问题不是所有数据都能JSON序列化比如Buffer、Map、Set等// 错误示范直接存Buffer await diskCache.set(image, imageBuffer); // 序列化失败 // 正确做法转为base64或直接存文件 const base64 imageBuffer.toString(base64); await diskCache.set(image_base64, base64);4.3 并发访问问题多线程同时读写缓存可能导致数据不一致。ArkTS是单线程模型但异步操作仍需注意// 潜在问题两次getOrSet可能同时执行factory const promise1 cache.getOrSet(key, expensiveFactory); const promise2 cache.getOrSet(key, expensiveFactory); // 解决方案使用Promise缓存 private pendingRequests: Mapstring, Promiseany new Map(); async getOrSetSafeT(key: string, factory: () PromiseT): PromiseT { // 检查是否有进行中的请求 if (this.pendingRequests.has(key)) { return this.pendingRequests.get(key) as PromiseT; } const promise this.getOrSet(key, factory); this.pendingRequests.set(key, promise); try { return await promise; } finally { this.pendingRequests.delete(key); } }4.4 缓存key设计key设计不合理会导致缓存冲突或难以管理// 错误示范简单拼接可能冲突 const key userId _ type; // 123_profile vs 12_3profile // 正确做法使用分隔符或结构化key const key user:${userId}:${type}; // 或者 const key JSON.stringify({ userId, type, version: v1 });4.5 过期时间精度磁盘缓存过期检查在读取时才触发可能积累大量过期数据// 建议定期清理 setInterval(() { cacheManager.cleanup(); }, 24 * 60 * 60 * 1000); // 每天清理一次五、HarmonyOS 6适配说明5.1 RDB API变更HarmonyOS 6对RDB的API进行了调整部分方法签名变化// HarmonyOS 5写法 const resultSet await rdbStore.query(predicates, [key, value]); // HarmonyOS 6适配 // query方法增加columns参数类型校验需显式声明 const columns: string[] [key, value]; const resultSet await rdbStore.query(predicates, columns);5.2 安全等级调整HarmonyOS 6对数据安全等级要求更严格// HarmonyOS 5 const config: relationalStore.StoreConfig { name: cache.db, securityLevel: relationalStore.SecurityLevel.S1 }; // HarmonyOS 6适配 // 敏感数据需使用S3或S4等级 const config: relationalStore.StoreConfig { name: cache.db, securityLevel: relationalStore.SecurityLevel.S2, // 根据数据敏感度调整 encrypt: false // 非敏感缓存可关闭加密提升性能 };5.3 内存管理增强HarmonyOS 6引入了更精细的内存管理API// HarmonyOS 6新增内存压力监听 import memory from ohos.memory; // 监听系统内存压力动态调整缓存大小 memory.on(pressure, (level: memory.PressureLevel) { if (level memory.PressureLevel.CRITICAL) { // 内存紧张时缩减缓存容量 const currentSize lruCache.getSize(); const newSize Math.floor(currentSize * 0.5); lruCache.resize(newSize); // 新增的resize方法 } });5.4 缓存持久化策略HarmonyOS 6支持应用冻结时的缓存持久化// HarmonyOS 6新增应用生命周期感知 import abilityLifecycle from ohos.app.ability.lifecycle; // 应用进入后台时可选择持久化内存缓存 abilityLifecycle.on(background, async () { if (memoryCache) { // 将热点数据持久化到磁盘 const hotKeys getHotKeys(); // 自定义热度判断 for (const key of hotKeys) { const value memoryCache.get(key); if (value diskCache) { await diskCache.set(key, value); } } } });六、总结维度评价学习难度⭐⭐⭐⭐使用频率⭐⭐⭐⭐⭐重要程度⭐⭐⭐⭐⭐调试难度⭐⭐⭐缓存策略设计是性能优化的基石。通过本文的学习你应该掌握了核心收获LRU算法原理双向链表HashMap实现O(1)时间复杂度的缓存淘汰多级缓存架构内存缓存快 磁盘缓存持久的协同工作模式策略模式应用不同数据类型配置不同的缓存策略工程化实践缓存key设计、并发控制、过期清理等实战技巧最佳实践建议内存缓存容量根据实际数据大小动态计算避免OOM磁盘缓存使用RDB而非Preferences支持更大数据量定期清理过期数据避免存储空间无限增长使用结构化key设计便于管理和调试监听系统内存压力动态调整缓存策略缓存不是银弹用错了反而增加复杂度。记住一个原则只缓存那些计算成本高、访问频率高、变化频率低的数据。其他情况老老实实请求网络反而更简单可靠。

相关新闻