#跟着晓明学鸿蒙# 鸿蒙系统开发高级教程 - 应用数据存储实战指南
·
引言
数据存储是任何应用程序的核心需求之一,关系到应用的性能、可靠性和用户体验。鸿蒙系统提供了丰富的数据存储解决方案,从轻量级的首选项存储到功能完备的关系型数据库,再到创新的分布式数据管理,满足不同应用场景的需求。本文将深入探讨鸿蒙系统数据存储的各种技术和最佳实践,帮助开发者选择合适的存储方案并高效实现数据管理功能。
鸿蒙数据存储方案概览
鸿蒙系统提供了多种数据存储方案,主要包括:
- 首选项(Preferences):轻量级键值对存储
- 轻量级存储(LightStorage):基于文件的简单数据存储
- 关系型数据库(RDB):结构化数据的存储和查询
- 对象关系映射(ORM):面向对象的数据访问方式
- 分布式数据库(KVStore):支持多设备数据同步
- 文件存储(File Storage):原始文件操作API
每种方案都有其适用场景和优势,下面将详细介绍各个存储方案的特点、使用方法和最佳实践。
首选项(Preferences)
首选项适用于存储少量的键值对数据,如用户设置、应用配置等。
基本用法
import data_preferences from '@ohos.data.preferences';
// 获取Preferences实例
async function getPreferences() {
const context = getContext(this);
const preferences = await data_preferences.getPreferences(context, 'mySettings');
return preferences;
}
// 存储数据
async function saveSettings() {
const preferences = await getPreferences();
// 存储各种类型的数据
await preferences.put('username', '张三');
await preferences.put('age', 28);
await preferences.put('isVip', true);
await preferences.put('lastLoginTime', new Date().getTime());
// 提交更改
await preferences.flush();
console.info('设置已保存');
}
// 读取数据
async function loadSettings() {
const preferences = await getPreferences();
const username = await preferences.get('username', '默认用户');
const age = await preferences.get('age', 0);
const isVip = await preferences.get('isVip', false);
const lastLoginTime = await preferences.get('lastLoginTime', 0);
console.info(`用户信息: ${username}, ${age}岁, VIP状态: ${isVip}, 上次登录: ${new Date(lastLoginTime)}`);
return { username, age, isVip, lastLoginTime };
}
// 删除数据
async function removeSettings() {
const preferences = await getPreferences();
await preferences.delete('lastLoginTime');
await preferences.flush();
console.info('上次登录时间已删除');
}
// 监听数据变化
async function observeSettings() {
const preferences = await getPreferences();
preferences.on('change', (data) => {
console.info(`数据变化: key=${data.key}`);
});
}
最佳实践
- 使用场景优化:只存储小型配置数据,避免存储大量或复杂数据
- 合理分组:为不同功能模块创建不同的Preferences实例
- 批处理操作:多次修改后统一调用flush()提交,提高性能
- 默认值策略:读取数据时始终提供合理的默认值,增强应用鲁棒性
- 异步处理:Preferences操作为异步API,正确处理Promise
关系型数据库(RDB)
鸿蒙系统的关系型数据库提供了完整的SQL支持,适合存储结构化数据。
数据库创建与基本操作
import data_rdb from '@ohos.data.rdb';
// 定义数据库配置
const DB_CONFIG = {
name: 'UserDatabase.db',
securityLevel: data_rdb.SecurityLevel.S1 // 安全等级
};
// 定义表结构
const SQL_CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT,
avatar BLOB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
// 创建/打开数据库
async function getDatabase() {
const context = getContext(this);
const rdbStore = await data_rdb.getRdbStore(context, DB_CONFIG, 1);
// 创建表
await rdbStore.executeSql(SQL_CREATE_TABLE);
return rdbStore;
}
// 插入数据
async function insertUser(user) {
const rdbStore = await getDatabase();
const userData = {
name: user.name,
age: user.age,
email: user.email,
avatar: user.avatar
};
const rowId = await rdbStore.insert('user', userData);
console.info(`插入用户成功,ID: ${rowId}`);
return rowId;
}
// 更新数据
async function updateUser(id, userData) {
const rdbStore = await getDatabase();
const predicates = new data_rdb.RdbPredicates('user');
predicates.equalTo('id', id);
const rows = await rdbStore.update(userData, predicates);
console.info(`更新了 ${rows} 条记录`);
return rows > 0;
}
// 查询数据
async function queryUsers(ageLimit) {
const rdbStore = await getDatabase();
const predicates = new data_rdb.RdbPredicates('user');
predicates.greaterThan('age', ageLimit)
.orderByDesc('created_at');
const resultSet = await rdbStore.query(predicates, ['id', 'name', 'age', 'email']);
const users = [];
while (resultSet.goToNextRow()) {
users.push({
id: resultSet.getDouble(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
email: resultSet.getString(resultSet.getColumnIndex('email'))
});
}
resultSet.close();
return users;
}
// 删除数据
async function deleteUser(id) {
const rdbStore = await getDatabase();
const predicates = new data_rdb.RdbPredicates('user');
predicates.equalTo('id', id);
const rows = await rdbStore.delete(predicates);
console.info(`删除了 ${rows} 条记录`);
return rows > 0;
}
高级查询与事务处理
// 使用复杂查询
async function advancedQuery() {
const rdbStore = await getDatabase();
// 构建复杂查询条件
const predicates = new data_rdb.RdbPredicates('user');
predicates.beginWrap()
.greaterThan('age', 18)
.and()
.lessThan('age', 60)
.endWrap()
.and()
.like('email', '%@example.com')
.orderByAsc('name')
.limit(10, 0); // 限制10条,从0开始
const resultSet = await rdbStore.query(predicates);
// 处理结果...
resultSet.close();
}
// 事务处理
async function executeTransaction() {
const rdbStore = await getDatabase();
try {
// 开始事务
await rdbStore.beginTransaction();
// 执行一系列操作
const user1 = { name: '李四', age: 30, email: 'lisi@example.com' };
const user2 = { name: '王五', age: 25, email: 'wangwu@example.com' };
await rdbStore.insert('user', user1);
await rdbStore.insert('user', user2);
// 提交事务
await rdbStore.commit();
console.info('事务执行成功');
return true;
} catch (error) {
// 回滚事务
await rdbStore.rollBack();
console.error(`事务执行失败: ${error.message}`);
return false;
}
}
最佳实践
- 表设计规范:合理设计表结构,添加适当索引,遵循数据库范式
- 参数化查询:使用参数化SQL防止SQL注入
- 使用事务:批量操作时使用事务提高性能和保证数据一致性
- 及时关闭资源:使用完ResultSet后及时调用close()方法
- 分页加载:使用limit和offset实现数据分页,避免一次加载过多数据
- 错误处理:完善的异常捕获和错误处理机制
分布式数据管理(KVStore)
鸿蒙系统的分布式数据管理是其特色功能之一,支持多设备数据同步。
基本用法
import distributedKVStore from '@ohos.data.distributedKVStore';
// 创建KVManager
async function getKVManager() {
const kvManager = await distributedKVStore.createKVManager({
bundleName: 'com.example.myapp',
userInfo: {
userId: '0',
userType: 0
}
});
return kvManager;
}
// 创建分布式数据库实例
async function getKVStore() {
const kvManager = await getKVManager();
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: 1
};
const storeId = {
userId: '0',
appId: 'com.example.myapp',
storeId: 'MainKVStore'
};
const kvStore = await kvManager.getKVStore(storeId, options);
return kvStore;
}
// 存储数据
async function putData(key, value) {
const kvStore = await getKVStore();
await kvStore.put(key, value);
console.info(`数据存储成功: ${key}`);
}
// 读取数据
async function getData(key) {
const kvStore = await getKVStore();
const value = await kvStore.get(key);
console.info(`读取数据: ${key} = ${value}`);
return value;
}
// 监听数据变化
async function subscribeChanges() {
const kvStore = await getKVStore();
kvStore.on('dataChange', 1, (changeData) => {
console.info(`收到数据变化通知,插入: ${changeData.insertEntries.length}, 更新: ${changeData.updateEntries.length}, 删除: ${changeData.deleteEntries.length}`);
// 处理变化的数据
for (const entry of changeData.insertEntries) {
console.info(`新增数据: ${entry.key} = ${entry.value}`);
}
});
}
// 设置同步参数
async function configureSyncMode() {
const kvStore = await getKVStore();
// 设置数据同步模式
const SYNC_MODE = distributedKVStore.SyncMode.PUSH_PULL;
// 设置要同步的设备列表
const deviceList = ['deviceId1', 'deviceId2'];
await kvStore.setSyncRange(deviceList, SYNC_MODE);
console.info('同步范围设置成功');
}
分布式场景实例
// 用户个人数据在多设备间同步
async function syncUserProfile() {
// 在手机上更新用户资料
await putData('userProfile', {
name: '张三',
avatar: 'https://example.com/avatar.jpg',
theme: 'dark',
settings: {
notification: true,
language: 'zh-CN'
},
lastUpdate: new Date().getTime()
});
// 数据会自动同步到平板、智能手表等设备
console.info('用户资料已更新并同步到关联设备');
}
// 处理冲突解决
async function handleSyncConflict() {
const kvStore = await getKVStore();
// 设置冲突解决策略
kvStore.setSyncParam({
conflictPolicy: distributedKVStore.ConflictPolicy.DEVICE_LAST_WIN,
allowedDelayMs: 1000
});
}
最佳实践
- 数据分区:根据功能将数据分到不同的KVStore
- 安全策略:重要数据启用加密功能
- 同步策略:根据应用场景设置合适的同步模式和频率
- 冲突处理:设计并实现合理的冲突解决策略
- 资源管理:不再使用时关闭KVStore和KVManager
轻量级存储(LightStorage)
轻量级存储适用于简单的数据持久化需求,基于文件系统实现。
import lightStorage from '@ohos.data.lightStorage';
// 创建或打开LightStorage
async function getLightStorage() {
const context = getContext(this);
const storage = await lightStorage.getStorage({
name: 'appCache',
path: context.filesDir // 应用私有目录
});
return storage;
}
// 存储数据
async function cacheData(key, value) {
const storage = await getLightStorage();
await storage.put(key, value);
await storage.flush(); // 立即写入磁盘
console.info(`缓存数据成功: ${key}`);
}
// 读取数据
async function loadCache(key) {
const storage = await getLightStorage();
const value = await storage.get(key);
console.info(`读取缓存: ${key}`);
return value;
}
// 关闭存储
async function closeStorage() {
const storage = await getLightStorage();
await storage.close();
console.info('已关闭轻量级存储');
}
文件存储
鸿蒙系统提供了完整的文件操作API,适用于处理二进制数据和大文件。
import fs from '@ohos.file.fs';
// 获取应用目录
function getAppDirectories() {
const context = getContext(this);
return {
filesDir: context.filesDir, // 内部存储目录
cacheDir: context.cacheDir, // 缓存目录
tempDir: context.tempDir // 临时目录
};
}
// 写入文件
async function writeFile(filename, content) {
const { filesDir } = getAppDirectories();
const filePath = `${filesDir}/${filename}`;
// 创建或打开文件
const file = await fs.open(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE);
try {
// 写入内容
if (typeof content === 'string') {
await fs.write(file.fd, content);
} else if (content instanceof ArrayBuffer) {
await fs.write(file.fd, content);
}
console.info(`文件写入成功: ${filePath}`);
return filePath;
} finally {
// 关闭文件
await fs.close(file.fd);
}
}
// 读取文件
async function readFile(filename) {
const { filesDir } = getAppDirectories();
const filePath = `${filesDir}/${filename}`;
// 检查文件是否存在
const exists = await fs.access(filePath);
if (!exists) {
console.error(`文件不存在: ${filePath}`);
return null;
}
// 获取文件信息
const stat = await fs.stat(filePath);
// 打开文件
const file = await fs.open(filePath, fs.OpenMode.READ);
try {
// 创建缓冲区
const buffer = new ArrayBuffer(stat.size);
// 读取文件内容
const readLen = await fs.read(file.fd, buffer);
console.info(`读取文件成功: ${filePath}, 大小: ${readLen} 字节`);
return buffer;
} finally {
// 关闭文件
await fs.close(file.fd);
}
}
// 列出目录内容
async function listDirectory(dirPath) {
const dir = await fs.openDir(dirPath);
const entries = [];
let entry;
while ((entry = await fs.readDir(dir)) !== null) {
entries.push({
name: entry.name,
isFile: entry.isFile,
isDirectory: entry.isDirectory
});
}
await fs.closeDir(dir);
return entries;
}
存储方案选择建议
根据不同场景选择合适的存储方案:
-
首选项(Preferences)
- 适用场景:应用设置、用户偏好、简单配置
- 数据特征:键值对、数据量小、读写频率中等
- 优势:使用简单、读写快速
-
关系型数据库(RDB)
- 适用场景:结构化数据、复杂查询、大量数据
- 数据特征:表格化数据、有明确关系、需要事务支持
- 优势:强大的查询能力、事务支持、数据完整性保障
-
分布式数据库(KVStore)
- 适用场景:多设备应用、需要跨设备同步的数据
- 数据特征:需要实时同步、处理冲突、支持离线操作
- 优势:自动同步、冲突解决、分布式能力
-
轻量级存储(LightStorage)
- 适用场景:简单数据缓存、临时数据
- 数据特征:结构简单、数据量适中
- 优势:轻量级、占用资源少
-
文件存储
- 适用场景:二进制数据、大文件、媒体内容
- 数据特征:非结构化、体积大
- 优势:支持任意格式、底层控制
实战案例:音乐播放器数据存储设计
下面通过一个音乐播放器应用的数据存储设计,展示如何组合使用多种存储方案:
1. 用户设置与播放状态(Preferences)
// 保存播放器设置和状态
async function savePlayerSettings() {
const context = getContext(this);
const preferences = await data_preferences.getPreferences(context, 'playerSettings');
// 保存设置
await preferences.put('soundQuality', 'high');
await preferences.put('equalizerEnabled', true);
await preferences.put('downloadOnWifiOnly', true);
// 保存播放状态
await preferences.put('lastPlayedSongId', 'song123');
await preferences.put('playbackPosition', 67.5);
await preferences.put('volume', 0.8);
await preferences.flush();
}
2. 音乐库管理(RDB)
// 创建音乐库数据表
async function createMusicDatabase() {
const context = getContext(this);
const rdbStore = await data_rdb.getRdbStore(context, {
name: 'MusicLibrary.db',
securityLevel: data_rdb.SecurityLevel.S1
}, 1);
// 创建歌曲表
await rdbStore.executeSql(`
CREATE TABLE IF NOT EXISTS songs (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
artist TEXT,
album TEXT,
duration INTEGER,
path TEXT,
thumbnail BLOB,
is_favorite INTEGER DEFAULT 0,
last_played TIMESTAMP
)
`);
// 创建播放列表表
await rdbStore.executeSql(`
CREATE TABLE IF NOT EXISTS playlists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// 创建播放列表歌曲关联表
await rdbStore.executeSql(`
CREATE TABLE IF NOT EXISTS playlist_songs (
playlist_id INTEGER,
song_id TEXT,
sort_order INTEGER,
PRIMARY KEY (playlist_id, song_id),
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
)
`);
return rdbStore;
}
3. 用户数据同步(KVStore)
// 同步用户收藏和播放历史
async function syncUserMusicData() {
const kvStore = await getKVStore();
// 保存收藏歌曲列表(会同步到用户的其他设备)
await kvStore.put('favoriteSongs', [
'song123', 'song456', 'song789'
]);
// 保存播放历史
await kvStore.put('playHistory', [
{ songId: 'song123', timestamp: Date.now() - 3600000 },
{ songId: 'song456', timestamp: Date.now() - 7200000 },
{ songId: 'song789', timestamp: Date.now() - 86400000 }
]);
// 监听其他设备的变更
kvStore.on('dataChange', 1, (changeData) => {
for (const entry of changeData.insertEntries.concat(changeData.updateEntries)) {
if (entry.key === 'favoriteSongs') {
// 更新本地收藏列表
updateLocalFavorites(entry.value);
}
}
});
}
4. 音乐文件管理(文件存储)
// 保存下载的音乐文件
async function saveMusicFile(songId, buffer) {
const context = getContext(this);
const musicDir = `${context.filesDir}/music`;
// 确保目录存在
try {
await fs.access(musicDir);
} catch (error) {
await fs.mkdir(musicDir);
}
// 保存文件
const filePath = `${musicDir}/${songId}.mp3`;
const file = await fs.open(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE);
await fs.write(file.fd, buffer);
await fs.close(file.fd);
// 更新数据库记录
const rdbStore = await getMusicDatabase();
const values = {
path: filePath,
is_downloaded: 1
};
const predicates = new data_rdb.RdbPredicates('songs');
predicates.equalTo('id', songId);
await rdbStore.update(values, predicates);
return filePath;
}
5. 专辑艺术作品缓存(LightStorage)
// 缓存专辑封面
async function cacheAlbumArtwork(albumId, imageBuffer) {
const storage = await getLightStorage();
const key = `album_artwork_${albumId}`;
await storage.put(key, imageBuffer);
// 记录缓存时间
await storage.put(`${key}_timestamp`, Date.now());
await storage.flush();
}
// 获取缓存的专辑封面
async function getAlbumArtwork(albumId) {
const storage = await getLightStorage();
const key = `album_artwork_${albumId}`;
try {
return await storage.get(key);
} catch (error) {
console.info(`封面未缓存: ${albumId}`);
return null;
}
}
// 清理过期缓存
async function cleanupArtworkCache(maxAgeMs = 30 * 24 * 60 * 60 * 1000) {
const storage = await getLightStorage();
const now = Date.now();
const keys = await storage.keys();
for (const key of keys) {
if (key.includes('album_artwork_') && !key.includes('_timestamp')) {
const timestampKey = `${key}_timestamp`;
try {
const timestamp = await storage.get(timestampKey);
if (now - timestamp > maxAgeMs) {
// 缓存超过30天,删除
await storage.delete(key);
await storage.delete(timestampKey);
}
} catch (error) {
// 如果没有时间戳,也删除
await storage.delete(key);
}
}
}
await storage.flush();
}
数据存储性能优化
缓存策略
实现多级缓存提高访问效率:
// 实现内存缓存 + 持久化缓存
class DataCache {
private memoryCache = new Map();
private maxMemoryItems = 100;
// 从缓存获取数据
async get(key) {
// 先查内存缓存
if (this.memoryCache.has(key)) {
return this.memoryCache.get(key);
}
// 再查持久化缓存
try {
const storage = await getLightStorage();
const value = await storage.get(key);
// 更新内存缓存
this.updateMemoryCache(key, value);
return value;
} catch (error) {
return null;
}
}
// 存储到缓存
async put(key, value) {
// 更新内存缓存
this.updateMemoryCache(key, value);
// 更新持久化缓存
const storage = await getLightStorage();
await storage.put(key, value);
await storage.flush();
}
// 更新内存缓存
private updateMemoryCache(key, value) {
// LRU策略:如果缓存已满,删除最早的项
if (this.memoryCache.size >= this.maxMemoryItems) {
const firstKey = this.memoryCache.keys().next().value;
this.memoryCache.delete(firstKey);
}
this.memoryCache.set(key, value);
}
}
批量操作优化
// 批量插入数据
async function batchInsertSongs(songs) {
const rdbStore = await getMusicDatabase();
try {
// 开始事务
await rdbStore.beginTransaction();
for (const song of songs) {
await rdbStore.insert('songs', song);
}
// 提交事务
await rdbStore.commit();
console.info(`批量插入 ${songs.length} 首歌曲成功`);
return true;
} catch (error) {
// 回滚事务
await rdbStore.rollBack();
console.error(`批量插入失败: ${error.message}`);
return false;
}
}
懒加载与分页
// 分页加载数据
async function loadSongsByPage(page, pageSize) {
const rdbStore = await getMusicDatabase();
const predicates = new data_rdb.RdbPredicates('songs');
predicates.orderByDesc('last_played')
.limit(pageSize)
.offset(page * pageSize);
const resultSet = await rdbStore.query(predicates);
const songs = [];
while (resultSet.goToNextRow()) {
songs.push({
id: resultSet.getString(resultSet.getColumnIndex('id')),
title: resultSet.getString(resultSet.getColumnIndex('title')),
artist: resultSet.getString(resultSet.getColumnIndex('artist')),
album: resultSet.getString(resultSet.getColumnIndex('album'))
});
}
resultSet.close();
return songs;
}
安全建议
保护数据存储安全的关键措施:
- 数据加密:敏感数据使用加密存储
- 权限控制:遵循最小权限原则
- 安全清理:应用卸载时清理敏感数据
- 输入验证:防止SQL注入和其他注入攻击
- 备份策略:实现数据备份和恢复机制
总结
鸿蒙系统提供了多样化的数据存储方案,从轻量级的首选项到功能完备的关系型数据库,再到创新的分布式数据管理。开发者应根据应用的具体需求,选择最合适的存储方案,并遵循最佳实践,确保数据的安全性、完整性和性能。
通过合理组合使用这些存储技术,可以构建出高效、可靠的鸿蒙应用,提供流畅的用户体验。随着鸿蒙生态的不断发展,其数据存储能力也将持续增强,为开发者提供更多可能性。
更多推荐
所有评论(0)