大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~

本文目录:

前言

先来一句实话:大多数应用一开始挂掉,不是因为功能写不完,而是因为数据管理一团乱。
今天你用 Preferences 存一坨配置,明天用文件随手写点 JSON,后天又糊弄了个 RDB 表……
等项目做大一点——

“这个 key 当时是我存的么?”
“这个 JSON 为啥有时候多字段,有时候少字段?”
“这张表是谁建的?字段命名是喝多了起的吗??”

更绝的是,鸿蒙给你准备的持久化方案,还不止一个:

  • Preferences
  • Data Storage / 文件存储
  • RDB(关系型数据库)
  • 分布式数据库(KV / 分布式 RDB 等)

每一个都能“完成任务”,但用错地方就会变成性能杀手/维护灾难

这篇就来好好把这四个方案按在桌上,一个一个拎起来对比:

  1. Preferences:轻量键值对存储
  2. Data Storage(文件):文件级读写
  3. RDB:本地关系型数据库
  4. 分布式数据库:多设备同步的杀手锏
  5. 适用场景对比:到底啥场景选谁?
  6. 实战示例与性能优劣:真项目里怎么搭配使用最舒服?

不打官腔,有吐槽、有经验、有代码、有选型建议,还有踩坑血泪史
看完你再选存储方案,就不会再是“感觉差不多就用这个吧”,而是——

“我很清楚,为什么这里用的是这个。” 😏


一、先搞清楚:鸿蒙里到底有几种持久化套路?

直接先上一个“全家福”的大概印象:

方案 典型形态 适合场景
Preferences 键值对,轻量存储 配置、开关、轻量用户信息、最近使用记录
Data Storage / 文件 读写文件 大文本、JSON 缓存、日志、下载文件、图片/音视频
RDB(关系数据库) 数据库 + 表 + SQL 本地业务数据(用户表、订单、消息)、复杂查询、多条件筛选
分布式数据库 分布式 KV / RDB 多设备同步:备忘录、任务清单、聊天草稿、设置跨设备保持一致

忘掉“哪个更高级”这种说法,真正的判断标准只有一个:

“我的业务数据长什么样?怎么用?要支撑到什么规模?”

接下来我们按从最轻量到最硬核的顺序来拆。


二、Preferences:轻到不像数据库,但日常离不开它

2.1 它是干嘛的?

Preferences 就是典型的:“我就存几个 key-value,别给我上数据库那么重的东西。”

非常适合这几类数据:

  • 用户偏好:theme = darklang = zh-CN
  • 登录态:tokenuserId(注意安全性)
  • 引导页是否展示:hasShownGuide = true
  • 一些简单的“记住上一次选择”:lastTab = 2

2.2 典型用法(ArkTS 伪代码示例)

@ohos.data.preferences 为例,示意完整生命周期。

写入数据
import preferences from '@ohos.data.preferences';
import common from '@ohos.app.ability.common';

async function saveUserSettings(darkMode: boolean, language: string) {
  const context = getContext(this) as common.UIAbilityContext;
  const pref = await preferences.getPreferences(context, 'user_settings');

  await pref.put('darkMode', darkMode);
  await pref.put('language', language);
  // 关键:flush 把数据真正写落盘
  await pref.flush();
}
读取数据
async function loadUserSettings(): Promise<{ darkMode: boolean; language: string }> {
  const context = getContext(this) as common.UIAbilityContext;
  const pref = await preferences.getPreferences(context, 'user_settings');

  const darkMode = await pref.get('darkMode', false);
  const language = await pref.get('language', 'zh-CN');

  return { darkMode, language };
}
删除 / 清空
await pref.delete('token');
await pref.flush();

// 或者完全清空
await pref.clear();
await pref.flush();

2.3 优点 ✅

  • 接口简单,写起来毫无心理负担
  • 轻量:适合配置类频繁读写
  • 天然 key-value,拿来存“小数据点”极顺手
  • 系统帮你管理文件位置,无需了解底层路径

2.4 缺点 ❌

  • 不适合存大对象 / 列表 / 海量数据
  • 不支持复杂查询:没有“WHERE / ORDER BY”
  • 没有事务概念:不能保证多个 put 的事务一致性
  • 滥用很容易变成“垃圾堆”,到处是神秘 key

2.5 一条血泪经验

千万不要拿 Preferences 当“简易数据库”用。

如果你开始往里塞:

  • 100+ 个键
  • 每个键对应一个大 JSON 字符串
  • 字符串里有几十个字段

那你未来维护的时候,一定会想穿越回去给当时的自己一巴掌


三、Data Storage / 文件:你想怎么存,我就怎么给你存

