HarmonyOS 鸿蒙Next中实现文件读写与本地存储管理

HarmonyOS 鸿蒙Next中实现文件读写与本地存储管理 在鸿蒙os开发中如何实现文件读写怀本地存储管理呢?

3 回复

应用场景

在应用中,经常需要对用户生成的内容进行临时保存,如 编辑器草稿自动保存、表单填写中途退出后恢复。有时也需要针对用户的媒体资源进行缓存,如下载图片/音频到本地,避免重复请求,再如离线查看已加载的文档,所有需要进行缓存的文件就需要进行文件的操作及存储。

实现思路

鸿蒙os已经为我们提供了大量的操作文件句柄的类了,但还是需要我们进行一些封装使用,比如封装一些获取文件路径、上传、下载等各种方法。

核心的用法包括:@ohos.file.fs 、 @ohos.file.fileuri 这2个文件操作的类。

完整代码

文件操作类

import fileUri from '[@ohos](/user/ohos).file.fileuri';
import fs, { ListFileOptions, ReadOptions, ReadTextOptions, WriteOptions } from '[@ohos](/user/ohos).file.fs';
import { StrUtil } from './StrUtil';
import { fileShare } from '[@kit](/user/kit).CoreFileKit';
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit';

export class FileUtil {
  static readonly separator: string = '/'; // 文件路径分隔符

  /**
   * 获取文件目录下的文件夹路径或文件路径。
   *
   * [@param](/user/param) dirPath 文件路径;支持完整路径和相对路径(如 `download/wps/doc`);传空字符串表示根目录。
   * [@param](/user/param) fileName 文件名(如 `test.text`);传空字符串表示文件夹路径。
   * [@param](/user/param) blHap 是否为 HAP 级别文件路径:
   *    - `true`:HAP 级别文件路径。
   *    - `false`:App 级别文件路径。
   * [@returns](/user/returns) 返回完整的文件路径。
   */
  static getFilesDirPath(dirPath: string = "", fileName: string = "", blHap: boolean = true): string {
    let filePath = blHap ? getContext().filesDir : getContext().getApplicationContext().filesDir; // 根目录
    return FileUtil.buildFilePath(filePath, dirPath, fileName);
  }

  /**
   * 获取缓存目录下的文件夹路径或文件路径。
   *
   * [@param](/user/param) dirPath 文件路径;支持完整路径和相对路径(如 `download/wps/doc`);传空字符串表示根目录。
   * [@param](/user/param) fileName 文件名(如 `test.text`);传空字符串表示文件夹路径。
   * [@param](/user/param) blHap 是否为 HAP 级别文件路径:
   *    - `true`:HAP 级别文件路径。
   *    - `false`:App 级别文件路径。
   * [@returns](/user/returns) 返回完整的文件路径。
   */
  static getCacheDirPath(dirPath: string = "", fileName: string = "", blHap: boolean = true): string {
    let filePath = blHap ? getContext().cacheDir : getContext().getApplicationContext().cacheDir; // 根目录
    return FileUtil.buildFilePath(filePath, dirPath, fileName);
  }

  /**
   * 获取临时目录下的文件夹路径或文件路径。
   *
   * [@param](/user/param) dirPath 文件路径;支持完整路径和相对路径(如 `download/wps/doc`);传空字符串表示根目录。
   * [@param](/user/param) fileName 文件名(如 `test.text`);传空字符串表示文件夹路径。
   * [@param](/user/param) blHap 是否为 HAP 级别文件路径:
   *    - `true`:HAP 级别文件路径。
   *    - `false`:App 级别文件路径。
   * [@returns](/user/returns) 返回完整的文件路径。
   */
  static getTempDirPath(dirPath: string = "", fileName: string = "", blHap: boolean = true): string {
    let filePath = blHap ? getContext().tempDir : getContext().getApplicationContext().tempDir; // 根目录
    return FileUtil.buildFilePath(filePath, dirPath, fileName);
  }

