读取磁盘存储字典单个键值且不加载全量数据的最佳方法
结论
该需求完全可以实现。你当前采用整字典归档为单文件的存储方式,所有键值对被打包为不可拆分的二进制块,自然无法在不加载全量数据的前提下读取单个键对应的值,调整存储结构即可满足需求。
现有代码的问题
- 写入逻辑通过
NSKeyedArchiver将整个字典序列化为单个数据块写入磁盘,文件内部没有单键值的位置索引,不支持随机读取指定片段 - 读取逻辑存在逻辑错误:先调用
NSKeyedUnarchiver反归档又强转Data类型再做JSON解码,和写入逻辑的归档操作不匹配,本身就无法正常完成读写。
推荐实现方案
优先选择分文件存储+索引的方案,实现简单、稳定性高、易维护,完全能满足你的需求:
核心思路是将字典中每个键对应的数组单独存储为独立文件,额外维护一个轻量索引文件记录所有键的清单。需要读取全量字典时,遍历索引加载所有单键文件即可;需要读取单个键的值时,直接加载对应单键文件,不需要触碰其他数据。
参考实现代码:
// 提前定义好缓存路径,可根据自己的业务调整 let cacheRoot: String = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! let indexPath = (cacheRoot as NSString).appendingPathComponent("dict_index.json") let valueDir = (cacheRoot as NSString).appendingPathComponent("dict_values") func write(_ value: [String: [MyObject]]) throws { // 初始化值存储目录 if !FileManager.default.fileExists(atPath: valueDir) { try FileManager.default.createDirectory(atPath: valueDir, withIntermediateDirectories: true) } var allKeys: [String] = [] // 逐个键单独序列化存储 for (key, objList) in value { let keyedData = try JSONEncoder().encode(objList) let keyPath = (valueDir as NSString).appendingPathComponent("\(key.hashValue)") try keyedData.write(to: URL(fileURLWithPath: keyPath)) allKeys.append(key) } // 更新索引文件 let indexData = try JSONEncoder().encode(allKeys) try indexData.write(to: URL(fileURLWithPath: indexPath)) } /// 读取单个键对应的值 func readValue(forKey targetKey: String) throws -> [MyObject]? { let keyPath = (valueDir as NSString).appendingPathComponent("\(targetKey.hashValue)") guard FileManager.default.fileExists(atPath: keyPath), let data = FileManager.default.contents(atPath: keyPath) else { return nil } return try JSONDecoder().decode([MyObject].self, from: data) } /// 读取完整字典 func readFullDictionary() throws -> [String: [MyObject]] { var result: [String: [MyObject]] = [:] guard let indexData = FileManager.default.contents(atPath: indexPath), let keyList = try? JSONDecoder().decode([String].self, from: indexData) else { return result } for key in keyList { if let value = try readValue(forKey: key) { result[key] = value } } return result }
这个方案的额外优势:
- 后续更新、删除单个键的值时,只需要操作对应小文件即可,不需要重写整份大字典数据,IO开销更低
- 实现逻辑没有复杂的二进制计算,出问题容易排查,适配
MyObject的序列化变更也更简单
如果你的数据量级极大,不适合生成大量小文件,可以选择自定义二进制存储格式:
- 文件头部固定长度存储索引区的位置、长度信息
- 索引区记录每个键名、对应值在文件中的起始偏移量、数据长度
- 数据区按顺序存储每个键对应数组的序列化二进制结果
读取单个键时,只需要先读文件头部拿到索引位置,查询到目标键的偏移量后,用文件句柄直接读取指定长度的片段解码即可,全程不需要加载全量文件。注意这种方案不能使用NSKeyedArchiver做整包序列化,必须自行控制每个值的序列化分片,才能记录准确的偏移信息。
内容的提问来源于stack exchange,提问作者Tometoyou
相关产品推荐
相关产品推荐