3.1 它到底是什么?

文件存储是所有平台的老朋友了:你拿到一个路径 / 句柄,随便读 / 随便写。
在鸿蒙里,典型通过 fileio 或媒体相关 API 来操作。

适合:

  • 本地缓存(比如接口数据用 JSON 文件缓存)
  • 大文本(日志、导出文件)
  • 二进制数据(图片、音频、视频、下载资源)
  • “不需要复杂查询,只是要读/写整块内容”的场景

3.2 ArkTS 简单示例:写一个 JSON 缓存

import fileio from '@ohos.fileio';

const CACHE_PATH = '/data/storage/el2/base/files/article_cache.json';

export async function saveArticleCache(data: object) {
  const json = JSON.stringify(data);
  const fd = fileio.openSync(CACHE_PATH, fileio.OpenMode.CREATE | fileio.OpenMode.TRUNC | fileio.OpenMode.READ_WRITE);
  fileio.writeSync(fd, json);
  fileio.closeSync(fd);
}

export async function readArticleCache<T>(): Promise<T | null> {
  if (!fileio.accessSync(CACHE_PATH)) {
    return null;
  }
  const fd = fileio.openSync(CACHE_PATH, fileio.OpenMode.READ_WRITE);
  const buf = new ArrayBuffer(1024 * 1024); // 简单示例,实战中自己控制大小
  const len = fileio.readSync(fd, buf);
  fileio.closeSync(fd);

  const json = String.fromCharCode(...new Uint8Array(buf, 0, len));
  return JSON.parse(json) as T;
}

真实项目里当然需要更完善的 buffer 管理,这里只是个味道。

3.3 优点 ✅

  • 完全自由:

    • 想存 JSON、二进制、Protobuf 都行
  • 不受“表结构”约束:非常适合半结构化 / 非结构化数据

  • 适合大数据量:几十 MB 甚至上百 MB 很常见

  • 与网络层、下载模块契合:下载到文件、断点续传等

3.4 缺点 ❌

  • 你要自己管理文件名 / 目录结构
  • 没有查询能力:想找某一条数据只能自己读出来过滤
  • 没有事务,一半写完崩了就可能出现“写了一半”的脏数据
  • 并发读写要自己注意锁 / 冲突

3.5 典型使用误区

误区:用一个 JSON 文件当数据库表用。

比如你搞了一个 todos.json

[
  { "id": 1, "title": "买菜", "done": false },
  { "id": 2, "title": "写代码", "done": false },
  ...
  // 1000+ 条
]

每次增删改都:

  1. 整个文件读进来
  2. 在内存里改
  3. 整个 JSON 再写回去

这样搞:

  • 数据一多,读写都很慢
  • 任何一个时刻崩了,容易导致文件半写坏掉
  • 还没有事务,改两条的时候,只成功一条也不稀奇

总结一句:
文件非常适合“整体大块读写”,不适合高频、小粒度、带复杂条件的业务数据。


四、RDB:当你开始认真搞业务数据,就该请它出场

4.1 它是什么?

RDB = Relational DataBase,本质上是鸿蒙里的 SQLite 那一挂:

  • 有“”:一个 .db 文件
  • 有“”:user / order / message
  • 有“字段”:id / name / created_at
  • 有“SQL”:SELECT / INSERT / UPDATE / DELETE
  • 有“索引”:查询快不快全看你索引建得好不好

适合:

  • 中大型业务数据:用户信息、本地消息、搜索历史、离线数据
  • 需要多条件查询 / 排序 / 分页
  • 希望“数据结构清晰、可维护”的场景
  • 要保证事务一致性(比如同时插入多条记录,要么都成功,要么都失败)

4.2 建一个最典型的表:用户信息表

import relationalStore from '@ohos.data.relationalStore';
import common from '@ohos.app.ability.common';

const STORE_CONFIG: relationalStore.StoreConfig = {
  name: 'app.db',
  securityLevel: relationalStore.SecurityLevel.S1
};

let globalStore: relationalStore.RdbStore | undefined;

export async function getRdbStore(): Promise<relationalStore.RdbStore> {
  if (globalStore) return globalStore;

  const context = getContext(this) as common.UIAbilityContext;
  return new Promise((resolve, reject) => {
    relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => {
      if (err) {
        reject(err);
        return;
      }
      globalStore = store;
      resolve(store);
    });
  });
}
初始化表结构
async function initUserTable() {
  const store = await getRdbStore();
  await store.executeSql(`
    CREATE TABLE IF NOT EXISTS user (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE,
      age INTEGER,
      created_at INTEGER
    )
  `);
}
插入、查询、更新示例
export async function insertUser(name: string, email: string, age: number) {
  const store = await getRdbStore();
  const valueBucket = {
    name,
    email,
    age,
    created_at: Date.now()
  };
  await store.insert('user', valueBucket);
}