  /**
   * 构建文件路径的通用逻辑。
   *
   * [@param](/user/param) rootPath 根目录路径。
   * [@param](/user/param) dirPath 子目录路径。
   * [@param](/user/param) fileName 文件名。
   * [@returns](/user/returns) 返回完整的文件路径。
   */
  private static buildFilePath(rootPath: string, dirPath: string, fileName: string): string {
    let filePath = rootPath;
    if (StrUtil.isNotEmpty(dirPath)) {
      if (FileUtil.hasDirPath(dirPath)) { // 路径中包含根目录,是完整路径。
        filePath = dirPath;
      } else { // 路径中不包含根目录,拼接成完整路径。
        filePath = `${filePath}${FileUtil.separator}${dirPath}`;
      }
      if (!FileUtil.accessSync(filePath)) {
        FileUtil.mkdirSync(filePath); // 如果文件夹不存在就创建。
      }
    }
    if (StrUtil.isNotEmpty(fileName)) {
      filePath = `${filePath}${FileUtil.separator}${fileName}`;
    }
    return filePath;
  }

  /**
   * 判断是否是完整路径。
   *
   * [@param](/user/param) path 文件路径。
   * [@returns](/user/returns) 返回布尔值,表示是否是完整路径。
   */
  static hasDirPath(path: string): boolean {
    return path.startsWith("/data/storage/") || path.startsWith("/storage/");
  }

  /**
   * 通过 URI 或路径,获取 FileUri 对象。
   *
   * [@param](/user/param) uriOrPath URI 或路径。
   * [@returns](/user/returns) 返回 FileUri 对象。
   */
  static getFileUri(uriOrPath: string): fileUri.FileUri {
    return new fileUri.FileUri(uriOrPath);
  }

  /**
   * 通过 URI 或路径,获取文件名。
   *
   * [@param](/user/param) uriOrPath URI 或路径。
   * [@returns](/user/returns) 返回文件名。
   */
  static getFileName(uriOrPath: string): string {
    return FileUtil.getFileUri(uriOrPath).name;
  }

  /**
   * 通过 URI 或路径,获取文件路径。
   *
   * [@param](/user/param) uriOrPath URI 或路径。
   * [@returns](/user/returns) 返回文件路径。
   */
  static getFilePath(uriOrPath: string): string {
    return FileUtil.getFileUri(uriOrPath).path;
  }

  /**
   * 通过 URI 或路径,获取对应文件父目录的 URI。
   *
   * [@param](/user/param) uriOrPath URI 或路径。
   * [@returns](/user/returns) 返回父目录的 URI。
   */
  static getParentUri(uriOrPath: string): string {
    return FileUtil.getFileUri(uriOrPath).getFullDirectoryUri();
  }

  /**
   * 通过 URI 或路径,获取对应文件父目录的路径名。
   *
   * [@param](/user/param) uriOrPath URI 或路径。
   * [@returns](/user/returns) 返回父目录的路径名。
   */
  static getParentPath(uriOrPath: string): string {
    const parentUri = FileUtil.getParentUri(uriOrPath);
    return FileUtil.getFilePath(parentUri);
  }

  /**
   * 以同步方法获取文件 URI。
   *
   * [@param](/user/param) path 应用沙箱路径。
   * [@returns](/user/returns) 返回文件 URI。
   */
  static getUriFromPath(path: string): string {
    return fileUri.getUriFromPath(path);
  }

  /**
   * 根据文件名获取文件后缀。
   *
   * [@param](/user/param) fileName 文件名(如 `test.txt` 或 `test.doc`)。
   * [@returns](/user/returns) 返回文件后缀(如 `txt` 或 `doc`)。
   */
  static getFileExtension(fileName: string): string {
    if (StrUtil.isNotEmpty(fileName) && fileName.includes(".")) {
      return fileName.substring(fileName.lastIndexOf(".") + 1);
    }
    return '';
  }

