实现RAM与硬盘双向缓存:APP中UserInfo数组缓存方案咨询
嘿,我来帮你拆解下这个双向缓存的实现思路,其实把它拆成内存缓存、硬盘缓存,再加上两者的协同逻辑,就清晰多了~
核心思路先明确
双向缓存的核心就是内存(RAM)缓存负责快速读写(适合高频访问的数据),硬盘缓存负责持久化存储(APP重启后数据还在)。两者配合的原则是:优先读内存,内存没有读硬盘,硬盘没有再请求API;拿到新数据后,同时更新两级缓存。
一、RAM缓存的实现
内存缓存的关键是要避免内存溢出,所以别自己用普通字典存,要用自带内存淘汰机制的缓存类:
- iOS 用
NSCache:系统会在内存不足时自动清理缓存里的内容,不用你手动处理; - Android 用
LruCache:基于LRU(最近最少使用)算法,你可以设置最大缓存容量,超出时自动淘汰最少使用的条目。
缓存UserInfo示例
比如针对单个UserInfo对象缓存(比缓存整个大数组更灵活,节省内存):
iOS 代码片段
// 全局初始化缓存 let userInfoMemoryCache = NSCache<NSString, UserInfo>() // 缓存单个UserInfo func cacheUserInfoInRAM(_ userInfo: UserInfo) { userInfoMemoryCache.setObject(userInfo, forKey: userInfo.userId as NSString) } // 从RAM读取 func getCachedUserInfoFromRAM(userId: String) -> UserInfo? { return userInfoMemoryCache.object(forKey: userId as NSString) }
Android 代码片段
// 初始化LruCache,最多缓存100个UserInfo(可根据内存调整) val userInfoMemoryCache = LruCache<String, UserInfo>(100) // 缓存 fun cacheUserInfoInRAM(userInfo: UserInfo) { userInfoMemoryCache.put(userInfo.userId, userInfo) } // 读取 fun getCachedUserInfoFromRAM(userId: String): UserInfo? { return userInfoMemoryCache.get(userId) }
图片的RAM缓存
图片可以单独用同样的缓存类处理,key用图片URL(或者URL的MD5值,避免特殊字符),value用UIImage(iOS)或Bitmap(Android),逻辑和UserInfo缓存一致。
二、硬盘缓存的实现
硬盘缓存有两种常用方式,选哪种看你的需求:
1. 文件缓存(简单易实现)
把UserInfo转成JSON字符串存在本地文件,图片直接下载保存为本地文件:
- UserInfo文件命名:用
userId作为文件名(比如123.json),存在APP的缓存目录(别存在Documents,避免被iCloud同步); - 图片文件命名:用图片URL的MD5值作为文件名(避免重复和URL里的特殊字符)。
iOS 保存UserInfo到硬盘示例
func saveUserInfoToDisk(_ userInfo: UserInfo) { guard let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { return } let fileUrl = cacheDir.appendingPathComponent("\(userInfo.userId).json") do { let jsonData = try JSONEncoder().encode(userInfo) try jsonData.write(to: fileUrl) } catch { print("保存UserInfo到硬盘失败:\(error.localizedDescription)") } } // 从硬盘读取 func loadUserInfoFromDisk(userId: String) -> UserInfo? { guard let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { return nil } let fileUrl = cacheDir.appendingPathComponent("\(userId).json") guard let jsonData = try? Data(contentsOf: fileUrl) else { return nil } return try? JSONDecoder().decode(UserInfo.self, from: jsonData) }
2. 数据库缓存(适合结构化查询)
如果需要频繁查询、更新UserInfo的某个字段,用数据库更高效:
- iOS 用 Core Data 或 Realm;
- Android 用 Room 或 Realm。
数据库里只存UserInfo的结构化数据,图片建议还是存本地文件路径,别存二进制(会拖慢数据库性能)。
三、双向缓存的协同逻辑(关键!)
要让两级缓存配合工作,得统一读写流程:
读取数据流程
- 先查RAM缓存:如果有直接用,速度最快;
- RAM没有查硬盘缓存:如果有,先把数据加载到RAM缓存,再返回;
- 硬盘也没有就请求API:拿到数据后,先存RAM缓存,再异步存硬盘缓存(别阻塞主线程)。
更新数据流程
当API返回新数据时:
- 立刻替换RAM缓存里的对应条目;
- 开一个后台线程更新硬盘缓存(避免卡UI)。
缓存失效策略
避免缓存永远不更新:
- 给每个缓存的UserInfo加个
cacheTimestamp字段,读取时判断是否超过有效期(比如7天),过期就重新请求API; - 监听系统内存警告:RAM缓存自动清理(
NSCache和LruCache已经处理); - 定期清理硬盘缓存:比如APP启动时删除超过30天的缓存文件。
四、避坑提醒
- 线程安全:RAM缓存的读写要加锁,比如iOS用串行
DispatchQueue包裹,Android用synchronized或ConcurrentHashMap,避免多线程读写崩溃; - 避免重复请求:如果多个地方同时请求同一个UserInfo,要加个“请求标记”(比如用字典记录正在请求的userId),防止重复发起API请求;
- 图片缓存偷懒技巧:如果不想自己写图片的两级缓存,iOS可以用Kingfisher,Android用Glide,这些成熟库已经帮你实现了完整的内存+硬盘缓存,直接传URL就能用。
内容的提问来源于stack exchange,提问作者Eduard Valentin
相关产品推荐
相关产品推荐