export async function queryAdultUsers(): Promise<Array<{ id: number; name: string }>> {
  const store = await getRdbStore();
  return new Promise((resolve, reject) => {
    store.querySql('SELECT id, name FROM user WHERE age >= ? ORDER BY created_at DESC', [18],
      (err, resultSet) => {
        if (err) {
          reject(err);
          return;
        }
        let res: Array<{ id: number; name: string }> = [];
        while (resultSet.goToNextRow()) {
          res.push({
            id: resultSet.getLong(resultSet.getColumnIndex('id')),
            name: resultSet.getString(resultSet.getColumnIndex('name'))
          });
        }
        resultSet.close();
        resolve(res);
      });
  });
}

export async function updateUserEmail(id: number, email: string) {
  const store = await getRdbStore();
  await store.executeSql('UPDATE user SET email = ? WHERE id = ?', [email, id]);
}

4.3 优点 ✅

  • 结构化强:字段是什么就是什么,不会混来混去
  • 支持复杂查询、多条件、排序、分页
  • 支持事务(比如批量插入 / 修改)
  • 性能好:合理建索引的话,几万行数据照样飞快
  • 非常适合“核心业务数据”的本地持久化

4.4 缺点 ❌

  • 初学者会嫌“麻烦”——要建表,要设计字段,要写 SQL
  • 迭代表结构需要做迁移(版本管理)
  • 简单项目刚起步的时候,用 RDB 可能觉得“有点重”

4.5 小结一句:

只要你在想:
“这些数据会越来越多,而且是业务的核心资产。”
那就老老实实用 RDB,
别再纠结“是不是可以用 Preferences/文件凑合一下”。


五、分布式数据库:多设备同步的灵魂玩法

5.1 它解决的到底是什么问题?

分布式数据库不是给你用来“取代 RDB 或 Preferences”的,而是解决一个完全不同维度的问题:

数据不再只属于“这台设备”,而是属于“这个用户 + 他的设备群”。

典型场景:

  • 手机 + 平板 + PC 同一个账号登录:

    • 备忘录、待办、收藏、设置自动同步
  • 手表记录的步数、心率,出现在手机健康 App 里

  • TV 继续播放手机上刚看了一半的视频

在鸿蒙里,一般会遇到:

  • 分布式 KV(Key-Value Store)
  • 分布式 RDB(关系型 + 多端同步)

我们这里以“分布式 KV”来示意。

5.2 分布式 KV 简单示例

import distributedData from '@ohos.data.distributedData';

let kvManager: distributedData.KVManager | undefined;
let kvStore: distributedData.SingleKVStore | undefined;

async function getKvStore(): Promise<distributedData.SingleKVStore> {
  if (kvStore) return kvStore;

  const context = getContext(this) as any;

  kvManager = distributedData.createKVManager({
    context,
    bundleName: 'com.example.app',
  });

  return new Promise((resolve, reject) => {
    kvManager!.getKVStore(
      {
        storeId: 'note_store',
        isAutoSync: true,
        kvStoreType: distributedData.KVStoreType.SINGLE_VERSION
      },
      (err, store) => {
        if (err) {
          reject(err);
          return;
        }
        kvStore = store;
        resolve(store);
      }
    );
  });
}

// 写入一条数据(会同步到其他设备)
export async function saveNote(id: string, content: string) {
  const store = await getKvStore();
  await store.put(id, content);
}

// 读取
export async function getNote(id: string): Promise<string | undefined> {
  const store = await getKvStore();
  return new Promise((resolve) => {
    store.get(id, (err, data) => {
      if (err) {
        resolve(undefined);
        return;
      }
      resolve(data as string);
    });
  });
}

// 监听变更(其他设备改了,我这边能收到)
export async function subscribeNoteChange(onChange: (id: string, value: string) => void) {
  const store = await getKvStore();
  store.on('dataChange', {
    onChange: (change) => {
      change.insertEntries.forEach(e => onChange(e.key, e.value as string));
      change.updateEntries.forEach(e => onChange(e.key, e.value as string));
    }
  });
}

5.3 优点 ✅

  • 多设备自动同步:你只写一份,系统帮你和其他设备对齐
  • API 相对简单(如果你只做 KV)
  • 和鸿蒙“多端协同”的理念高度契合