  /**
   * 获取指定文件夹下所有文件的大小或指定文件的大小。
   *
   * [@param](/user/param) path 文件夹路径或文件路径。
   * [@returns](/user/returns) 返回文件或文件夹的总大小(单位:字节)。
   */
  static getFileDirSize(path: string): number {
    if (!FileUtil.accessSync(path)) {
      return 0; // 路径不存在时返回 0。
    }
    if (FileUtil.isDirectory(path)) { // 文件夹
      let totalSize = 0;
      FileUtil.listFileSync(path, { recursion: true }).forEach((filePath) => {
        try {
          totalSize += FileUtil.statSync(filePath).size;
        } catch (error) {
          console.warn(`Failed to get size for file ${filePath}. Error: ${error.message}`);
        }
      });
      return totalSize;
    } else { // 文件
      return FileUtil.statSync(path).size;
    }
  }

  /**
   * 判断文件是否是普通文件。
   *
   * [@param](/user/param) file 文件的应用沙箱路径或已打开的文件描述符。
   * [@returns](/user/returns) 返回布尔值,表示是否是普通文件。
   */
  static isFile(file: string | number): boolean {
    try {
      return fs.statSync(file).isFile();
    } catch (error) {
      throw new Error(`Failed to check if file is a regular file. Path/FD: ${file}. Error: ${error.message}`);
    }
  }

  /**
   * 判断文件是否是目录。
   *
   * [@param](/user/param) file 文件的应用沙箱路径或已打开的文件描述符。
   * [@returns](/user/returns) 返回布尔值,表示是否是目录。
   */
  static isDirectory(file: string | number): boolean {
    try {
      return fs.statSync(file).isDirectory();
    } catch (error) {
      throw new Error(`Failed to check if file is a directory. Path/FD: ${file}. Error: ${error.message}`);
    }
  }

  /**
   * 重命名文件或文件夹,使用 Promise 异步回调。
   *
   * [@param](/user/param) oldPath 文件的应用沙箱原路径。
   * [@param](/user/param) newPath 文件的应用沙箱新路径。
   * [@returns](/user/returns) 返回一个无返回值的 Promise。
   */
  static rename(oldPath: string, newPath: string): Promise<void> {
    return fs.rename(oldPath, newPath).catch((error:BusinessError) => {
      throw new Error(`Failed to rename file/directory from ${oldPath} to ${newPath}. Error: ${error.message}`);
    });
  }

  /**
   * 重命名文件或文件夹,以同步方法。
   *
   * [@param](/user/param) oldPath 文件的应用沙箱原路径。
   * [@param](/user/param) newPath 文件的应用沙箱新路径。
   */
  static renameSync(oldPath: string, newPath: string): void {
    try {
      fs.renameSync(oldPath, newPath);
    } catch (error) {
      throw new Error(`Failed to rename file/directory from ${oldPath} to ${newPath}. Error: ${error.message}`);
    }
  }


  /**
   * 创建目录,支持多层级创建。
   *
   * [@param](/user/param) path 目录的应用沙箱路径。
   * [@param](/user/param) recursion 是否多层级创建目录:
   *    - `true`:递归创建多级目录。
   *    - `false`:仅创建单层目录。
   * [@returns](/user/returns) 返回一个无返回值的 Promise。
   */
  static mkdir(path: string, recursion: boolean = true): Promise<void> {
    return fs.mkdir(path, recursion).catch((error:BusinessError) => {
      throw new Error(`Failed to create directory at ${path}. Recursion: ${recursion}. Error: ${error.message}`);
    });
  }

  /**
   * 创建目录,以同步方法,支持多层级创建。
   *
   * [@param](/user/param) path 目录的应用沙箱路径。
   * [@param](/user/param) recursion 是否多层级创建目录:
   *    - `true`:递归创建多级目录。
   *    - `false`:仅创建单层目录。
   */
  static mkdirSync(path: string, recursion: boolean = true): void {
    try {
      fs.mkdirSync(path, recursion);
    } catch (error) {
      throw new Error(`Failed to create directory at ${path}. Recursion: ${recursion}. Error: ${error.message}`);
    }
  }

  /**
   * 删除整个目录。
   *
   * [@param](/user/param) path 目录的应用沙箱路径。
   * [@returns](/user/returns) 返回一个无返回值的 Promise。
   */
  static rmdir(path: string): Promise<void> {
    return fs.rmdir(path).catch((error:BusinessError) => {
      throw new Error(`Failed to remove directory at ${path}. Error: ${error.message}`);
    });
  }

