数据存储
HarmonyOS 提供多种数据存储方案,包括用户首选项(Preferences)、关系型数据库(RDB)、键值型数据库(KV-Store)以及文件存储,满足不同场景需求。
存储方案对比
| 方案 | 数据类型 | 容量 | 适用场景 | 起始版本 |
|---|---|---|---|---|
| Preferences | 键值对 | 小(建议 < 1MB) | 用户设置、配置项 | API 9 |
| RDB | 结构化数据 | 大 | 复杂数据、需要查询 | API 9 |
| KV-Store | 键值对 | 大 | 分布式数据同步 | API 9 |
| 文件 | 任意 | 大 | 图片、日志、缓存 | API 6 |
用户首选项(Preferences)
轻量级键值对存储,适合保存用户设置等小数据。数据存储在 XML 文件中,支持布尔、数值、字符串类型。
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
preferences.getPreferences() | 获取 Preferences 实例 | API 9 |
Preferences.put() | 写入键值对 | API 9 |
Preferences.get() | 读取键值对 | API 9 |
Preferences.delete() | 删除键值对 | API 9 |
Preferences.flush() | 将数据持久化到磁盘 | API 9 |
Preferences.clear() | 清空所有数据 | API 9 |
preferences.deletePreferences() | 删除 Preferences 实例 | API 9 |
ValueType 类型
| 类型 | 说明 |
|---|---|
number | 数值类型 |
string | 字符串类型 |
boolean | 布尔类型 |
基础用法
typescript
import { preferences } from '@kit.ArkData'
class PreferenceUtil {
private static instance: PreferenceUtil
private pref: preferences.Preferences | null = null
static async getInstance(): Promise<PreferenceUtil> {
if (!PreferenceUtil.instance) {
PreferenceUtil.instance = new PreferenceUtil()
const context = getContext()
PreferenceUtil.instance.pref = await preferences.getPreferences(context, 'my_app_prefs')
}
return PreferenceUtil.instance
}
async put(key: string, value: preferences.ValueType): Promise<void> {
if (this.pref) {
await this.pref.put(key, value)
await this.pref.flush() // 必须 flush 才落盘
}
}
async get(key: string, defaultValue: preferences.ValueType): Promise<preferences.ValueType> {
if (this.pref) {
return await this.pref.get(key, defaultValue)
}
return defaultValue
}
async delete(key: string): Promise<void> {
if (this.pref) {
await this.pref.delete(key)
await this.pref.flush()
}
}
async clear(): Promise<void> {
if (this.pref) {
await this.pref.clear()
await this.pref.flush()
}
}
}
// 使用示例
async function demo() {
const pref = await PreferenceUtil.getInstance()
// 保存数据
await pref.put('username', '张三')
await pref.put('isVip', true)
await pref.put('level', 5)
// 读取数据
const username = await pref.get('username', '') as string
const isVip = await pref.get('isVip', false) as boolean
const level = await pref.get('level', 0) as number
console.info(`用户: ${username}, VIP: ${isVip}, 等级: ${level}`)
// 删除数据
await pref.delete('temp_data')
}数据变更监听
typescript
import { preferences } from '@kit.ArkData'
async function watchPreferenceChanges() {
const context = getContext()
const pref = await preferences.getPreferences(context, 'my_app_prefs')
// 注册数据变更监听
pref.on('change', (key: string) => {
console.info(`键 ${key} 的值发生变化`)
})
// 写入数据会触发监听
await pref.put('theme', 'dark')
await pref.flush()
// 取消监听
// pref.off('change')
}关系型数据库(RDB)
基于 SQLite 的封装,适合结构化数据存储。支持 SQL 语句、事务、索引等高级功能。
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
relationalStore.getRdbStore() | 获取 RDB 实例 | API 9 |
RdbStore.executeSql() | 执行 SQL 语句 | API 9 |
RdbStore.insert() | 插入数据 | API 9 |
RdbStore.update() | 更新数据 | API 9 |
RdbStore.delete() | 删除数据 | API 9 |
RdbStore.query() | 条件查询 | API 9 |
RdbStore.querySql() | SQL 查询 | API 9 |
RdbStore.beginTransaction() | 开始事务 | API 9 |
RdbStore.commit() | 提交事务 | API 9 |
RdbStore.rollBack() | 回滚事务 | API 9 |
SecurityLevel 枚举
| 枚举值 | 说明 |
|---|---|
S1 | 数据库在公共目录下,没有安全保护 |
S2 | 数据库在应用私有目录下,有基本安全保护 |
S3 | 数据库加密,有较高安全保护 |
S4 | 数据库加密,有最高安全保护 |
基础用法
typescript
import { relationalStore } from '@kit.ArkData'
class DatabaseHelper {
private static store: relationalStore.RdbStore | null = null
static async init(): Promise<void> {
const context = getContext()
const config: relationalStore.StoreConfig = {
name: 'app.db',
securityLevel: relationalStore.SecurityLevel.S1
}
this.store = await relationalStore.getRdbStore(context, config)
// 创建表
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT,
age INTEGER,
created_at INTEGER
)
`)
}
static async insertUser(user: Omit<User, 'id'>): Promise<number> {
if (!this.store) throw new Error('Database not initialized')
const values = {
name: user.name,
email: user.email,
age: user.age,
created_at: Date.now()
}
return await this.store.insert('users', values)
}
static async getUsers(): Promise<User[]> {
if (!this.store) throw new Error('Database not initialized')
const resultSet = await this.store.querySql('SELECT * FROM users ORDER BY created_at DESC')
const users: User[] = []
while (resultSet.goToNextRow()) {
users.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
email: resultSet.getString(resultSet.getColumnIndex('email')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
createdAt: resultSet.getLong(resultSet.getColumnIndex('created_at'))
})
}
resultSet.close()
return users
}
static async updateUser(id: number, values: Partial<User>): Promise<void> {
if (!this.store) throw new Error('Database not initialized')
const updateValues: relationalStore.ValuesBucket = {}
if (values.name) updateValues.name = values.name
if (values.email) updateValues.email = values.email
if (values.age) updateValues.age = values.age
const predicates = new relationalStore.RdbPredicates('users')
predicates.equalTo('id', id)
await this.store.update(updateValues, predicates)
}
static async deleteUser(id: number): Promise<void> {
if (!this.store) throw new Error('Database not initialized')
const predicates = new relationalStore.RdbPredicates('users')
predicates.equalTo('id', id)
await this.store.delete(predicates)
}
static async searchUsers(keyword: string): Promise<User[]> {
if (!this.store) throw new Error('Database not initialized')
const predicates = new relationalStore.RdbPredicates('users')
predicates.contains('name', keyword)
.or()
.contains('email', keyword)
const resultSet = await this.store.query(predicates, ['id', 'name', 'email', 'age', 'created_at'])
const users: User[] = []
while (resultSet.goToNextRow()) {
users.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
email: resultSet.getString(resultSet.getColumnIndex('email')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
createdAt: resultSet.getLong(resultSet.getColumnIndex('created_at'))
})
}
resultSet.close()
return users
}
static async close(): Promise<void> {
if (this.store) {
await this.store.close()
this.store = null
}
}
}
interface User {
id: number
name: string
email: string
age: number
createdAt: number
}事务处理
typescript
import { relationalStore } from '@kit.ArkData'
async function batchInsertUsers(users: Omit<User, 'id'>[]): Promise<void> {
const store = await relationalStore.getRdbStore(getContext(), {
name: 'app.db',
securityLevel: relationalStore.SecurityLevel.S1
})
try {
store.beginTransaction()
for (const user of users) {
await store.insert('users', {
name: user.name,
email: user.email,
age: user.age,
created_at: Date.now()
})
}
store.commit()
console.info('批量插入成功')
} catch (error) {
store.rollBack()
console.error('批量插入失败,已回滚:', JSON.stringify(error))
throw error
}
}RdbPredicates 条件构造
typescript
import { relationalStore } from '@kit.ArkData'
// 等于
const p1 = new relationalStore.RdbPredicates('users')
p1.equalTo('age', 18)
// 不等于
const p2 = new relationalStore.RdbPredicates('users')
p2.notEqualTo('status', 'deleted')
// 大于/小于
const p3 = new relationalStore.RdbPredicates('users')
p3.greaterThan('age', 18)
.lessThan('age', 60)
// 包含
const p4 = new relationalStore.RdbPredicates('users')
p4.contains('name', '张')
// 以...开头
const p5 = new relationalStore.RdbPredicates('users')
p5.beginsWith('email', 'admin')
// 组合条件
const p6 = new relationalStore.RdbPredicates('users')
p6.equalTo('status', 'active')
.and()
.greaterThan('age', 18)
// 排序
const p7 = new relationalStore.RdbPredicates('users')
p7.orderByAsc('age')
.orderByDesc('created_at')
// 限制数量
const p8 = new relationalStore.RdbPredicates('users')
p8.limit(10)
.offset(20)键值型数据库(KV-Store)
适合需要跨设备同步的键值对数据存储场景。
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
distributedKVManager.getKVManager() | 获取 KVManager 实例 | API 9 |
KVManager.getKVStore() | 获取 KVStore 实例 | API 9 |
SingleKVStore.put() | 写入键值对 | API 9 |
SingleKVStore.get() | 读取键值对 | API 9 |
SingleKVStore.delete() | 删除键值对 | API 9 |
SingleKVStore.on('dataChange') | 监听数据变化 | API 9 |
基础用法
typescript
import { distributedKVManager } from '@kit.ArkData'
class KVStoreUtil {
private static kvManager: distributedKVManager.KVManager | null = null
private static kvStore: distributedKVManager.SingleKVStore | null = null
static async init(): Promise<void> {
const context = getContext()
const config: distributedKVManager.KVManagerConfig = {
bundleName: context.applicationInfo.name,
context
}
this.kvManager = distributedKVManager.getKVManager(config)
const storeConfig: distributedKVManager.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVManager.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVManager.SecurityLevel.S1
}
this.kvStore = await this.kvManager.getKVStore('my_kv_store', storeConfig)
}
static async put(key: string, value: Uint8Array | string | number | boolean): Promise<void> {
if (!this.kvStore) throw new Error('KVStore not initialized')
await this.kvStore.put(key, value)
}
static async get(key: string): Promise<Uint8Array | string | number | boolean | undefined> {
if (!this.kvStore) throw new Error('KVStore not initialized')
return await this.kvStore.get(key)
}
static async delete(key: string): Promise<void> {
if (!this.kvStore) throw new Error('KVStore not initialized')
await this.kvStore.delete(key)
}
static async close(): Promise<void> {
if (this.kvManager && this.kvStore) {
await this.kvManager.closeKVStore('my_kv_store')
this.kvStore = null
}
}
}文件存储
应用沙箱目录读写,无需权限。适合存储图片、日志、缓存等大文件。
应用沙箱目录
| 目录 | 路径获取 | 说明 |
|---|---|---|
| filesDir | context.filesDir | 应用文件目录,持久存储 |
| cacheDir | context.cacheDir | 缓存目录,系统可能自动清理 |
| tempDir | context.tempDir | 临时目录 |
| databaseDir | context.databaseDir | 数据库目录 |
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
fileIo.openSync() | 打开文件 | API 9 |
fileIo.writeSync() | 写入文件 | API 9 |
fileIo.readSync() | 读取文件 | API 9 |
fileIo.closeSync() | 关闭文件 | API 9 |
fileIo.unlinkSync() | 删除文件 | API 9 |
fileIo.accessSync() | 检查文件是否存在 | API 9 |
fileIo.statSync() | 获取文件状态 | API 9 |
fileIo.mkdirSync() | 创建目录 | API 9 |
OpenMode 枚举
| 枚举值 | 说明 |
|---|---|
READ_ONLY | 只读模式 |
WRITE_ONLY | 只写模式 |
READ_WRITE | 读写模式 |
CREATE | 创建文件 |
TRUNC | 截断文件 |
APPEND | 追加模式 |
基础用法
typescript
import { fileIo } from '@kit.CoreFileKit'
class FileUtil {
// 获取应用文件目录
static getFilesDir(): string {
return getContext().filesDir
}
// 写入文本文件
static writeText(fileName: string, content: string): void {
const path = `${this.getFilesDir()}/${fileName}`
const file = fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY)
fileIo.writeSync(file.fd, content)
fileIo.closeSync(file)
}
// 读取文本文件
static readText(fileName: string): string {
const path = `${this.getFilesDir()}/${fileName}`
const file = fileIo.openSync(path, fileIo.OpenMode.READ_ONLY)
const stat = fileIo.statSync(file.fd)
const buf = new ArrayBuffer(stat.size)
fileIo.readSync(file.fd, buf)
fileIo.closeSync(file)
return bufferToString(buf)
}
// 追加写入
static appendText(fileName: string, content: string): void {
const path = `${this.getFilesDir()}/${fileName}`
const file = fileIo.openSync(path, fileIo.OpenMode.APPEND | fileIo.OpenMode.WRITE_ONLY)
fileIo.writeSync(file.fd, content)
fileIo.closeSync(file)
}
// 检查文件是否存在
static exists(fileName: string): boolean {
try {
const path = `${this.getFilesDir()}/${fileName}`
fileIo.accessSync(path)
return true
} catch {
return false
}
}
// 删除文件
static delete(fileName: string): void {
const path = `${this.getFilesDir()}/${fileName}`
fileIo.unlinkSync(path)
}
// 创建目录
static mkdir(dirName: string): void {
const path = `${this.getFilesDir()}/${dirName}`
fileIo.mkdirSync(path)
}
// 获取文件大小
static getSize(fileName: string): number {
const path = `${this.getFilesDir()}/${fileName}`
const file = fileIo.openSync(path, fileIo.OpenMode.READ_ONLY)
const stat = fileIo.statSync(file.fd)
fileIo.closeSync(file)
return stat.size
}
}
// 辅助函数:ArrayBuffer 转字符串
function bufferToString(buffer: ArrayBuffer): string {
const decoder = new TextDecoder('utf-8')
return decoder.decode(buffer)
}
// 辅助函数:字符串转 ArrayBuffer
function stringToBuffer(str: string): ArrayBuffer {
const encoder = new TextEncoder()
return encoder.encode(str).buffer
}
// 使用示例
function demo() {
// 保存 JSON 数据
const data = { name: '张三', age: 25 }
FileUtil.writeText('user.json', JSON.stringify(data))
// 读取 JSON 数据
const json = FileUtil.readText('user.json')
const user = JSON.parse(json)
console.info('用户:', JSON.stringify(user))
// 追加日志
FileUtil.appendText('app.log', `[${new Date().toISOString()}] 应用启动\n`)
// 检查文件
if (FileUtil.exists('user.json')) {
console.info('文件存在,大小:', FileUtil.getSize('user.json'))
}
// 删除文件
FileUtil.delete('user.json')
}完整示例:用户数据管理
typescript
import { preferences } from '@kit.ArkData'
import { relationalStore } from '@kit.ArkData'
@Entry
@Component
struct StorageDemo {
@State username: string = ''
@State users: User[] = []
aboutToAppear() {
this.loadData()
}
async loadData() {
// 从 Preferences 读取
const pref = await PreferenceUtil.getInstance()
const savedName = await pref.get('lastUser', '') as string
this.username = savedName
// 从数据库读取
await DatabaseHelper.init()
this.users = await DatabaseHelper.getUsers()
}
async addUser() {
if (!this.username) return
// 保存到数据库
const id = await DatabaseHelper.insertUser({
name: this.username,
email: `${this.username}@example.com`,
age: 25
})
// 保存到 Preferences
const pref = await PreferenceUtil.getInstance()
await pref.put('lastUser', this.username)
// 刷新列表
this.users = await DatabaseHelper.getUsers()
this.username = ''
}
build() {
Column({ space: 16 }) {
Text('用户管理')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Row({ space: 12 }) {
TextInput({ placeholder: '输入用户名', text: this.username })
.layoutWeight(1)
.onChange((v) => this.username = v)
Button('添加')
.onClick(() => this.addUser())
}
.width('100%')
.padding(16)
List() {
ForEach(this.users, (user: User) => {
ListItem() {
Row() {
Column() {
Text(user.name)
.fontSize(16)
Text(user.email)
.fontSize(12)
.fontColor('#999')
}
.alignItems(HorizontalAlign.Start)
Button('删除')
.fontSize(12)
.onClick(async () => {
await DatabaseHelper.deleteUser(user.id)
this.users = await DatabaseHelper.getUsers()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
}
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}最佳实践
- 选择合适存储方案:小配置用 Preferences,结构化数据用 RDB,大文件用文件存储
- 及时 flush:Preferences 写入后必须调用
flush()才能持久化 - 关闭资源:数据库查询完关闭 ResultSet,文件操作完关闭文件描述符
- 使用事务:批量操作使用事务保证数据一致性
- 异常处理:文件操作可能失败,使用 try/catch 处理
- 数据迁移:应用升级时考虑数据库版本迁移
- 加密敏感数据:涉及敏感信息使用 SecurityLevel.S3/S4 加密存储