前言

文件系统是操作系统的核心抽象之一,应用的配置缓存、用户数据、离线内容都需要通过文件系统进行持久化存储。HarmonyOS 提供了 @ohos.file.fs 模块,封装了完整的 POSIX 文件 I/O 操作——打开、读取、写入、关闭、列出目录、获取文件信息、获取存储空间等核心功能。

与 Web 端的 File API 不同,HarmonyOS 的文件系统 API 更接近 Unix 系统的 open/read/write/close 模型。开发者通过文件描述符(File descriptor)与文件交互,获得精细的读写控制。每个 HarmonyOS 应用运行在独立的沙箱文件系统中,通过 @kit.CoreFileKit 提供的 fileIo 命名空间访问。

本文通过一个可交互的 存储空间管理器 Demo,深入讲解 @ohos.file.fs@ohos.file.statvfs 的全部核心 API,涵盖文件的增删查改和磁盘空间统计。

导入与沙箱模型

模块导入

import fileIo from '@ohos.file.fs';
import statfs from '@ohos.file.statvfs';

两个模块都在 @kit.CoreFileKit 中,但分属不同的 API 文件:

  • @ohos.file.fs — 文件操作(open/read/write/close/listFile/stat/unlink)
  • @ohos.file.statvfs — 文件系统统计(getFreeSize/getTotalSize)

应用沙箱

每个 HarmonyOS 应用运行在独立的文件系统沙箱中。通过 getContext(this).filesDir 获取应用专属的数据目录路径。应用只能读写自己的沙箱目录,无法直接访问其他应用的文件或系统目录(除非通过特定的 FilePicker API)。

let context: Context = getContext(this);
let appDir: string = context.filesDir;
// 示例路径: /data/storage/el2/base/haps/entry/files

沙箱目录通常在应用卸载时自动清理,适合存储用户数据、缓存文件、配置文件等。

fileIo.listFile / listFileSync — 目录浏览

列出指定目录下的所有文件和子目录名称。

同步版本

let names: string[] = fileIo.listFileSync(appDir);
// 返回: ["config.json", "cache", "test.txt", ...]

异步版本(Promise)

let names: string[] = await fileIo.listFile(appDir);

过滤选项

通过 ListFileOptions 参数可以按文件名过滤:

let options: fileIo.ListFileOptions = {
  filter: {
    suffix: ['.txt', '.json']  // 只列出 .txt 和 .json 文件
  }
};
let names = fileIo.listFileSync(appDir, options);

同步版本在 UI 线程中执行,适合少量文件的目录浏览。对于可能包含大量文件的目录,应使用异步 Promise 版本避免阻塞 UI。

fileIo.stat / statSync — 文件信息

获取文件的元数据,包括大小、权限、时间戳、文件类型等。

let statInfo: fileIo.Stat = fileIo.statSync('/path/to/file.txt');

Stat 对象关键属性与方法

interface Stat {
  readonly ino: bigint;     // 文件索引节点号
  readonly mode: number;    // 文件权限位(Unix 风格)
  readonly size: bigint;     // 文件大小(字节)
  readonly atimeNs?: bigint; // 最后访问时间(纳秒)
  readonly mtimeNs?: bigint; // 最后修改时间(纳秒)
  
  isFile(): boolean;         // 是否为普通文件
  isDirectory(): boolean;    // 是否为目录
  isBlockDevice(): boolean;  // 是否为块设备
  isCharacterDevice(): boolean; // 是否为字符设备
  isFIFO(): boolean;         // 是否为命名管道
  isSocket(): boolean;       // 是否为 socket
}

实际用法

let statInfo = fileIo.statSync(filePath);
let fileSize = Number(statInfo.size);  // bigint → number
let isDir = statInfo.isDirectory();    // 判断类型

// 获取修改时间(纳秒 → 毫秒 → Date)
let mtimeMs = Number(statInfo.mtimeNs) / 1000000;
let modifiedDate = new Date(mtimeMs);

注意 sizeinobigint 类型,需要 Number() 转换后才能与其他 number 值进行算术运算。atimeNsmtimeNs 是可选的(?),因为部分文件系统可能不记录这些时间。

fileIo.openSync / open — 打开文件

打开(或创建)文件,返回 File 对象,其中包含文件描述符 .fd

let file: fileIo.File = fileIo.openSync(path, mode);

OpenMode 常量

enum OpenMode {
  READ_ONLY = 0o0,    // 只读打开
  WRITE_ONLY = 0o1,   // 只写打开
  READ_WRITE = 0o2,   // 读写打开
  CREATE = 0o100,     // 文件不存在时创建
  TRUNC = 0o1000,     // 打开时截断文件(清空)
  APPEND = 0o2000,    // 追加模式
  NONBLOCK = 0o4000,  // 非阻塞模式
  SYNC = 0o4010000    // 同步写入
}

多个模式通过按位或(|)组合:

