2. Flutter本地存储方案全景

在Flutter中谈“本地存储”,并不是在挑一个最快的库,而是在为数据的结构、规模、查询方式与生命周期匹配最合适的持久化引擎。同一个App里,用户偏好设置、离线文章缓存、订单流水、全文搜索索引,往往需要三到四种方案并存。理解每种方案的边界,比记住API更重要。

一、先按数据形态分类,而不是按库名分类

所有本地存储方案,本质上都在回答两个问题:数据以什么形态落盘,以及读取时以什么方式被检索。据此可以把主流方案分成四层:

  • 键值层:SharedPreferences、Hive(键值模式)、flutter_secure_storage。以字符串键直接映射到值,读取是O(1)哈希查找,没有查询语言。
  • 文件层:path_provider + dart:io、文件缓存库。以字节流或文本落盘,适合大块非结构化内容,如JSON快照、图片、日志。
  • 对象/文档层:Hive、Isar、ObjectBox。以对象为单位存储,支持索引、类型化查询,介于键值与数据库之间。
  • 关系层:SQLite(sqflite)、Drift。支持表、外键、事务、SQL聚合,适合有复杂关联和统计需求的数据。

这四层不是替代关系,而是共存关系。一个典型的离线优先App可能是:SharedPreferences存主题与登录态,Hive存用户草稿,SQLite/Drift存业务实体,文件系统存图片缓存。

二、关键性能特征与适用边界

SharedPreferences底层是平台原生的XML/plist,首次读取会把全部键值加载进内存。它适合几十到几百条、单条不超过几KB的配置项。写入是异步的,但不适合存列表、大对象或频繁变更的数据——每次写入都会触发整文件序列化。

文件存储的吞吐取决于文件大小和写入方式。追加日志用FileMode.append可以做到接近顺序写的性能;而每次全量重写JSON,在几MB级别就会明显卡顿。它的优势是零schema、零依赖、可直接被其他工具读取,适合导出、备份和调试。

SQLite / Drift的性能拐点通常在万级行数。带索引的等值查询在10万行内可保持毫秒级;但如果没有索引,全表扫描会随数据量线性劣化。Drift在SQLite之上提供了类型安全的Dart API和编译期SQL校验,代价是引入代码生成步骤。它适合订单、消息、同步队列这类需要事务和关联查询的数据。

Hive是纯Dart实现的键值/对象存储,读写走内存映射文件,小对象读写极快,无需原生依赖。但它的查询能力弱,复杂过滤需要全量遍历后在Dart层筛选,且不支持多进程并发写。适合配置、草稿、小型集合。

Isar和ObjectBox是原生高性能对象数据库,支持索引、复合查询、全文检索和响应式监听。Isar的查询在十万级对象上仍能保持亚毫秒到毫秒级;ObjectBox以极低的读写延迟见长。两者都适合中大型、需要频繁按条件检索的本地数据集,代价是包体积增加和平台绑定。

三、选型决策依据

面对具体需求,可以按以下顺序自问:

  1. 数据是否需要按非主键条件查询?否 → 键值或文件;是 → 进入下一步。
  2. 是否存在多表关联、事务或聚合统计?是 → SQLite/Drift;否 → 进入下一步。
  3. 数据量是否超过约1万条,或需要全文检索、响应式查询?是 → Isar/ObjectBox;否 → Hive。
  4. 是否需要跨平台一致、零原生依赖?Hive和Drift(Web端受限)各有取舍,需按目标平台验证。

此外还有两个常被忽略的维度:加密需求(敏感数据应使用flutter_secure_storage或SQLCipher,而非普通Hive/SQLite)和迁移成本(schema变更时,Drift有迁移API,Hive需手写版本适配,文件方案则完全靠业务层兼容)。

选型不是一次性的。建议在项目初期用接口隔离存储层,例如定义UserRepository,底层实现可先从SharedPreferences起步,数据量增长后再替换为Drift,而不影响上层业务代码。

四、方案对比总览

下面的SVG以“数据形态—检索方式—性能拐点—典型场景”四个维度,把七种方案放在同一张选型地图上,便于横向比较。

Flutter 本地存储方案选型地图 按数据形态分层 · 检索能力向右增强 · 性能拐点标注在轴下方 键值层 文件层 对象 / 文档层 关系层 SharedPreferences 配置项 · 登录态 · 主题 拐点:数百条 / 单条几KB flutter_secure_storage Token · 密钥 · 敏感字段 拐点:少量高敏感数据 path_provider + dart:io JSON快照 · 日志 · 导出 拐点:全量重写 > 数MB 文件缓存(图片/媒体) 大块非结构化内容 拐点:受磁盘IO限制 Hive 草稿 · 小型集合 · 缓存 拐点:约1万条 / 弱查询 Isar 索引查询 · 全文检索 · 监听 拐点:十万级仍毫秒级 ObjectBox 低延迟读写 · 关系映射 拐点:中大型对象集 sqflite(SQLite) 表 · 外键 · 事务 · SQL 拐点:万级行 / 需索引 Drift 类型安全SQL · 迁移 · 聚合 拐点:复杂关联与统计 检索能力弱 检索能力强 键值 → 文件 → 对象/文档 → 关系型 实际项目常多方案并存:配置用键值,缓存用文件,业务实体用关系型或对象库

五、一个可落地的选型示例

假设要做一个离线优先的笔记App,需求是:主题设置、笔记正文、标签、全文搜索、按标签筛选、导出Markdown。可以这样分配:

数据方案理由
主题、字号、上次打开位置SharedPreferences少量键值,读写频繁但数据极小
笔记正文与元数据Drift(SQLite)需要按标签关联查询、按时间排序、事务保证
全文搜索索引SQLite FTS5 或 Isar需要倒排索引,普通键值无法胜任
导出的Markdown文件path_provider + dart:io面向用户的文件产物,需可被外部读取

这个分配说明:选型不是选“最好的库”,而是选“最匹配当前数据职责的层”。当笔记量从几百涨到几万,Drift的索引和FTS会继续工作;而如果一开始用Hive存全部笔记,到几千条时按标签筛选就会退化为全量遍历,届时再迁移的成本远高于一开始就分层。

不要用SharedPreferences存JSON数组来“模拟数据库”。当数据超过几百条或需要条件查询时,序列化开销和内存占用会迅速失控,且没有事务保护,写入中断可能损坏整个文件。