5.4 缺点 ❌

  • 不适合存特别大的数据块
  • 不适合搞复杂查询(KV 天然是 key → value)
  • 需要考虑网络、同步时机、冲突处理等问题
  • 对“同账号多设备”的场景比较友好,单设备纯离线的话略显浪费

六、适用场景对比:到底什么时候该选谁?

我们直接上一个“更实用”的对比表,尽量按你真会遇到的问题来排。

6.1 从“数据形态”角度看

问题 最推荐 次选
几个简单的开关 / 配置 Preferences 分布式 KV(需要多端同步时)
大段 JSON 文本 / 缓存 文件 RDB(如果后面要查)
结构明确的业务数据(订单、用户) RDB 分布式 RDB
图片 / 音频 / 视频 文件 / 媒体库 分布式 + 文件(看业务需要)
多设备同步的小数据(设置、草稿) 分布式 KV RDB + 自己做同步

6.2 从“规模 + 性能”角度看

维度 Preferences 文件 RDB 分布式数据库
数据量(条数) 几十(最多几百) 看文件大小(MB 级) 几千、几万、十几万都 OK 视设计而定,一般偏中小
单条大小 小(字符串 / bool 等) 小~中(KB~MB) 行级数据 小(KB 级)
查询复杂度 O(1) by key 需要自己读出+遍历 SQL,WHERE/ORDER/LIMIT 随便搞 通常根据 key 或简单遍历
读写性能 非常快(轻量) 取决于文件大小 很快(建索引后更快) 本地快,同步取决于网络
事务一致性 有事务,支持事务块 一般有基本保证,需按文档理解

6.3 从“开发复杂度”角度看

指标 Preferences 文件 RDB 分布式数据库
上手难度 ☆(最简单) ☆☆ ☆☆☆ ☆☆☆
需要设计结构 自己约定 中(数据建模) 中~高(还要考虑同步)
迁移成本 中~高(表结构迁移) 中~高

一句话:你花在 RDB 上的设计时间,会在维护期加倍赚回来
而你在 Preferences / 文件上“省的那点时间”,可能会在后期 debug 时痛哭流涕地还回去。


七、实战:做一个小应用,四种方案一起上场

假设我们要做一个不算太小的鸿蒙 App:“多端同步待办 + 日志 + 设置”

功能需求:

  1. 待办列表:标题、描述、是否完成、创建时间、优先级
  2. 用户设置:主题、语言、是否开启通知等
  3. 离线缓存:最近 50 条操作日志,支持导出为文件
  4. 多端同步:同一账号在手机 / 平板上看到同一份待办

我们看看如何组合这四种方案。

7.1 合理选型方案

  1. 待办列表(Todo)

    • 本地:RDB
    • 多端同步:分布式 KV 或 分布式 RDB

    设计:

    • todo 表:id, title, desc, done, priority, updated_at
    • 分布式部分可以只同步变更记录(比如通过 KV 存“增量变化”)
  2. 用户设置(Settings)

    • 单设备:Preferences(本机设置)
    • 多设备一致:分布式 KV + 本地 Preferences 做缓存
  3. 操作日志(Logs)

    • 文件存储:每天一份 log_YYYYMMDD.txt 或统一 app.log
    • 支持一键导出/上传
  4. 临时搜索结果 / 缓存列表

    • 可以用文件 + 内存缓存

7.2 简化版数据层代码结构

/service
  todo-rdb.ets          # 用 RDB 管理待办核心数据
  todo-sync.ets         # 用分布式 KV 做多端同步  
  settings-pref.ets     # 用户设置:Preferences 封装
  settings-distributed.ets # 多端同步设置(可选)
  log-file.ets          # 日志写文件
待办 RDB:插入 + 查询
// todo-rdb.ets
export async function addTodo(item: TodoItem) {
  const store = await getRdbStore();
  await store.insert('todo', {
    title: item.title,
    desc: item.desc,
    done: item.done ? 1 : 0,
    priority: item.priority,
    updated_at: Date.now()
  });
}

