鸿蒙新特性:@ohos.file.fs 文件系统实战
前言
文件系统是操作系统的核心抽象之一,应用的配置缓存、用户数据、离线内容都需要通过文件系统进行持久化存储。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);
注意 size 和 ino 是 bigint 类型,需要 Number() 转换后才能与其他 number 值进行算术运算。atimeNs 和 mtimeNs 是可选的(?),因为部分文件系统可能不记录这些时间。
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 获取文件描述符,再传递给 readSync、writeSync 和 closeSync。
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,可以在沙箱中创建、查看、删除文件,并实时查看存储空间变化。
功能模块
- 存储空间卡片 — 显示总容量、已使用、可用空间和当前文件数。刷新按钮可实时更新
- 新建文件 — 输入文件名和文本内容,创建新文件到 sandbox
- 文件列表 — 列出应用目录下所有文件和子目录,显示文件大小和类型标识
- 查看内容 — 点击文件读取并显示其文本内容(等宽字体,可滚动,超过 5000 字节自动截断)
- 删除文件 — 点击删除按钮移除文件,自动刷新列表和空间统计
- 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 同时接受 number 和 File 类型:
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:
- listFile/listFileSync — 目录浏览,获取文件和子目录名列表
- stat/statSync — 获取文件元数据(大小、类型、权限、时间戳)
- open/openSync — 打开/创建文件,返回 File 对象
- write/writeSync — 写入字符串或 ArrayBuffer 数据
- read/readSync — 读取数据到 ArrayBuffer
- close/closeSync — 关闭文件,释放文件描述符
- unlink/unlinkSync — 删除文件
- access/accessSync — 检查文件是否存在
- statfs.getTotalSizeSync/getFreeSizeSync — 分区容量和可用空间
HarmonyOS 的文件 I/O 模型直接继承自 Unix 的 open-read-write-close 范式,提供了精细的控制能力。结合应用沙箱机制,既保证了数据安全性,又提供了灵活的文件操作接口。
存储空间管理器 Demo 是一个实用的开发工具,展示了文件 CRUD 操作的完整流程,可直接在真机或模拟器上运行体验。
更多推荐




所有评论(0)