访问应用文件

应用需要对应用文件目录下的应用文件进行查看、创建、读写、删除、移动、复制、获取属性等访问操作,下文介绍具体方法。

接口说明

开发者通过基础文件操作接口(ohos.file.fs)实现应用文件访问能力,主要功能如下表所示。

表1 基础文件操作接口功能,其中“√”表示支持,“-”表示不区分同步和异步。

接口名 功能 接口类型 支持同步 支持异步
access 检查文件是否存在 方法
close 关闭文件 方法
copyFile 复制文件 方法
createStream 基于文件路径打开文件流 方法
listFile 列出文件夹下所有文件名 方法
mkdir 创建目录 方法
moveFile 移动文件 方法
open 打开文件 方法
read 从文件读取数据 方法
rename 重命名文件或文件夹 方法
rmdir 删除整个目录 方法
stat 获取文件详细属性信息 方法
unlink 删除单个文件 方法
write 将数据写入文件 方法
Stream.close 关闭文件流 方法
Stream.flush 刷新文件流 方法
Stream.write 将数据写入流文件 方法
Stream.read 从流文件读取数据 方法
File.fd 获取文件描述符 属性 - -
OpenMode 设置文件打开标签 属性 - -
Filter 设置文件过滤配置项 类型 - -

注意

使用基础文件操作接口时,耗时较长的操作,例如:read、write等,建议使用异步接口,避免应用崩溃。

开发示例

在对应用文件开始访问前,开发者需要获取应用文件路径。以从UIAbilityContext获取HAP级别的文件路径为例进行说明,UIAbilityContext的获取方式请参见获取UIAbility的上下文信息

下面介绍几种常用操作示例。

新建并读写一个文件

以下示例代码演示了如何新建一个文件并对其读写。

