HarmonyOS7 数据持久化:Preferences 和 RDB 到底选哪个?
文章目录
- 前言
- 两种方案定位
- Preferences 实战:轻量 KV 存储
- 完整代码
- API 逐个讲解
- 使用示例
- RDB 实战:关系型数据库 CRUD
- 完整代码
- SQL 和 API 讲解
- 性能对比数据
- 选型建议
- 写在最后
前言
每次做新项目,数据持久化这块我都会纠结——用 Preferences 还是 RDB?选轻量了不够用,选重量了又杀鸡用牛刀。踩了几次坑之后,我总算搞明白了这俩的边界。今天把经验分享一下,帮你少走弯路。
存个用户设置、缓存个登录状态,用 Preferences 三行代码搞定。但要存聊天记录、商品列表这种有关系的复杂数据,Preferences 就撑不住了,得上 RDB。问题在于,很多人不知道什么时候该选哪个,干脆全用 RDB,结果简单配置项也要写 SQL,累不累?
两种方案定位
先看核心区别,一张表搞定:
| 维度 | Preferences | RDB (关系型数据库) |
|---|---|---|
| 底层 | KV 键值对文件 | SQLite |
| 数据结构 | Key-Value | 表、行、列 |
| 适合数据量 | < 1万条,轻量级 | 无硬限制,百万级都行 |
| 查询能力 | 只能按 Key 查 | 支持 SQL 全量查询 |
| 事务支持 | 无 | 有(begin/commit/rollback) |
| 数据类型 | number/string/boolean/Array | 任意 SQL 类型 |
| 并发安全 | 不支持多进程 | 支持 |
| 学习成本 | 低,5 分钟上手 | 中,要会写 SQL |
| 典型场景 | 用户设置、登录标记、引导状态 | 聊天记录、商品数据、订单信息 |
|Key 限制| 长度 ≤ 1024 字节 | 无此限制 |
|Value 限制| string ≤ 16MB | 无此限制 |
|缓存机制| 内存缓存,读取极快 | 无内存缓存 |
一句话总结:简单配置用 Preferences,复杂关系用 RDB。
Preferences 实战:轻量 KV 存储
场景:保存用户设置(主题、字号、通知开关)。
完整代码
import{preferences}from'@kit.ArkData'import{Context}from'@kit.AbilityKit'constPREF_NAME='user_settings'classPrefManager{privatepref:preferences.Preferences|null=nullasyncinit(context:Context){this.pref=awaitpreferences.getPreferences(context,PREF_NAME)}asyncput(key:string,value:preferences.ValueType){if(!this.pref)returnawaitthis.pref.put(key,value)awaitthis.pref.flush()}asyncget(key:string,defaultValue:preferences.ValueType):Promise<preferences.ValueType>{if(!this.pref)returndefaultValuereturnawaitthis.pref.get(key,defaultValue)}asyncremove(key:string){if(!this.pref)returnawaitthis.pref.delete(key)awaitthis.pref.flush()}asyncclear(){if(!this.pref)returnawaitthis.pref.clear()awaitthis.pref.flush()}}exportconstprefManager=newPrefManager()API 逐个讲解
getPreferences(context, name):根据 name 获取 Preferences 实例。同一个 name 全局只有一个实例,系统会缓存。put(key, value):写入键值对。数据先写到内存缓存,调用flush()才真正写入磁盘。flush():这个必须调!不调 flush,应用崩溃数据就丢了。put之后紧跟flush是标准操作。get(key, defaultValue):读取数据。没有这个 key 就返回默认值,不会报错。delete(key):删除指定 key,也要跟flush()。clear():清空所有数据,慎用。
使用示例
// 初始化(在 EntryAbility.onCreate 中)awaitprefManager.init(this.context)// 存储用户设置awaitprefManager.put('theme','dark')awaitprefManager.put('fontSize',18)awaitprefManager.put('notifications',true)// 读取consttheme=awaitprefManager.get('theme','light')asstringconstfontSize=awaitprefManager.get('fontSize',14)asnumberconstnotify=awaitprefManager.get('notifications',false)asboolean就这么简单。存配置项、开关、标记,Preferences 真的够了。
RDB 实战:关系型数据库 CRUD
场景:做一个记账 App,需要存账单数据(金额、分类、日期、备注)。
完整代码
import{relationalStore}from'@kit.ArkData'import{Context}from'@kit.AbilityKit'constDB_NAME='bill.db'constSTORE_CONFIG:relationalStore.StoreConfig={name:DB_NAME,securityLevel:relationalStore.SecurityLevel.S1}constCREATE_TABLE_SQL=`CREATE TABLE IF NOT EXISTS bills ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount REAL NOT NULL, category TEXT NOT NULL, date TEXT NOT NULL, note TEXT, created_at INTEGER )`classBillDB{privaterdbStore:relationalStore.RdbStore|null=nullasyncinit(context:Context){this.rdbStore=awaitrelationalStore.getRdbStore(context,STORE_CONFIG)awaitthis.rdbStore.executeSql(CREATE_TABLE_SQL)}asyncinsert(bill:BillItem):Promise<number>{if(!this.rdbStore)return-1constvalues:relationalStore.ValuesBucket={amount:bill.amount,category:bill.category,date:bill.date,note:bill.note||'',created_at:Date.now()}constrowId=awaitthis.rdbStore.insert('bills',values)returnrowId}asyncqueryByDate(startDate:string,endDate:string):Promise<BillItem[]>{if(!this.rdbStore)return[]constpredicates=newrelationalStore.RdbPredicates('bills')predicates.between('date',startDate,endDate).orderByDesc('date')constresultSet=awaitthis.rdbStore.query(predicates)returnthis.resultSetToList(resultSet)}asyncdeleteById(id:number):Promise<number>{if(!this.rdbStore)return0constpredicates=newrelationalStore.RdbPredicates('bills')predicates.equalTo('id',id)returnawaitthis.rdbStore.delete(predicates)}asyncupdateById(id:number,bill:Partial<BillItem>):Promise<number>{if(!this.rdbStore)return0constvalues:relationalStore.ValuesBucket={}if(bill.amount!==undefined)values.amount=bill.amountif(bill.category!==undefined)values.category=bill.categoryif(bill.note!==undefined)values.note=bill.noteconstpredicates=newrelationalStore.RdbPredicates('bills')predicates.equalTo('id',id)returnawaitthis.rdbStore.update(values,predicates)}privateresultSetToList(resultSet:relationalStore.ResultSet):BillItem[]{constlist:BillItem[]=[]if(resultSet.rowCount<=0){resultSet.close()returnlist}resultSet.goToFirstRow()do{constbill:BillItem={id:resultSet.getLong(resultSet.getColumnIndex('id')),amount:resultSet.getDouble(resultSet.getColumnIndex('amount')),category:resultSet.getString(resultSet.getColumnIndex('category')),date:resultSet.getString(resultSet.getColumnIndex('date')),note:resultSet.getString(resultSet.getColumnIndex('note'))}list.push(bill)}while(resultSet.goToNextRow())resultSet.close()returnlist}}interfaceBillItem{id?:numberamount:numbercategory:stringdate:stringnote?:string}exportconstbillDB=newBillDB()SQL 和 API 讲解
建表 SQL:
INTEGER PRIMARY KEY AUTOINCREMENT:主键自增,插入时不用手动指定 idREAL NOT NULL:金额用浮点类型,不允许为空TEXT NOT NULL:分类和日期用字符串,不允许为空TEXT:备注可空
CRUD 操作:
- 增:
insert(tableName, valuesBucket)—— valuesBucket 是键值对,key 对应列名 - 查:
RdbPredicates构建查询条件,between做范围查询,equalTo做精确匹配 - 改:
update(valuesBucket, predicates)—— 只更新 valuesBucket 里的字段 - 删:
delete(predicates)—— 按 predicates 条件删除
ResultSet 解析:
这是 RDB 最烦的部分。query返回ResultSet,要手动遍历、按列名取值、最后close()。忘 close 会内存泄漏,我踩过。
性能对比数据
实测数据(1000 条记录,中端手机):
| 操作 | Preferences | RDB |
|---|---|---|
| 写入 1 条 | < 1ms | 2-5ms |
| 读取 1 条 | < 1ms | 3-8ms |
| 写入 100 条 | 10-20ms | 50-100ms |
| 条件查询 100 条 | 不支持 | 10-30ms |
| 全量查询 1000 条 | 不现实 | 50-100ms |
Preferences 读写速度碾压 RDB,因为数据全在内存里。但一旦涉及条件查询、排序、关联,Preferences 直接歇菜。
选型建议
别纠结了,按这张决策表选:
| 你的需求 | 选哪个 | 理由 |
|---|---|---|
| 用户设置、开关、标记 | Preferences | 轻量快速,KV 就够了 |
| 登录态缓存 | Preferences | 就一个 token,用啥数据库 |
| 引导页是否展示过 | Preferences | 一个 boolean 搞定 |
| 购物车(< 50 条) | Preferences | 数据少,简单 |
| 购物车(> 50 条) | RDB | 数据多了需要筛选排序 |
| 聊天记录 | RDB | 数据量大,需要按时间查询 |
| 商品数据 | RDB | 需要分类筛选排序 |
| 订单记录 | RDB | 多表关联,复杂查询 |
| 收藏列表(> 100 条) | RDB | 需要分页查询 |
我的原则:能 Preferences 就不 RDB,数据一复杂就别硬撑。
两个方案也不冲突,可以在同一个项目里都用——设置用 Preferences,业务数据用 RDB,各司其职。
写在最后
数据持久化没有银弹,选型就是看场景。Preferences 是一把快刀,削苹果削黄瓜都好用,但别拿它砍树。RDB 是电锯,砍树一流,削苹果就太费劲了。
搞清楚自己要存什么、怎么查、数据量多大,答案就出来了。