  /**
   * 删除整个目录,以同步方法。
   *
   * [@param](/user/param) path 目录的应用沙箱路径。
   */
  static rmdirSync(path: string): void {
    try {
      fs.rmdirSync(path);
    } catch (error) {
      throw new Error(`Failed to remove directory at ${path}. Error: ${error.message}`);
    }
  }

  /**
   * 删除单个文件。
   *
   * [@param](/user/param) path 文件的应用沙箱路径。
   * [@returns](/user/returns) 返回一个无返回值的 Promise。
   */
  static unlink(path: string): Promise<void> {
    return fs.unlink(path).catch((error:BusinessError) => {
      throw new Error(`Failed to delete file at ${path}. Error: ${error.message}`);
    });
  }

  /**
   * 删除单个文件,以同步方法。
   *
   * [@param](/user/param) path 文件的应用沙箱路径。
   */
  static unlinkSync(path: string): void {
    try {
      fs.unlinkSync(path);
    } catch (error) {
      throw new Error(`Failed to delete file at ${path}. Error: ${error.message}`);
    }
  }

  /**
   * 检查文件是否存在。
   *
   * [@param](/user/param) path 文件应用沙箱路径。
   * [@returns](/user/returns) 返回一个布尔值的 Promise。
   */
  static access(path: string): Promise<boolean> {
    return fs.access(path).catch((error:BusinessError) => {
      throw new Error(`Failed to check file existence at ${path}. Error: ${error.message}`);
    });
  }

  /**
   * 检查文件是否存在,以同步方法。
   *
   * [@param](/user/param) path 文件应用沙箱路径。
   * [@returns](/user/returns) 返回布尔值,表示文件是否存在。
   */
  static accessSync(path: string): boolean {
    try {
      return fs.accessSync(path);
    } catch (error) {
      throw new Error(`Failed to check file existence at ${path}. Error: ${error.message}`);
    }
  }

  /**
   * 打开文件,支持使用 URI 打开。
   *
   * [@param](/user/param) path 文件的应用沙箱路径或 URI。
   * [@param](/user/param) mode 打开文件的选项,默认为只读方式打开。
   * [@returns](/user/returns) 返回文件对象的 Promise。
   */
  static open(path: string, mode: number = fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE): Promise<fs.File> {
    return fs.open(path, mode).catch((error:BusinessError) => {
      throw new Error(`Failed to open file at ${path}. Mode: ${mode}. Error: ${error.message}`);
    });
  }

  /**
   * 打开文件,支持使用 URI 打开,以同步方法。
   *
   * [@param](/user/param) path 文件的应用沙箱路径或 URI。
   * [@param](/user/param) mode 打开文件的选项,默认为只读方式打开。
   * [@returns](/user/returns) 返回文件对象。
   */
  static openSync(path: string, mode: number = fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE): fs.File {
    try {
      return fs.openSync(path, mode);
    } catch (error) {
      throw new Error(`Failed to open file at ${path}. Mode: ${mode}. Error: ${error.message}`);
    }
  }

  /**
   * 从文件读取数据。
   *
   * [@param](/user/param) fd 已打开的文件描述符。
   * [@param](/user/param) buffer 用于保存读取到的文件数据的缓冲区。
   * [@param](/user/param) options 可选参数:
   *    - offset:读取文件的位置。
   *    - length:读取数据的长度。
   * [@returns](/user/returns) 返回实际读取的数据长度的 Promise。
   */
  static read(fd: number, buffer: ArrayBuffer, options?: ReadOptions): Promise<number> {
    return fs.read(fd, buffer, options).catch((error:BusinessError) => {
      throw new Error(`Failed to read file data. FD: ${fd}. Error: ${error.message}`);
    });
  }

