HarmonyOS开发中缓存策略设计:内存缓存与磁盘缓存
HarmonyOS开发中缓存策略设计:内存缓存与磁盘缓存
一、小知识
你有没有遇到过这种情况——应用启动时加载一堆数据,用户切换个页面又重新请求一遍,网络稍微慢点就白屏转圈圈?说白了,这就是缓存没做好。
缓存这东西,听着简单,做起来全是坑。光有内存缓存吧,应用一杀数据就没了;光有磁盘缓存吧,每次读取还得IO,性能上不去。真正靠谱的做法是多级缓存——内存做一级,磁盘做二级,两层配合才能既快又持久。
更麻烦的是缓存淘汰。内存不是无限大的,你总不能让缓存把应用撑爆吧?这时候就得用LRU(Least Recently Used)算法,把那些"八百年没人用"的数据清理掉,给新数据腾地方。
HarmonyOS里做缓存,你既可以用Preferences做轻量级存储,也能用RDB存结构化数据,还能直接操作文件系统。选哪种方案,得看你的数据特点和性能要求——咱们这篇就把这些方案掰开揉碎讲清楚。
二、核心原理
2.1 多级缓存架构
多级缓存的核心思想是读写分离、逐级降级。读取时先查内存,命中就返回;没命中再查磁盘,磁盘有就回填内存并返回;都没有才走网络请求。写入时则要同步更新所有层级。
2.2 LRU淘汰算法
LRU的核心是维护一个访问顺序链表。每次访问数据时,把它移到链表头部;缓存满了要淘汰时,直接删掉链表尾部的数据——因为尾部肯定是"最久未使用"的。
实现上有两种常见方案:
- LinkedHashMap:利用其accessOrder特性自动维护顺序
- 双向链表+HashMap:O(1)时间复杂度的经典实现
HarmonyOS的ArkTS里没有现成的LinkedHashMap,咱们得自己实现一个双向链表版本。
2.3 缓存策略模式
实际业务中,不同数据对缓存的要求差异很大:
- 用户头像:可以缓存很久,淘汰优先级低
- 新闻列表:需要定时刷新,过期时间短
- 临时数据:用完就删,不需要持久化
所以咱们要设计一个灵活的策略模式,让每种数据都能配置自己的缓存行为。
三、代码实战
3.1 LRU内存缓存实现
先实现一个通用的LRU缓存类,这是整个缓存体系的核心:
/**
* LRU缓存节点 - 双向链表结构
*/
class LRUNode<K, V> {
key: K;
value: V;
prev: LRUNode<K, V> | null = null;
next: LRUNode<K, V> | null = null;
constructor(key: K, value: V) {
this.key = key;
this.value = value;
}
}
/**
* LRU内存缓存实现
* 基于双向链表+HashMap,O(1)时间复杂度
*/
export class LRUCache<K, V> {
private capacity: number; // 最大容量
private cache: Map<K, LRUNode<K, V>>; // HashMap存储
private head: LRUNode<K, V> | null; // 链表头(最近使用)
private tail: LRUNode<K, 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: LRUNode<K, V>): void {
this.removeNode(node);
this.addToHead(node);
}
/**
* 添加节点到头部
*/
private addToHead(node: LRUNode<K, 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: LRUNode<K, 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(): Promise<void> {
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 set<T>(key: string, value: T, maxAge?: number): Promise<void> {
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 get<T>(key: string): Promise<T | 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): Promise<void> {
if (!this.rdbStore) {
return;
}
const predicates = new relationalStore.RdbPredicates(this.config.tableName);
predicates.equalTo('key', key);
await this.rdbStore.delete(predicates);
}
/**
* 清理所有过期缓存
*/
async clearExpired(): Promise<number> {
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(): Promise<void> {
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: LRUCache<string, any> | null = null;
private diskCache: DiskCache | null = null;
private policy: CachePolicy;
private initialized: boolean = false;
constructor(private context: Context, policy: Partial<CachePolicy> = {}) {
this.policy = { ...DEFAULT_POLICY, ...policy };
}
/**
* 初始化缓存管理器
*/
async init(): Promise<void> {
if (this.initialized) {
return;
}
// 初始化内存缓存
if (this.policy.memoryCache) {
this.memoryCache = new LRUCache<string, 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 get<T>(key: string): Promise<T | 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.get<T>(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 set<T>(key: string, value: T, maxAge?: number): Promise<void> {
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 getOrSet<T>(
key: string,
factory: () => Promise<T>,
maxAge?: number
): Promise<T> {
const cached = await this.get<T>(key);
if (cached !== null) {
return cached;
}
// 执行factory获取数据
const value = await factory();
await this.set(key, value, maxAge);
return value;
}
/**
* 删除缓存
*/
async delete(key: string): Promise<void> {
if (this.memoryCache) {
this.memoryCache.delete(key);
}
if (this.diskCache) {
await this.diskCache.delete(key);
}
}
/**
* 清空所有缓存
*/
async clear(): Promise<void> {
if (this.memoryCache) {
this.memoryCache.clear();
}
if (this.diskCache) {
await this.diskCache.clear();
}
}
/**
* 清理过期缓存
*/
async cleanup(): Promise<number> {
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(): Promise<void> {
// 初始化缓存管理器
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): Promise<UserInfo> {
// 使用缓存的getOrSet方法
// 缓存命中直接返回,未命中则执行网络请求
return await this.cacheManager!.getOrSet<UserInfo>(
`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(): Promise<void> {
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 LRUCache<string, 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: Map<string, Promise<any>> = new Map();
async getOrSetSafe<T>(key: string, factory: () => Promise<T>): Promise<T> {
// 检查是否有进行中的请求
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key) as Promise<T>;
}
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设计,便于管理和调试
- 监听系统内存压力,动态调整缓存策略
缓存不是银弹,用错了反而增加复杂度。记住一个原则:只缓存那些"计算成本高、访问频率高、变化频率低"的数据。其他情况,老老实实请求网络反而更简单可靠。
更多推荐

所有评论(0)