// 创建新文件(只写,不存在则创建)
fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);

// 只读打开
fileIo.openSync(path, fileIo.OpenMode.READ_ONLY);

// 追加写入
fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.APPEND);

File 对象

interface File {
  fd: number;  // 文件描述符,传递给 read/write/close
  path: string; // 文件路径
}

openSync 返回的 fileIo.File 需要通过 .fd 获取文件描述符,再传递给 readSyncwriteSynccloseSync

fileIo.writeSync / write — 写入文件

向打开的文件写入数据。

fileIo.writeSync(file.fd, dataString);

数据可以是字符串或 ArrayBuffer:

// 写入字符串
fileIo.writeSync(file.fd, 'Hello HarmonyOS!');

// 写入二进制数据
let buffer = new ArrayBuffer(1024);
fileIo.writeSync(file.fd, buffer);

在这里插入图片描述
在这里插入图片描述

完整创建文件示例

function createTextFile(path: string, content: string): void {
  let file = fileIo.openSync(path,
    fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
  fileIo.writeSync(file.fd, content);
  fileIo.closeSync(file);
}

fileIo.readSync / read — 读取文件

从打开的文件读取数据到 ArrayBuffer。

let buffer = new ArrayBuffer(fileSize);
fileIo.readSync(file.fd, buffer, { offset: 0, length: fileSize });

ReadOptions

interface ReadOptions {
  offset?: number;  // 缓冲区偏移量
  length?: number;  // 读取长度
}

ArrayBuffer → 字符串转换

读取的原始数据是 ArrayBuffer,需要手动转换为字符串:

let uint8Arr: Uint8Array = new Uint8Array(buffer);
let result: string = '';
for (let i = 0; i < uint8Arr.length; i++) {
  result += String.fromCharCode(uint8Arr[i]);
}

这种逐字节转换当文件较大时性能较差。对于大文件(>100KB),建议使用 fileIo.createStream() 创建流式读写对象,或者使用 fileIo.read() 异步版本分批读取。

fileIo.closeSync / close — 关闭文件

操作完毕后必须关闭文件,释放文件描述符。

fileIo.closeSync(file);

closeSync 接受 File 对象,而不仅仅是文件描述符数字。忘记关闭文件会导致文件描述符泄漏——每个进程的文件描述符数量有限(通常 1024 个),泄漏过多会导致无法打开新文件。

最佳实践:使用 try-finally 模式确保关闭:

let file: fileIo.File | null = null;
try {
  file = fileIo.openSync(path, fileIo.OpenMode.READ_ONLY);
  // ... 读取操作 ...
} finally {
  if (file) {
    fileIo.closeSync(file);
  }
}

fileIo.unlinkSync / unlink — 删除文件

删除指定路径的文件。

fileIo.unlinkSync('/path/to/file.txt');

最佳实践:安全删除

删除前检查文件是否存在并确认是文件(非目录):

function safeDelete(path: string): boolean {
  try {
    if (!fileIo.accessSync(path)) return false;
    let statInfo = fileIo.statSync(path);
    if (!statInfo.isFile()) return false;
    fileIo.unlinkSync(path);
    return true;
  } catch (e) {
    return false;
  }
}

fileIo.accessSync / access — 检查文件

检查文件是否存在(不打开文件)。

let exists: boolean = fileIo.accessSync('/path/to/file.txt');

返回 true 表示文件存在且可访问,但调用可能抛出异常(如权限不足等)。

@ohos.file.statvfs —— 存储空间统计

@ohos.file.statvfs 模块提供两个核心函数:

getTotalSizeSync / getTotalSize

获取指定路径所在分区的总容量(字节)。

let totalBytes: number = statfs.getTotalSizeSync(context.filesDir);

getFreeSizeSync / getFreeSize

获取指定路径所在分区的可用空间(字节)。

let freeBytes: number = statfs.getFreeSizeSync(context.filesDir);
let usedBytes: number = totalBytes - freeBytes;

格式化显示

function formatBytes(bytes: number): string {
  if (bytes < 1024) return bytes + ' B';
  if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
  if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
  return (bytes / 1073741824).toFixed(1) + ' GB';
}

Demo:存储空间管理器

我们构建了一个完整的文件系统操作 Demo,可以在沙箱中创建、查看、删除文件,并实时查看存储空间变化。

功能模块

  1. 存储空间卡片 — 显示总容量、已使用、可用空间和当前文件数。刷新按钮可实时更新
  2. 新建文件 — 输入文件名和文本内容,创建新文件到 sandbox
  3. 文件列表 — 列出应用目录下所有文件和子目录,显示文件大小和类型标识
  4. 查看内容 — 点击文件读取并显示其文本内容(等宽字体,可滚动,超过 5000 字节自动截断)
  5. 删除文件 — 点击删除按钮移除文件,自动刷新列表和空间统计
  6. API 速查列表 — 8 个核心 API 的名称和说明

核心实现:目录浏览与文件列表

refreshList(): void {
  let names: string[] = fileIo.listFileSync(this.currentPath);
  let items: FileItem[] = [];
  this.fileCount = names.length;

  for (let i = 0; i < names.length; i++) {
    let fullPath = this.currentPath + '/' + names[i];
    let statInfo: fileIo.Stat = fileIo.statSync(fullPath);
    let isDir: boolean = !statInfo.isFile();
    let sizeStr = isDir ? '[目录]' : this.formatBytes(Number(statInfo.size));
    items.push(new FileItem(names[i], sizeStr, isDir));
  }
  this.fileList = items;
}

核心实现:创建文件

let file: fileIo.File = fileIo.openSync(filePath,
  fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
fileIo.writeSync(file.fd, this.newFileContent);
fileIo.closeSync(file);

核心实现:读取文件

let statInfo = fileIo.statSync(filePath);
let fileSize = Number(statInfo.size);
let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
let buffer = new ArrayBuffer(fileSize);
fileIo.readSync(file.fd, buffer, { offset: 0, length: fileSize });
// ArrayBuffer → string conversion
let uint8Arr = new Uint8Array(buffer);
let result = '';
for (let i = 0; i < uint8Arr.length; i++) {
  result += String.fromCharCode(uint8Arr[i]);
}
fileIo.closeSync(file);

核心实现:存储空间统计

let totalBytes = statfs.getTotalSizeSync(this.currentPath);
let freeBytes = statfs.getFreeSizeSync(this.currentPath);
let usedBytes = totalBytes - freeBytes;

性能与最佳实践

1. 同步 vs 异步

同步 API(*Sync)在 UI 线程执行,适合小文件(<10KB)和少量文件操作。对于大文件读写或大量文件操作,应使用异步 Promise 版本避免阻塞 UI。

// 适合同步:小文件读取
let statInfo = fileIo.statSync(path);

// 应该异步:大文件拷贝
await fileIo.copyFile(src, dest);

2. 文件描述符管理

每个打开的文件必须被关闭。即使发生异常也要关闭——closeSync 同时接受 numberFile 类型:

let file: fileIo.File = fileIo.openSync(path, fileIo.OpenMode.READ_ONLY);
try {
  // 操作...
} finally {
  fileIo.closeSync(file);
}

3. 大文件读取

对于超过数 MB 的文件,不要一次性读入内存。使用 fileIo.createStream() 创建流式读写对象,分批读取:

let stream = fileIo.createStreamSync(path, 'r');
let buffer = new ArrayBuffer(4096); // 4KB 块
let bytesRead: number;
while ((bytesRead = stream.readSync(buffer)) > 0) {
  // 处理 buffer 中的 bytesRead 字节
}
stream.closeSync();

4. 存储空间检查

在写大文件前先检查可用空间:

let freeBytes = statfs.getFreeSizeSync(dirPath);
if (freeBytes < estimatedSize) {
  // 空间不足,提示用户
}

5. 路径拼接

避免手动拼接路径字符串(可能导致双斜杠或反斜杠问题)。使用路径常量:

// 推荐
let fullPath = this.currentPath + '/' + fileName;

// 对于复杂路径操作,考虑 fileUri 模块

与 Preferences 的对比

特性 @ohos.file.fs @ohos.data.preferences
用途 通用文件存储 键值对配置
数据类型 任意文本/二进制 基本类型(string/number/boolean)
文件位置 任意沙箱路径 固定配置目录
并发安全 需自行处理 框架保证
适合场景 文档、缓存、日志 用户设置、配置项

对于配置文件(开关、选项、用户偏好),使用 Preferences 更方便。对于用户数据(文本文档、JSON 缓存、日志文件),使用 @ohos.file.fs 更灵活。

总结

本文详细讲解了 HarmonyOS 文件系统操作的核心 API:

  1. listFile/listFileSync — 目录浏览,获取文件和子目录名列表
  2. stat/statSync — 获取文件元数据(大小、类型、权限、时间戳)
  3. open/openSync — 打开/创建文件,返回 File 对象
  4. write/writeSync — 写入字符串或 ArrayBuffer 数据
  5. read/readSync — 读取数据到 ArrayBuffer
  6. close/closeSync — 关闭文件,释放文件描述符
  7. unlink/unlinkSync — 删除文件
  8. access/accessSync — 检查文件是否存在
  9. statfs.getTotalSizeSync/getFreeSizeSync — 分区容量和可用空间

HarmonyOS 的文件 I/O 模型直接继承自 Unix 的 open-read-write-close 范式,提供了精细的控制能力。结合应用沙箱机制,既保证了数据安全性,又提供了灵活的文件操作接口。

存储空间管理器 Demo 是一个实用的开发工具,展示了文件 CRUD 操作的完整流程,可直接在真机或模拟器上运行体验。


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