数据存储与管理:你到底该用 Preferences 还是 SQLite?还是……全都要?

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
🧠 摘要
说到“数据存储”,我脑子里第一反应不是技术点,而是一个灵魂拷问:你存的是数据,还是未来某天凌晨两点的崩溃😭?(别不信,真有这天……我替你先哭一会儿🥲)
🗺️先给你一张“选型地图”(不然容易一上来就乱用😵💫)
| 场景/需求 | 推荐方案 | 为什么(人话版😄) |
|---|---|---|
| 少量配置、开关、用户偏好 | ✅ Preferences | 轻、快、上手简单,但别多进程硬刚(会出事) |
| 结构化数据、复杂查询、事务一致性 | ✅ RelationalStore(SQLite) | 真·数据库,适合账单/订单/日志等“正经数据” |
| 需要跨设备协同的键值数据(分布式) | ✅ KVStore(distributedKVStore) | 多端同步才是鸿蒙“值钱点”之一✨ |
| 导入导出、缓存文件、图片/附件 | ✅ 文件存储(FileIO) | 文件就是文件,别硬塞数据库里折磨自己😅 |
| 换机/重装/系统备份恢复 | ✅ 数据备份(BackupExtensionAbility) | 想“无感迁移”,得走系统备份扩展能力 |
🧷 Preferences:小而美的“记事本”📒(但别当仓库用!)
✅它适合存什么?
- 用户设置:暗黑模式🌙、字体大小🔠、是否开启通知🔔
- 登录态的一点点标记(注意:敏感信息要谨慎处理🙃)
- “上次打开的页面”“是否展示新手引导”等轻量数据
官方也说得很直白:它就是 Key-Value 的轻量持久化能力([华为开发者][1]),而且不保证进程并发安全,多进程可能造成文件损坏/丢失风险([华为开发者][1]) ——我翻译一下就是:
别在多进程里拿它当数据库,不然你会收获惊喜(惊吓版)😵。
🧪代码示例:存一个“用户设置对象”(带 JSON 序列化)
import preferences from '@ohos.data.preferences'
import { BusinessError } from '@kit.BasicServicesKit'
type UserSettings = {
darkMode: boolean
fontScale: number
lang: string
}
export class PrefsRepo {
private pref?: preferences.Preferences
async init(context: any) {
// preferences.getPreferences:轻量 KV 存储:contentReference[oaicite:7]{index=7}
this.pref = await preferences.getPreferences(context, 'app_prefs')
}
async saveSettings(s: UserSettings) {
if (!this.pref) throw new Error('Prefs not initialized')
await this.pref.put('settings', JSON.stringify(s))
await this.pref.flush() // 持久化落盘:contentReference[oaicite:8]{index=8}
}
async loadSettings(): Promise<UserSettings> {
if (!this.pref) throw new Error('Prefs not initialized')
const raw = (await this.pref.get('settings', '{}')) as string
try {
const obj = JSON.parse(raw)
return {
darkMode: !!obj.darkMode,
fontScale: Number(obj.fontScale ?? 1),
lang: String(obj.lang ?? 'zh-CN')
}
} catch {
// 数据坏了也别炸,给个兜底(成熟一点🙂)
return { darkMode: false, fontScale: 1, lang: 'zh-CN' }
}
}
}
😌小贴士(我自己的“防猝死三件套”)
- Preferences 只放“少量 & 低频更新”的东西
- 一律加 JSON 解析兜底(数据脏起来很快的)
- 多进程别用它硬刚(官方都提醒了)
🗄️RelationalStore(SQLite):适合“认真存数据”的大哥🧱
如果你的数据有这些特征:
- 有结构(表、字段、索引)
- 要排序、筛选、分页
- 要事务(例如同时写入两张表,不能半成功半失败)
那就别犹豫了,RelationalStore 上场。它就是鸿蒙的关系型数据库能力,核心实例是 RdbStore,使用前需要通过 getRdbStore 获取。
🧪代码示例:创建表 + 插入 + 查询(最小可用闭环✅)
import relationalStore from '@ohos.data.relationalStore'
import { BusinessError } from '@kit.BasicServicesKit'
const STORE_CONFIG: relationalStore.StoreConfig = {
name: 'demo_rdb.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1
}
export class RdbRepo {
private store?: relationalStore.RdbStore
async init(context: any) {
// 通过 getRdbStore 获取 RdbStore 实例:contentReference[oaicite:11]{index=11}
this.store = await relationalStore.getRdbStore(context, STORE_CONFIG)
// 建表(注意:IF NOT EXISTS 防止重复创建)
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS t_note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
createdAt INTEGER
)
`, [])
}
async addNote(title: string, content: string) {
if (!this.store) throw new Error('Rdb not initialized')
const values: relationalStore.ValuesBucket = {
title,
content,
createdAt: Date.now()
}
return this.store.insert('t_note', values)
}
async listNotes(limit: number = 20) {
if (!this.store) throw new Error('Rdb not initialized')
const predicates = new relationalStore.RdbPredicates('t_note')
.orderByDesc('createdAt')
.limitAs(limit)
const rs = await this.store.query(predicates, ['id', 'title', 'content', 'createdAt'])
const out: Array<any> = []
while (rs.goToNextRow()) {
out.push({
id: rs.getLong(rs.getColumnIndex('id')),
title: rs.getString(rs.getColumnIndex('title')),
content: rs.getString(rs.getColumnIndex('content')),
createdAt: rs.getLong(rs.getColumnIndex('createdAt'))
})
}
rs.close()
return out
}
}
😏什么时候“不要用 SQLite”?
- 你只存一个开关、一个字符串……你上 SQLite,属于“用航母送外卖”🚢🍔
- 你数据量很小、结构很扁平,用 Preferences 或 KVStore 就够了
🌐KVStore:跨设备协同的“灵魂存储”✨(鸿蒙味儿这就来了)
KVStore 的典型使用场景是:
- 同账号设备之间同步某些键值数据(比如“最近搜索”“阅读进度”)
- 多端共享轻量状态(比如“是否收藏”“最近打开文件列表”)
另外提醒一下:旧接口 @ohos.data.distributedData 在 API Version 9 起就不再维护,官方建议用 @ohos.data.distributedKVStore。
(翻译:别抱着旧接口不撒手,后面你会很被动🥲)
🧪代码示例:创建 KVManager + 获取 KVStore + put/get
import { distributedKVStore } from '@kit.ArkData'
import { BusinessError } from '@kit.BasicServicesKit'
export class KvRepo {
private kvManager?: distributedKVStore.KVManager
private store?: distributedKVStore.SingleKVStore
init(context: any, bundleName: string) {
const cfg: distributedKVStore.KVManagerConfig = { context, bundleName }
this.kvManager = distributedKVStore.createKVManager(cfg)
}
async openStore(storeId: string) {
if (!this.kvManager) throw new Error('KVManager not initialized')
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
}
this.store = await new Promise((resolve, reject) => {
this.kvManager!.getKVStore<distributedKVStore.SingleKVStore>(storeId, options, (err, kv) => {
if (err) reject(err)
else resolve(kv)
})
})
}
async put(key: string, value: string) {
if (!this.store) throw new Error('KVStore not opened')
await new Promise<void>((resolve, reject) => {
this.store!.put(key, value, (err) => (err ? reject(err) : resolve()))
})
}
async get(key: string): Promise<string | undefined> {
if (!this.store) throw new Error('KVStore not opened')
return await new Promise((resolve, reject) => {
this.store!.get(key, (err, data) => {
if (err) reject(err)
else resolve(data as string | undefined)
})
})
}
}
😄我个人的“KVStore 使用姿势”
- 把它当“跨设备小本本”,别塞一堆大对象(同步压力会让你怀疑人生😅)
- 键命名统一前缀,比如
user:recent_search,后期维护会感谢现在的你🙏
📁文件存储(FileIO):文件就该待在文件系统里😎
图片、音频、导出包、缓存、日志——这些东西放数据库里,就像把冬天棉袄塞进冰箱🧥🧊:能放,但没必要。
鸿蒙文件能力常用的是 @ohos.file.fs,提供 open/read/write/readText 等基础 FileIO。并且操作前需要拿到应用沙箱路径,比如 Stage 模型用 context.filesDir。
🧪代码示例:写入文本文件 + 读取文本文件(filesDir 下)
import fs from '@ohos.file.fs'
export class FileRepo {
constructor(private context: any) {}
private get noteFilePath(): string {
// Stage 模型:context.filesDir 获取沙箱目录:contentReference[oaicite:15]{index=15}
return `${this.context.filesDir}/note_export.txt`
}
async writeText(content: string) {
const file = await fs.open(this.noteFilePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC) // open 支持 CREATE/TRUNC 等选项:contentReference[oaicite:16]{index=16}
try {
await fs.write(file.fd, content)
await fs.fsync(file.fd)
} finally {
await fs.close(file)
}
}
async readText(): Promise<string> {
// 直接用 readText 更省事:contentReference[oaicite:17]{index=17}
return await fs.readText(this.noteFilePath, { encoding: 'utf-8' })
}
}
😏文件存储的“别作死提醒”
- 重要文件写完
fsync一下(不然断电/崩溃时你会骂自己🙂) - 路径别乱拼到系统目录,老老实实用沙箱目录(filesDir/cacheDir 等)
- 大文件读写建议用 stream(别一次性全读进内存,内存会炸💥)
🧳数据备份:换机/重装也能“数据不丢”,你不心动吗?🥹
说实话,很多应用看起来功能挺全,但用户换个手机数据没了——用户的信任也跟着没了😮💨。
鸿蒙这块可以通过 **BackupExtensionAbility(备份恢复扩展能力)**来做应用数据的备份与恢复。社区里也有基于 Next 的实现思路分享(比如自定义备份扩展)。
✅备份设计的“工程现实”(别光写 Demo 啊喂😅)
你在做备份时,至少要想清楚这几件事:
-
备份范围:
- Preferences 要不要备?要(用户设置很关键)
- SQLite 要不要备?看数据价值(账单/笔记就很需要)
- 缓存要不要备?通常不要(备了还浪费空间)
-
版本迁移:
备份恢复最怕“旧版本数据恢复到新版本崩掉”。建议你做:- schema 版本号
- 数据升级脚本/兼容读取逻辑
-
敏感数据处理:
备份不是“裸奔打包”🙃。该加密加密、该脱敏脱敏。
实现层面:你会创建备份扩展能力,提供备份/恢复时的处理逻辑,具体能力定义属于 BackupExtensionAbility 模块范畴。
(我这里不把整套工程模板铺开,免得你一屏看晕😵;但你要我下一条就能把“文件清单 + 数据库拷贝 + 版本迁移”按真实项目结构写出来。)
🧯最后来个“保命级”总结:别让存储把你反杀🫠
- ✅ Preferences:轻量配置小甜饼🍪,别喂成大胖子(多进程更别搞)
- ✅ RelationalStore:结构化数据扛把子🗄️,事务/查询一把梭
- ✅ KVStore:跨设备协同灵魂✨,记得用推荐的新接口 distributedKVStore
- ✅ FileIO:文件归文件📁,数据库归数据库(别混着折磨自己)
- ✅ 备份:真正的“体验加分项”🏆,但要做好范围、版本、敏感数据处理
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」专栏(全网一个名),bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。
✨️ Who am I?
我是bug菌(全网一个名),CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主/价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-
- 点赞
- 收藏
- 关注作者
评论(0)