  /**
   * 从文件读取数据,以同步方法。
   *
   * [@param](/user/param) fd 已打开的文件描述符。
   * [@param](/user/param) buffer 用于保存读取到的文件数据的缓冲区。
   * [@param](/user/param) options 可选参数:
   *    - offset:读取文件的位置。
   *    - length:读取数据的长度。
   * [@returns](/user/returns) 返回实际读取的数据长度。
   */
  static readSync(fd: number, buffer: ArrayBuffer, options?: ReadOptions): number {
    try {
      return fs.readSync(fd, buffer, options);
    } catch (error) {
      throw new Error(`Failed to read file data. FD: ${fd}. Error: ${error.message}`);
    }
  }

  /**
   * 基于文本方式读取文件内容。
   *
   * [@param](/user/param) filePath 文件的应用沙箱路径。
   * [@param](/user/param) options 可选参数:
   *    - offset:读取文件的位置。
   *    - length:读取数据的长度。
   *    - encoding:编码方式,默认为 'utf-8'。
   * [@returns](/user/returns) 返回文件内容的 Promise。
   */
  static readText(filePath: string, options?: ReadTextOptions): Promise<string> {
    return fs.readText(filePath, options).catch((error:BusinessError) => {
      throw new Error(`Failed to read text file at ${filePath}. Error: ${error.message}`);
    });
  }

  /**
   * 基于文本方式读取文件内容,以同步方法。
   *
   * [@param](/user/param) filePath 文件的应用沙箱路径。
   * [@param](/user/param) options 可选参数:
   *    - offset:读取文件的位置。
   *    - length:读取数据的长度。
   *    - encoding:编码方式,默认为 'utf-8'。
   * [@returns](/user/returns) 返回文件内容。
   */
  static readTextSync(filePath: string, options?: ReadTextOptions): string {
    try {
      return fs.readTextSync(filePath, options);
    } catch (error) {
      throw new Error(`Failed to read text file at ${filePath}. Error: ${error.message}`);
    }
  }

  /**
   * 将数据写入文件。
   *
   * [@param](/user/param) fd 已打开的文件描述符。
   * [@param](/user/param) buffer 待写入文件的数据(可以是缓冲区或字符串)。
   * [@param](/user/param) options 可选参数

更多关于HarmonyOS 鸿蒙Next中实现文件读写与本地存储管理的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS Next中文件读写通过FileManager和FileIO模块实现。使用FileManager进行文件系统操作,如创建、删除、移动文件或目录。FileIO模块提供文件流读写功能,支持同步和异步操作。本地存储管理通过Preferences或分布式数据对象实现轻量级数据持久化,适用于键值对存储。安全沙箱机制确保应用数据隔离,访问外部存储需申请相应权限。开发者需在module.json5中声明所需存储权限。

在HarmonyOS Next中,文件读写与本地存储管理主要通过[@ohos](/user/ohos).file.fs(文件系统)和[@ohos](/user/ohos).data.preferences(轻量级偏好数据库)等模块实现。以下是核心方法与路径说明:

1. 文件读写(@ohos.file.fs)

  • 沙箱路径:应用仅能直接访问沙箱内的私有文件,路径通过Context获取:
    const context = getContext(this);
    const filesDir = context.filesDir; // 私有文件目录
    
  • 常用操作
    • 读写文件:使用fs.openSync()fs.writeSync()fs.readSync()等同步/异步接口。
    • 外部媒体文件:需申请ohos.permission.READ_IMAGEVIDEO等权限,并通过PhotoAccessHelper等媒体库接口访问。

2. 轻量存储(@ohos.data.preferences)

  • 适用于键值对数据(如配置信息):
    import { preferences } from '[@ohos](/user/ohos).data.preferences';
    const pref = await preferences.getPreferences(context, 'mydata');
    await pref.put('key', 'value'); // 存储
    const value = await pref.get('key', 'default'); // 读取
    

3. 数据库存储

  • 关系型数据可使用[@ohos](/user/ohos).data.relationalStore(SQLite引擎)。
  • 对象型数据推荐[@ohos](/user/ohos).data.distributedDataObject(跨设备同步)。

注意事项

  • 敏感数据建议使用[@ohos](/user/ohos).security.cryptoFramework加密。
  • 大文件或结构化数据应避免直接使用preferences

具体接口调用需参考官方文档的ArkTS/JS API声明,并注意异步操作需使用async/awaitPromise处理。

回到顶部