引言

数据存储是任何应用程序的核心需求之一,关系到应用的性能、可靠性和用户体验。鸿蒙系统提供了丰富的数据存储解决方案,从轻量级的首选项存储到功能完备的关系型数据库,再到创新的分布式数据管理,满足不同应用场景的需求。本文将深入探讨鸿蒙系统数据存储的各种技术和最佳实践,帮助开发者选择合适的存储方案并高效实现数据管理功能。

鸿蒙数据存储方案概览

鸿蒙系统提供了多种数据存储方案,主要包括:

  1. 首选项(Preferences):轻量级键值对存储
  2. 轻量级存储(LightStorage):基于文件的简单数据存储
  3. 关系型数据库(RDB):结构化数据的存储和查询
  4. 对象关系映射(ORM):面向对象的数据访问方式
  5. 分布式数据库(KVStore):支持多设备数据同步
  6. 文件存储(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}`);
  });
}

最佳实践

  1. 使用场景优化:只存储小型配置数据,避免存储大量或复杂数据
  2. 合理分组:为不同功能模块创建不同的Preferences实例
  3. 批处理操作:多次修改后统一调用flush()提交,提高性能
  4. 默认值策略:读取数据时始终提供合理的默认值,增强应用鲁棒性
  5. 异步处理: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;
  }
}

最佳实践

  1. 表设计规范:合理设计表结构,添加适当索引,遵循数据库范式
  2. 参数化查询:使用参数化SQL防止SQL注入
  3. 使用事务:批量操作时使用事务提高性能和保证数据一致性
  4. 及时关闭资源:使用完ResultSet后及时调用close()方法
  5. 分页加载:使用limit和offset实现数据分页,避免一次加载过多数据
  6. 错误处理:完善的异常捕获和错误处理机制

分布式数据管理(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
  });
}

最佳实践

  1. 数据分区:根据功能将数据分到不同的KVStore
  2. 安全策略:重要数据启用加密功能
  3. 同步策略:根据应用场景设置合适的同步模式和频率
  4. 冲突处理:设计并实现合理的冲突解决策略
  5. 资源管理:不再使用时关闭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;
}

存储方案选择建议

根据不同场景选择合适的存储方案:

  1. 首选项(Preferences)

    • 适用场景:应用设置、用户偏好、简单配置
    • 数据特征:键值对、数据量小、读写频率中等
    • 优势:使用简单、读写快速
  2. 关系型数据库(RDB)

    • 适用场景:结构化数据、复杂查询、大量数据
    • 数据特征:表格化数据、有明确关系、需要事务支持
    • 优势:强大的查询能力、事务支持、数据完整性保障
  3. 分布式数据库(KVStore)

    • 适用场景:多设备应用、需要跨设备同步的数据
    • 数据特征:需要实时同步、处理冲突、支持离线操作
    • 优势:自动同步、冲突解决、分布式能力
  4. 轻量级存储(LightStorage)

    • 适用场景:简单数据缓存、临时数据
    • 数据特征:结构简单、数据量适中
    • 优势:轻量级、占用资源少
  5. 文件存储

    • 适用场景:二进制数据、大文件、媒体内容
    • 数据特征:非结构化、体积大
    • 优势:支持任意格式、底层控制

实战案例:音乐播放器数据存储设计

下面通过一个音乐播放器应用的数据存储设计,展示如何组合使用多种存储方案:

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;
}

安全建议

保护数据存储安全的关键措施:

  1. 数据加密:敏感数据使用加密存储
  2. 权限控制:遵循最小权限原则
  3. 安全清理:应用卸载时清理敏感数据
  4. 输入验证:防止SQL注入和其他注入攻击
  5. 备份策略:实现数据备份和恢复机制

总结

鸿蒙系统提供了多样化的数据存储方案,从轻量级的首选项到功能完备的关系型数据库,再到创新的分布式数据管理。开发者应根据应用的具体需求,选择最合适的存储方案,并遵循最佳实践,确保数据的安全性、完整性和性能。

通过合理组合使用这些存储技术,可以构建出高效、可靠的鸿蒙应用,提供流畅的用户体验。随着鸿蒙生态的不断发展,其数据存储能力也将持续增强,为开发者提供更多可能性。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