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

举报
bug菌 发表于 2025/12/25 17:10:38 2025/12/25
【摘要】 🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8 🧠 摘要说到“数据存储”,我脑子里第一反应不是技术点,而是一个灵魂拷...

🏆本文收录于「滚雪球学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 啊喂😅)

你在做备份时,至少要想清楚这几件事:

  1. 备份范围

    • Preferences 要不要备?要(用户设置很关键)
    • SQLite 要不要备?看数据价值(账单/笔记就很需要)
    • 缓存要不要备?通常不要(备了还浪费空间)
  2. 版本迁移
    备份恢复最怕“旧版本数据恢复到新版本崩掉”。建议你做:

    • schema 版本号
    • 数据升级脚本/兼容读取逻辑
  3. 敏感数据处理
    备份不是“裸奔打包”🙃。该加密加密、该脱敏脱敏。

实现层面:你会创建备份扩展能力,提供备份/恢复时的处理逻辑,具体能力定义属于 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-

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。