// pages/xxx.ets
import { fileIo, ReadOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { buffer } from '@kit.ArkTS';

// 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
function createFile(context: common.UIAbilityContext): void {
  let filesDir = context.filesDir;
  let file: fileIo.File | null = null;
  try {
    // 文件不存在时创建并打开文件,文件存在时打开文件
    file = fileIo.openSync(filesDir + '/test.txt', fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    // 写入一段内容至文件
    let writeLen = fileIo.writeSync(file.fd, 'Hello world');
    console.info('The length of str is: ' + writeLen);
    // 创建一个大小为1024字节的ArrayBuffer对象,用于存储从文件中读取的数据
    let arrayBuffer = new ArrayBuffer(1024);
    // 设置读取的偏移量和长度,单位为Byte
    let readOptions: ReadOptions = {
      offset: 0,
      length: arrayBuffer.byteLength
    };
    // 读取文件内容到ArrayBuffer对象中,并返回实际读取的字节数
    let readLen = fileIo.readSync(file.fd, arrayBuffer, readOptions);
    // 将ArrayBuffer对象转换为Buffer对象,并转换为字符串输出
    let buf = buffer.from(arrayBuffer, 0, readLen);
    console.info('Succeeded in creating file, the content of file: ' + buf.toString());
  } catch (err) {
    console.error(`Failed to create file. Code: ${err.code}, message: ${err.message}`);
  } finally {
    if (file) {
      try {
        fileIo.closeSync(file);
      } catch (err) {
        console.error(`Failed to close file`);
      }
    }
  }
}

代码逻辑走读:

  1. 获取文件目录
    • 通过context.filesDir获取应用的文件目录。
  2. 文件操作
    • 尝试打开或创建文件:使用fileIo.openSync方法,如果文件不存在则创建并打开,如果文件存在则直接打开。
    • 写入内容:使用fileIo.writeSync方法将字符串'Hello world'写入文件。
    • 读取内容:创建一个ArrayBuffer对象,用于存储从文件中读取的数据。设置读取的偏移量和长度,然后使用fileIo.readSync方法将文件内容读取到ArrayBuffer中。
    • ArrayBuffer转换为Buffer对象,并转换为字符串输出。
  3. 错误处理
    • 使用try-catch结构捕获可能的异常,并通过console.error输出错误信息。
  4. 文件关闭
    • finally块中,如果文件对象存在,则尝试关闭文件。如果关闭失败,输出错误信息。

读取文件内容并写入到另一个文件

以下示例代码演示了如何从一个文件读写内容到另一个文件。

// pages/xxx.ets
import { fileIo, ReadOptions, WriteOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
function readWriteFile(context: common.UIAbilityContext): void {
  let srcFile: fileIo.File | null = null;
  let destFile: fileIo.File | null = null;
  try {
    let filesDir = context.filesDir;
    // 以读写的方式打开文件,文件不存在会新建文件
    srcFile = fileIo.openSync(filesDir + '/readFile.txt', fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    destFile = fileIo.openSync(filesDir + '/writeFile.txt', fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    // 创建缓冲区
    let bufSize = 4096;
    let buf = new ArrayBuffer(bufSize);
    let readOffset = 0;
    let readLength = 128;
    // 设置读取的偏移量和长度,单位为Byte
    let readOptions: ReadOptions = {
      offset: readOffset,
      length: readLength
    };
    // 分次读取源文件内容并写入至目标文件
    let readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
    while (readLen > 0) {
      readOffset += readLen;
      let writeOptions: WriteOptions = {
        length: readLen
      };
      // 写入目标文件
      fileIo.writeSync(destFile.fd, buf, writeOptions);
      // 更新读取位置
      readOptions.offset = readOffset;
      readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
    }
    console.info(`Succeeded in reading and writing file.`);
  } catch (err) {
    console.error(`Failed to read and write File. Code: ${err.code}, message: ${err.message}`);
  } finally {
    try {
      if (srcFile) {
        fileIo.closeSync(srcFile);
      }
      if (destFile) {
        fileIo.closeSync(destFile);
      }
    } catch (closeErr) {
      console.error(`Failed to close file`);
    }
  }
} 	

代码逻辑走读:

  1. 初始化文件变量:定义并初始化源文件和目标文件变量为null。
  2. 获取文件目录:从UIAbilityContext中获取文件目录。
  3. 打开源文件和目标文件:使用fileIo.openSync方法以读写模式打开或创建源文件和目标文件。
  4. 创建缓冲区:定义一个缓冲区变量buf,并设置其大小为4096字节。
  5. 设置读取参数:定义读取偏移量和长度,初始化为0和128字节。
  6. 读取源文件内容:使用fileIo.readSync方法从源文件中读取数据到缓冲区,直到读取长度为0。
  7. 写入目标文件:使用fileIo.writeSync方法将缓冲区中的数据写入目标文件。
  8. 处理异常:使用try-catch结构捕获可能的异常,并在控制台输出错误信息。
  9. 关闭文件:在finally块中关闭源文件和目标文件,确保资源释放。

以流的形式读写文件

以下示例代码演示了如何使用流接口读取test.txt的文件内容并写入到destFile.txt文件中。

// pages/xxx.ets
import { fileIo, ReadOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
async function readWriteFileWithStream(context: common.UIAbilityContext): Promise<void> {
  let filesDir = context.filesDir;
  let inputStream: fileIo.Stream | null = null;
  let outputStream: fileIo.Stream | null = null;
  try {
    // 创建并打开输入文件流
    inputStream = fileIo.createStreamSync(filesDir + '/test.txt', 'r+');
    // 创建并打开输出文件流
    outputStream = fileIo.createStreamSync(filesDir + '/destFile.txt', 'w+');
    let bufSize = 4096;
    let readSize = 0;
    let buf = new ArrayBuffer(bufSize);
    // 设置读取的偏移量和长度,单位为Byte
    let readOptions: ReadOptions = {
      offset: readSize,
      length: bufSize
    };
    // 以流的形式读取源文件内容并写入到目标文件
    let readLen = await inputStream.read(buf, readOptions);
    readSize += readLen;
    while (readLen > 0) {
      const writeBuf = readLen < bufSize ? buf.slice(0, readLen) : buf;
      await outputStream.write(writeBuf);
      readOptions.offset = readSize;
      readLen = await inputStream.read(buf, readOptions);
      readSize += readLen;
    }
    console.info(`Succeeded in reading and writing file with stream.`);
  } catch (err) {
    console.error(`Failed to read and write file with stream. Code: ${err.code}, message: ${err.message}`);
  } finally {
    try {
      if (inputStream) {
        inputStream.closeSync();
      }
      if (outputStream) {
        outputStream.closeSync();
      }
    } catch (closeErr) {
      console.error(`Failed to close stream`);
    }
  }
}

代码逻辑走读:

  1. 初始化文件流
    • 定义变量 inputStreamoutputStream,用于存储输入和输出文件流。
    • 使用 fileIo.createStreamSync方法创建并打开输入文件流,路径为 filesDir下的 test.txt
    • 使用 fileIo.createStreamSync方法创建并打开输出文件流,路径为 filesDir下的 destFile.txt
  2. 读取和写入文件
    • 定义缓冲区 buf和其大小 bufSize,用于存储读取的数据。
    • 设置读取的偏移量和长度 readOptions,初始化为读取整个文件。
    • 使用 inputStream.read方法从输入流中读取数据到缓冲区 buf中。
    • 若读取到的数据长度 readLen大于 0,则将数据写入到输出流中。
    • 若读取的数据长度小于缓冲区大小,则使用 buf.slice方法截取相应长度的数据进行写入。
    • 更新读取偏移量 readOptions.offset,并继续读取下一段数据,直到读取长度为 0。
  3. 错误处理
    • try块中捕获可能的异常,并在控制台输出错误信息。
    • catch块中处理流关闭时的异常,并在控制台输出错误信息。
  4. 关闭文件流
    • finally块中尝试关闭输入和输出文件流。
    • 如果关闭流时发生错误,则在控制台输出错误信息。

查看文件列表

以下示例代码演示了如何查看文件列表。

import { fileIo, Filter, ListFileOptions } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
function getListFile(context: common.UIAbilityContext): void {
  let listFileOption: ListFileOptions = {
    recursion: false,
    listNum: 0,
    filter: {
      suffix: ['.png', '.jpg', '.txt'],
      displayName: ['test*'],
      fileSizeOver: 0,
      lastModifiedAfter: new Date(0).getTime()
    }
  };
  let filesDir = context.filesDir;
  try {
    let files = fileIo.listFileSync(filesDir, listFileOption);
    for (let i = 0; i < files.length; i++) {
      console.info(`Succeeded in listing file, The name of file: ${files[i]}`);
    }
  } catch (err) {
    console.error(`Failed to list file. Code: ${err.code}, message: ${err.message}`);
  }
}

代码逻辑走读:

  1. 定义函数getListFile函数接受一个common.UIAbilityContext类型的参数context,用于在文件系统中操作文件。
  2. 设置文件过滤选项:创建一个ListFileOptions对象listFileOption,设置文件递归列表选项为false,列表数量为0,并定义文件过滤条件,包括后缀名、显示名称、文件大小和最后修改时间。
  3. 获取文件目录:从context中获取文件目录filesDir
  4. 列出文件:使用fileIo.listFileSync方法同步列出符合过滤条件的文件,并将结果存储在files数组中。
  5. 处理结果:遍历files数组,将每个文件的名称输出到控制台。
  6. 错误处理:如果在列出文件过程中发生错误,捕获异常并输出错误代码和信息。

使用文件流

以下示例代码演示了如何使用文件可读流,文件可写流。

// pages/xxx.ets
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
function copyFileWithReadable(context: common.UIAbilityContext): void {
  try {
    let filesDir = context.filesDir;
    // 创建文件可读流
    const rs = fileIo.createReadStream(`${filesDir}/test.txt`);
    // 创建文件可写流
    const ws = fileIo.createWriteStream(`${filesDir}/destFile.txt`);
    // 暂停模式拷贝文件。在拷贝数据时,将原始数据暂停,然后将数据复制到另一个位置,适用于对数据完整性和一致性要求较高的场景
    rs.on('readable', () => {
      const data = rs.read();
      if (!data) {
        return;
      }
      ws.write(data);
    });

    rs.on('end', () => {
      ws.end();
      console.info(`Succeeded in copying file with read stream.`);
    });

    // 捕获异常
    rs.on('error', () => {
      rs.close();
      ws.close();
    });
  } catch (err) {
    console.error(`Failed to copy file with read stream. Code: ${err.code}, message: ${err.message}`);
  }
}

代码逻辑走读:

  1. 定义了一个名为 copyFileWithReadable的函数,该函数接受一个 context参数,类型为 common.UIAbilityContext
  2. try块中,首先获取 filesDir,即应用的文件目录。
  3. 使用 fileIo.createReadStream创建一个读取流 rs,用于读取 ${filesDir}/test.txt文件的内容。
  4. 使用 fileIo.createWriteStream创建一个写入流 ws,用于将读取到的内容写入 ${filesDir}/destFile.txt文件。
  5. 为读取流 rs添加 readable事件监听器,当流变为可读时,读取数据并通过写入流 ws写入。
  6. 为读取流 rs添加 end事件监听器,当读取流结束时,关闭写入流 ws并输出成功信息。
  7. 为读取流 rs添加 error事件监听器,当发生错误时,关闭读取流和写入流,并输出错误信息。
  8. 使用 catch块捕获可能的异常,并输出错误信息,包括错误码和错误消息。
function copyFileWithData(context: common.UIAbilityContext): void {
  let filesDir = context.filesDir;

  try {
    // 创建文件可读流
    let rs = fileIo.createReadStream(`${filesDir}/test.txt`);
    // 创建文件可写流
    let ws = fileIo.createWriteStream(`${filesDir}/destFile.txt`);

    rs.push('Hello world');
    // 流动模式拷贝文件
    rs.on('data', (emitData) => {
      const data = emitData?.data;
      if (!data) {
        return;
      }
      ws.write(data as Uint8Array);
    });

    rs.on('end', () => {
      ws.end();
      console.info(`Succeeded in copying file with data.`);
    });

    // 捕获异常
    rs.on('error', () => {
      rs.close();
      ws.close();
    });
  } catch (err) {
    console.error(`Failed to copy file with data. Code: ${err.code}, message: ${err.message}`);
  }
}

代码逻辑走读:

  1. 获取文件目录:通过context.filesDir获取应用的文件目录路径。
  2. 创建文件可读流:使用fileIo.createReadStream方法创建一个读取test.txt文件的可读流。
  3. 创建文件可写流:使用fileIo.createWriteStream方法创建一个写入destFile.txt文件的可写流。
  4. 写入初始数据:调用rs.push('Hello world')将初始数据写入可读流。
  5. 流动模式拷贝文件:
    • 当可读流有数据可读时,触发data事件。
    • data事件中获取数据,并将其写入到可写流中。
  6. 结束拷贝:当可读流数据结束时,触发end事件,关闭可写流并输出成功信息。
  7. 异常处理:在error事件中,关闭可读流和可写流,并输出错误信息。
  8. 异常捕获:使用try-catch块捕获可能的异常,并在捕获到异常时关闭流并输出错误信息。

使用文件哈希流

哈希流是一种数据传输和存储技术,可以将任意长度的数据转换为固定长度的哈希值来验证数据的完整性和一致性。以下代码演示了如何使用文件哈希处理接口(ohos.file.hash)来处理文件哈希流。

// pages/xxx.ets
import { fileIo } from '@kit.CoreFileKit';
import { hash } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

// 获取应用文件路径,请在组件内获取context
let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
function hashFileWithStream(context: common.UIAbilityContext) {
  try {
    let filesDir = context.filesDir;
    const filePath = `${filesDir}/test.txt`;
    // 创建文件可读流
    const rs = fileIo.createReadStream(filePath);
    // 创建哈希流
    const hs = hash.createHash('sha256');
    rs.on('data', (emitData) => {
      const data = emitData?.data;
      hs.update(new Uint8Array(data?.split('').map((x: string) => x.charCodeAt(0))).buffer);
    });
    rs.on('end', async () => {
      const hashResult = hs.digest();
      const fileHash = await hash.hash(filePath, 'sha256');
      console.info(`Succeeded in hashing file with stream, hash result: ${hashResult}, file hash: ${fileHash}`);
    });
  } catch (err) {
    console.error(`Failed to hash file with stream. Code: ${err.code}, message: ${err.message}`);
  }
}

代码逻辑走读:

  1. 定义了一个名为hashFileWithStream的函数,该函数接受一个context参数,用于获取文件路径。
  2. 使用try-catch结构处理可能的异常。
  3. context中获取filesDir,并构建文件路径filePath
  4. 创建一个文件可读流rs,用于读取指定路径的文件。
  5. 创建一个哈希流hs,用于计算文件的哈希值。
  6. 在可读流的data事件触发时,读取数据并更新哈希流。
  7. 在可读流的end事件触发时,计算最终的哈希值,并与文件系统提供的哈希值进行比较。
  8. 如果过程中发生错误,捕获并输出错误信息。
Logo

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

更多推荐