export async function listTodo(): Promise<TodoItem[]> {
  const store = await getRdbStore();
  return new Promise((resolve, reject) => {
    store.querySql('SELECT * FROM todo ORDER BY updated_at DESC', [],
      (err, resultSet) => {
        if (err) {
          reject(err);
          return;
        }
        let list: TodoItem[] = [];
        while (resultSet.goToNextRow()) {
          list.push({
            id: resultSet.getLong(resultSet.getColumnIndex('id')),
            title: resultSet.getString(resultSet.getColumnIndex('title')),
            desc: resultSet.getString(resultSet.getColumnIndex('desc')),
            done: resultSet.getLong(resultSet.getColumnIndex('done')) === 1,
            priority: resultSet.getLong(resultSet.getColumnIndex('priority')),
            updatedAt: resultSet.getLong(resultSet.getColumnIndex('updated_at')),
          });
        }
        resultSet.close();
        resolve(list);
      });
  });
}
设置:Preferences + 分布式 KV 双写
// settings-pref.ets
export async function saveLocalSettings(settings: AppSettings) {
  const context = getContext(this) as common.UIAbilityContext;
  const pref = await preferences.getPreferences(context, 'settings');
  await pref.put('theme', settings.theme);
  await pref.put('lang', settings.lang);
  await pref.put('notify', settings.notify);
  await pref.flush();
}

// settings-distributed.ets
export async function syncSettingsToCloud(settings: AppSettings) {
  const store = await getKvStore();
  await store.put('settings', JSON.stringify(settings));
}

export async function loadSettings(): Promise<AppSettings> {
  // 1. 优先用本地 Preferences
  // 2. 若本地为空,尝试从分布式 KV 获取后落地
}
日志文件:简单到爆,但非常好用
// log-file.ets
const LOG_PATH = '/data/storage/el2/base/files/app.log';

export function appendLog(line: string) {
  const fd = fileio.openSync(LOG_PATH,
    fileio.OpenMode.CREATE | fileio.OpenMode.READ_WRITE | fileio.OpenMode.APPEND);
  const content = `[${new Date().toISOString()}] ${line}\n`;
  fileio.writeSync(fd, content);
  fileio.closeSync(fd);
}

7.3 如果你选错,会怎样?(真实翻车剧本)

错误玩法 1:把所有待办都存 Preferences

await pref.put('todo_list', JSON.stringify(bigArray));

后果:

  • 每次增删改都得读整个数组 → 改 → 写回
  • 待办一多,首屏加载会变慢得肉眼可见
  • 你还不能做复杂筛选(比如“优先级=高 且 未完成 且 最近 7 天”)

错误玩法 2:把用户设置写进 RDB

虽然“功能上没毛病”,但:

  • 对读取这种小配置来说,RDB 有点太重
  • 多一个 service,多几层封装
  • Preferences 解决的问题非要让 RDB 来解决,其实是在浪费时间

错误玩法 3:完全不考虑分布式

假设你的 App 是“多设备协同待办”,结果你所有数据只存本地 RDB:

  • 手机上勾选了“已完成”,平板上还是“未完成”
  • 用户会以为:“这同步做坏了”
  • 你再补同步方案,就得做一堆复杂补丁

正确姿势:
核心数据仍用 RDB 管本地一致性,“改变记录”或最终快照用分布式 KV 同步出去,多端收到后再更新各自 RDB。


八、性能与选型:一套“简单粗暴但十分好用”的决策树

最后给你一张“脑子累了时用的图”:

每次你要做数据持久化的时候,就按下面几步问自己。

Step 1:数据是不是跨设备要保持一致?

  • 是 → 优先考虑“分布式数据库”

    • 小数据/配置:分布式 KV
    • 结构复杂:分布式 RDB + 本地缓存
  • 否 → 看下一步

Step 2:是不是结构化业务数据?会不会越来越多?

  • 是 → RDB,乖乖设计表
  • 否 → 看下一步

Step 3:是不是简单配置/状态?

  • 是 → Preferences
  • 否 → 看下一步

Step 4:是不是偏文件/缓存性质的数据?

  • 文本、JSON、大对象、下载、媒体
    → 文件存储

结语:数据持久化选型,决定的是“你未来会不会被自己写的东西拖死”

很多人刚开始做 App,心态大概是这样的:

“先实现功能,数据这块先随便搞搞,以后再重构。”

然后你会发现:以后永远不会自己来。
等到你真想重构时,项目已经到处都是:

  • 随手命名的 Preferences key
  • 到底哪个 JSON 里有哪个字段谁也说不清
  • 神秘的 .db 文件里有一堆“为了赶工随便建的表”

而如果你在一开始,就对这四种方案有一个明确认知:

  • Preferences → 小配置、小状态
  • 文件 → 大块数据、缓存、日志
  • RDB → 业务核心、本地表结构
  • 分布式数据库 → 多端协同、账号级数据

你会发现:

  • 数据越来越多时,代码结构却越来越
  • 新需求来了,不是“完了我得重写一遍”,而是“在哪一层加一点东西就行”
  • 你的 App 不只是“能跑”,而是“跑得稳、跑得久、跑得起规模”

数据,是应用的“骨头”。
你想做个有战斗力的鸿蒙应用,先把骨架打好。

如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