鸿蒙开发 - 数据持久化 Preferences (内存存储) (封装)
这篇文章介绍鸿蒙中的 Preferences
,它是一种轻量级存储方式,数据存储在内存中,用于存储少量的数据。
可以执行 flush()
方法将内存中的数据写入到磁盘文件,保证下次重启后数据可以继续使用,下面会有介绍到
主要特性:
- 数据存储形式:键值对,键的类型为字符串,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型 点击查看
- 轻量级:Preferences主要用于存储少量的数据,不适合用来存储大量的数据集。所有数据会被加载到内存中,过多的数据可能导致内存占用过高
- 快速访问:由于数据被缓存在内存中,因此读取速度非常快
- 同步与异步操作:提供了同步和异步两种方式来处理数据的读写操作
初始化 Preferences 实例
import { preferences } from '@kit.ArkData'
import { BusinessError } from '@kit.BasicServicesKit'
@Entry
@Component
struct Index {
dataPreferences: preferences.Preferences | null = null
aboutToAppear(): void {
let options: preferences.Options = { name: 'myStore' }
this.dataPreferences = preferences.getPreferencesSync(getContext(this), options)
}
}
preferences.getPreferencesSync(context: Context, options: Options)
获取 Preferences 实例Options: { name: string }
指定 Preferences 实例的名称
操作数据的方法(以下都是异步写法,下面会有同步写法)
写入数据 put
将数据写入缓存中
示例代码:
build() {
Column() {
Button('登录')
.onClick(async () => {
this.dataPreferences?.put('name', '诡术妖姬', (err: BusinessError) => {
if (err) {
console.error(`写入数据失败:code=${err.code},message=${err.message}`)
return
}
console.log('写入数据成功')
})
}
}
}
当我们写入数据的时候,如果不需要关注回调通知,可以忽略掉,如下:
this.dataPreferences?.put('name', '诡术妖姬')
获取数据 get
从缓存中获取键对应的值
get
方法中第一个参数是Key,第二个参数是默认值,如果获取的数据为null或者非默认值类型就会返回默认值
示例代码:
this.dataPreferences?.get('name', '默认值', (err: BusinessError, val: preferences.ValueType) => {
if (err) {
console.error(`获取数据失败: code=${err.code}, message=${err.message}`)
return
}
console.log(`获取数据成功: val=${val}`)
})
获取所有数据 getAll
从缓存中获取所有的键值数据
示例代码:
this.dataPreferences?.getAll((err: BusinessError, val: preferences.ValueType) => {
console.log('获取所有数据:', JSON.stringify(val)) // 获取所有数据: {"name1":"武器","name2":"杰斯","name3":"酒桶"}
})
检查是否存在 has
检查是否存在指定Key的键值数据,返回值是 boolean
示例代码:
this.dataPreferences?.has('name', (err: BusinessError, flag: boolean) => {
if (err) {
console.error(`检查失败: code=${err.code}, message=${err.message}`)
return
}
console.log('检查的key值是否存在:', flag) // true
})
this.dataPreferences?.has('name22222', (err: BusinessError, flag: boolean) => {
if (err) {
console.error(`检查失败:code=${err.code}, message=${err.message}`)
return
}
console.log('检查的key值是否存在:', flag) // false
})
删除数据 delete
删除指定key的键值数据
示例代码:
this.dataPreferences?.delete('name', (err: BusinessError) => {
if (err) {
console.error(`删除失败: code=${err.code}, message=${err.message}`)
return
}
console.log('删除成功')
})
删除所有数据 clear
删除缓存中的所有数据
示例代码:
this.dataPreferences?.clear((err: BusinessError) => {
if (err) {
console.error(`删除所有数据失败: code=${err.code}, message=${err.message}`)
}
console.log('删除所有数据成功')
})
将缓存数据同步到磁盘文件 flush
作用: flush
主要用于将内存的更改同步到磁盘文件中。当操作了数据(例如添加、更新、删除)后,这些更改首先会保存在内存中。调用
flush
会将这些更改写入到磁盘中,从而确保数据在应用程序重启或设备重启后仍然可用。
示例代码:
this.dataPreferences?.flush((err: BusinessError) => {
if (err) {
console.log(`Failed to flush:code=${err.code},message:${err.message}}`)
return
}
console.log('Succeeded in flush')
})
如果不执行 flush() 方法,可能会有以下影响:
- 数据丢失风险:如果应用程序在数据同步到持久化存储之前崩溃或被强制终止,那么内存中的更改可能会丢失。这意味着用户的数据可能无法被正确保存。
- 数据不一致性:在某些情况下,如果没有及时调用 flush(),可能会导致数据在不同时间点读取时出现不一致的情况。例如,一个进程可能已经更新了数据但尚未同步,而另一个进程读取的仍然是旧数据。
- 性能考虑:虽然 flush() 方法可以确保数据持久化,但频繁调用它可能会影响性能。因此,开发者通常会在合适的时间点(如用户明确保存操作或应用程序即将退出时)调用 flush()。 最佳实践
为了确保数据的完整性和一致性,开发者应该遵循以下最佳实践:
- 在关键数据修改后,及时调用 flush() 方法。
- 考虑在应用程序的生命周期事件中(如 onPause() 或 onDestroy())调用 flush(),以确保在应用程序退出前数据被正确保存。
- 避免在性能敏感的操作中频繁调用 flush(),而是根据实际需求选择合适的时间点进行同步。
总之,虽然不调用 flush() 方法在某些情况下可能不会立即导致问题,但为了确保数据的可靠性和持久性,开发者应该养成良好的习惯,在适当的时候调用 flush() 方法。
另外一种异步写法
上面方法示例代码都是用的 callback异步回调 这种方式,除了这种,还有另外一种方式 Promise异步回调,如下:
let promise = this.dataPreferences?.get('name', '默认值',)
promise?.then((val: preferences.ValueType) => {
console.log(`获取数据成功: val=${val}`)
}).catch((err: BusinessError) => {
console.error(`获取数据失败: code=${err.code}, message=${err.message}`)
})
如果不愿意使用Promise
链式回调,也可以使用async/await:
let res = await this.dataPreferences?.get('name', '默认值',)
console.log('获取数据:', res)
同步写法
上面方法除了 flush
没有同步写法,其他方法都有同步写法,比如:
- get
let value = dataPreferences?.getSync('name', '默认值')
- put
let value = dataPreferences?.putSync('name', '小鲁班')
监听数据变更
监听所有key的数据变更
监听所有key,其中一个键值发生变化,就会触发回调。(只有在执行flush方法后,才会触发callback回调)
语法: on(type: 'change', callback: Callback<string>): void
示例代码:
@Entry
@Component
struct Index {
aboutToAppear(): void {
let observer = (key: string) => {
console.log('发生数据变更的key:', key) // 发生数据变更的key:name,诡术妖姬
}
this.dataPreferences.on('change', observer)
}
build() {
Column() {
Button('按钮')
.onClick(() => {
this.dataPreferences?.delete('name', (err: BusinessError) => {
if (err) {
console.error(`删除失败: code=${err.code}, message=${err.message}`)
return
}
console.log('删除成功')
})
this.dataPreferences?.flush()
})
}
}
}
监听指定key的数据变更
可以传入一个数组,指定一部分key进行监听
语法: on(type: 'dataChange', keys: Array<string>, callback: Callback<Record<string, ValueType>>): void
示例代码:
let keys = ['name', 'age']
let observer = (data: Record<string, preferences.ValueType>) => {
for (const keyValue of Object.entries(data)) {
console.info(`observer : ${keyValue}`)
}
console.info("The observer called.")
}
this.dataPreferences.on('dataChange', keys, observer);
监听进程间的数据变更
监听进程间数据变更,多个进程持有同一个首选项文件时,在任意一个进程(包括本进程)执行flush方法,持久化文件发生变更后,触发callback回调。
语法: on(type: 'multiProcessChange', callback: Callback<string>): void
示例代码:
let observer = (key: string) => {
console.log('发生数据变更的key:', key)
}
this.dataPreferences?.on('multiProcessChange', observer)
取消监听
语法: off(type: 'change', callback?: Callback<string>): void
如果需要取消指定的回调函数,就需要填写callback,不填写则全部取消。
示例代码: dataPreferences.off('change', observer);
multiProcessChange
和 dataChange
同理
Preferences使用的约束限制
- 首选项无法保证进程并发安全,会有文件损坏和数据丢失的风险,不支持在多进程场景下使用
- Key键为string类型,要求非空且长度不超过1024个字节
- 如果Value值为string类型,请使用UTF-8编码格式,可以为空,不为空时长度不超过16 * 1024 * 1024个字节
- 内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销
封装
做一个简单的封装玩玩,没有经历过多的测试
- 封装一个 PreferencesUtil 类
import { preferences } from '@kit.ArkData'
import { BusinessError } from '@kit.BasicServicesKit'
class PreferenceUtil {
private _preferences?: preferences.Preferences
private context?: Context
private fileName?: string
constructor(t?: Context, fileName: string = 'myStore') {
this.context = t || getContext(this)
this.fileName = fileName
this.init()
}
init() {
if (this._preferences) {
this._preferences = preferences.getPreferencesSync(this.context, { name: this.fileName })
}
}
get(key: string): Promise<preferences.ValueType> {
return new Promise(resolve => {
this._preferences?.get(key, '', (err: BusinessError, val: preferences.ValueType) => {
resolve(val)
})
})
}
getSync(key: string): Promise<preferences.ValueType | undefined> {
return new Promise(resolve => {
resolve(this._preferences?.getSync(key, ''))
})
}
put(key: string, value: preferences.ValueType) {
this._preferences?.put(key, value, (err: BusinessError) => {
if (err) {
console.error(`写入数据失败:code=${err.code},message=${err.message}`)
return
}
console.log('写入数据成功')
this.flush()
})
}
putSync(key: string, value: preferences.ValueType) {
this._preferences?.putSync(key, value)
this.flush()
}
delete(key: string) {
this._preferences?.delete(key, (err: BusinessError) => {
if (err) {
console.error(`删除失败: code=${err.code}, message=${err.message}`)
return
}
console.log('删除成功')
this.flush()
})
}
deleteSync(key: string) {
this._preferences?.deleteSync(key)
this.flush()
}
clear() {
this._preferences?.clear((err: BusinessError) => {
if (err) {
console.error(`清空所有数据失败: code=${err.code}, message=${err.message}`)
}
console.log('清空所有数据成功')
this.flush()
})
}
clearSync() {
this._preferences?.clearSync()
this.flush()
}
has(key: string): Promise<boolean> {
return new Promise(resolve => {
this._preferences?.has(key, (err: BusinessError, flag: boolean) => {
if (err) {
console.error(`检查失败: code=${err.code}, message=${err.message}`)
return
}
resolve(flag)
console.log('检查的key值是否存在:', flag) // true
})
})
}
hasSync(key: string): Promise<boolean | undefined> {
return new Promise(resolve => {
resolve(this._preferences?.hasSync(key))
})
}
flush() {
this._preferences?.flush((err: BusinessError) => {
if (err) {
console.log(`Failed to flush:code=${err.code},message:${err.message}}`)
return
}
console.log('Succeeded in flush')
})
}
}
export default PreferenceUtil
- 组件内使用
import PreferenceUtil from './storage'
@Entry
@Component
struct Index {
preferences?: PreferencesUtil
async aboutToAppear(): Promise<void>{
this.preferenceUtil = new PreferenceUtil()
console.log('Get name', await this.preferenceUtil?.get('name'))
}
}
问题
数据不一致问题
最后
如果大家有不理解的地方可以留言,或自行阅读文档 文档地址
鸿蒙开发 - 数据持久化 Preferences (内存存储) (封装)的更多相关文章
- IOS开发--数据持久化篇文件存储(二)
前言:个人觉得开发人员最大的悲哀莫过于懂得使用却不明白其中的原理.在代码之前我觉得还是有必要简单阐述下相关的一些知识点. 因为文章或深或浅总有适合的人群.若有朋友发现了其中不正确的观点还望多多指出,不 ...
- iPhone开发 数据持久化总结(终结篇)—5种数据持久化方法对比
iPhone开发 数据持久化总结(终结篇)—5种数据持久化方法对比 iphoneiPhoneIPhoneIPHONEIphone数据持久化 对比总结 本篇对IOS中常用的5种数据持久化方法进行简单 ...
- IOS开发--数据持久化篇之文件存储(一)
前言:个人觉得开发人员最大的悲哀莫过于懂得使用却不明白其中的原理.在代码之前我觉得还是有必要简单阐述下相关的一些知识点. 因为文章或深或浅总有适合的人群.若有朋友发现了其中不正确的观点还望多多指出,不 ...
- iOS开发——数据持久化Swift篇&使用Core Data进行数据持久化存储
使用Core Data进行数据持久化存储 一,Core Data介绍 1,Core Data是iOS5之后才出现的一个数据持久化存储框架,它提供了对象-关系映射(ORM)的功能,即能够将对象转化成 ...
- iOS开发——数据持久化&本地数据的存储(使用NSCoder将对象保存到.plist文件)
本地数据的存储(使用NSCoder将对象保存到.plist文件) 下面通过一个例子将联系人数据保存到沙盒的“documents”目录中.(联系人是一个数组集合,内部为自定义对象). 功能如下: ...
- iOS开发——数据持久化&使用NSUserDefaults来进行本地数据存储
使用NSUserDefaults来进行本地数据存储 NSUserDefaults适合存储轻量级的本地客户端数据,比如记住密码功能,要保存一个系统的用户名.密码.使用NSUserDefaults是首 ...
- iOS开发——数据持久化Swift篇&iCloud云存储
iCloud云存储 import UIKit class ViewController: UIViewController { override func viewDidLoad() { super. ...
- iOS开发——数据持久化Swift篇&通用文件存储
通用文件存储 import UIKit class ViewController: UIViewController { @IBOutlet weak var textField: UITextFie ...
- iOS开发——数据持久化Swift篇&(一)NSUserDefault
NSUserDefault //******************** 5.1 NSUserDefault和对象归档 func useNSUserDefault() { //通过单利来创建一个NSU ...
- iOS开发-数据持久化
iOS中四种最常用的将数据持久存储在iOS文件系统的机制 前三种机制的相同点都是需要找到沙盒里面的Documents的目录路径,附加自己相应的文件名字符串来生成需要的完整路径,再往里面创建.读取.写入 ...
随机推荐
- 腾讯技术岗位笔试&面试题(五)
说在前面 本篇文章是腾讯技术面试题目汇总第五篇. 后续将持续推出互联网大厂,如阿里,腾讯,百度,美团,头条等技术面试题目,以及答案和分析. 欢迎大家点赞关注转发. 1.define.const.typ ...
- node + vue 实现服务端单向推送消息,利用EventSource
场景:后台系统需要实时收到电池报警消息,并语音提醒,前台不需要发送任何东西,所以想的是,服务端单向推送 1. 实现EventSource参考博客: https://www.jqhtml.com/412 ...
- TaurusDB库表时间点极速恢复,大幅缩短数据恢复时间
经过多组实验对比,对于大实例下仅需恢复几张表数据的情况,有显著优化效果.尤其针对游戏业务等需要频繁回档的场景,将大幅度缩短因数据恢复导致的停服时间.后续我们将逐步在公有云上开放此特性,以惠及更多用户. ...
- 序列化与反序列化的概念、基于django原生编写5个接口、drf介绍和快速使用、cbv源码分析
目录 一.序列化反序列化 二.基于django原生编写5个接口 三.drf介绍和快速使用 概念 安装 代码 四.cbv源码分析 一.序列化反序列化 api接口开发,最核心最常见的一个过程就是序列化,所 ...
- C#获得本地IP地址的各种方法
网上有很多种方法可以获取到本地的IP地址.一线常用的有这么些: 枚举本地网卡 using System.Net.NetworkInformation; using System.Net.Sockets ...
- CMake构建学习笔记19-OpenSSL库的构建
1. 概述 OpenSSL是一个开源的加密工具包和库,主要实现了安全套接字层(SSL)和传输层安全(TLS)协议,以及各种加密算法.数字签名.消息摘要.加密证书等功能.这个库可以说是Web开发尤其是H ...
- [Mybatis Plus]lambdaQueryWrapper和QueryWrapper的选择
结论 更推荐使用:LambdaQueryWrapper QueryWrapper:灵活但是不够类型安全 LambdaQueryWrapper:安全 分析 在MyBatis-Plus中,QueryWra ...
- Nginx https证书生成
一.证书和私钥的生成 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1.创建服务器证书密钥文件 server.key: ...
- shell学习之路——取参数
shell中参数的调用方式: 1.$0-9:表示第0个到第9个参数,其中$0表示文件执行路径.如:$0,$1. 2.${10以后}:如果参数数目大于9个,可以用${10},${11}...等方式表示. ...
- Qt开发经验小技巧276-280
对MDI窗体区域设置背景颜色透明,会发现 QMdiArea{background:transparent;} 无效,哪怕是指定颜色 QMdiArea{background:#ff0000;} 或者 Q ...