鸿蒙学习实战之路-ArkTS文件访问操作全攻略
最近好多朋友问我:“西兰花啊,我想在鸿蒙里读写文件,但一看到那些API文档就懵了!什么fileIo、Stream、Hash,看得我头都大了!这些到底咋用啊?”害,这问题可问对人了!我刚开始学时也被这些接口搞得晕头转向,今天这篇,我就手把手带你玩转文件操作,从入门到精通~读完这篇文章,你将收获:全程干货满满,让你的文件操作技能直接起飞!🥦想象一下,你的手机就像一个大型图书馆,文件操作API就是你管
鸿蒙学习实战之路-ArkTS文件访问操作全攻略
最近好多朋友问我:“西兰花啊,我想在鸿蒙里读写文件,但一看到那些API文档就懵了!什么fileIo、Stream、Hash,看得我头都大了!这些到底咋用啊?”
害,这问题可问对人了!我刚开始学时也被这些接口搞得晕头转向,今天这篇,我就手把手带你玩转文件操作,从入门到精通~
读完这篇文章,你将收获:
- 搞懂基础文件操作API的套路
- 学会文件读写、复制、移动等核心操作
- 掌握流式操作,处理大文件不卡顿
- 了解文件过滤和哈希验证的技巧
- 避开文件操作的各种坑点
全程干货满满,让你的文件操作技能直接起飞!🥦
1. 文件操作API大盘点:你的"文件管理工具箱"
想象一下,你的手机就像一个大型图书馆,文件操作API就是你管理图书的工具箱:
核心工具一览
| 工具名 | 功能 | 同步 | 异步 | 西兰花点评 |
|---|---|---|---|---|
| access | 检查文件是否存在 | ✅ | ✅ | 就像检查书架上有没有这本书 |
| open/close | 打开/关闭文件 | ✅ | ✅ | 开门关门,记得随手关灯 |
| read/write | 读写文件数据 | ✅ | ✅ | 读书写字,基础技能 |
| copyFile | 复制文件 | ✅ | ✅ | 影印书籍,方便备份 |
| moveFile | 移动文件 | ✅ | ✅ | 整理书架,腾地方 |
| mkdir/rmdir | 创建/删除目录 | ✅ | ✅ | 新建书架或清理书架 |
| listFile | 列出文件列表 | ✅ | ✅ | 查看书架上都有什么书 |
| stat | 获取文件属性 | ✅ | ✅ | 了解书的详细信息 |
🥦 西兰花警告:
耗时长的操作(read、write等)建议用异步接口!不然应用会卡死,用户体验就崩了~
2. 基础文件操作:从小白到入门
2.1 新建并读写文件:你的第一个"文件宝宝"
import { fileIo as fs, ReadOptions } from '@kit.CoreFileKit';
import { Context } from '@ohos.base';
@Entry
@ComponentV2
struct 文件读写演示 {
@Local 读写结果: string = '';
build() {
Column({ space: 20 }) {
Text('🥦 西兰花文件操作课堂')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Button('创建并读写文件')
.onClick(() => {
this.创建读写文件();
})
Text(this.读写结果)
.fontSize(14)
.fontColor('#666666')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.width('100%')
}
.padding(20)
}
创建读写文件() {
try {
// 获取应用上下文
const context = getContext() as Context;
const filesDir = context.filesDir;
// 第一步:创建并打开文件(如果不存在会自动创建)
const 文件句柄 = fs.openSync(
`${filesDir}/西兰花日记.txt`,
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
);
// 第二步:写入内容
const 今日心情 = "今天学会了文件操作,好开心!西兰花真是天才~";
const 写入长度 = fs.writeSync(文件句柄.fd, 今日心情);
// 第三步:准备读取数据
const 数据缓冲区 = new ArrayBuffer(1024); // 1KB缓冲区
const 读取选项: ReadOptions = {
offset: 0, // 从文件开头开始读取
length: 数据缓冲区.byteLength
};
// 第四步:读取文件内容
const 实际读取长度 = fs.readSync(文件句柄.fd, 数据缓冲区, 读取选项);
// 第五步:关闭文件(重要!)
fs.closeSync(文件句柄);
this.读写结果 = `
📝 文件操作成功!
📏 写入内容长度: ${写入长度} 字符
📖 读取内容长度: ${实际读取长度} 字符
📄 文件内容: ${今日心情}
`;
console.log('文件操作成功完成!');
} catch (错误信息) {
this.读写结果 = `❌ 操作失败: ${错误信息.message}`;
console.error(`文件操作失败: ${错误信息}`);
}
}
}
🥦 西兰花小贴士:
文件路径记得用Context获取,不要硬编码!这样既安全又兼容~
2.2 文件复制操作:备份你的"宝贝文件"
import { fileIo as fs, ReadOptions, WriteOptions } from '@kit.CoreFileKit';
import { Context } from '@ohos.base';
class 文件复制器 {
private context: Context;
constructor(context: Context) {
this.context = context;
}
// 复制文件(支持大文件)
async 复制文件(源文件路径: string, 目标文件路径: string): Promise<void> {
try {
const 源文件 = fs.openSync(源文件路径, fs.OpenMode.READ_ONLY);
const 目标文件 = fs.openSync(
目标文件路径,
fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE
);
// 分块读取,避免内存溢出
const 缓冲区大小 = 4096; // 4KB一块
let 已读取大小 = 0;
const 缓冲区 = new ArrayBuffer(缓冲区大小);
const 读取选项: ReadOptions = {
offset: 已读取大小,
length: 缓冲区大小
};
let 实际读取长度 = fs.readSync(源文件.fd, 缓冲区, 读取选项);
while (实际读取长度 > 0) {
// 写入对应长度的数据
const 写入选项: WriteOptions = {
length: 实际读取长度
};
fs.writeSync(目标文件.fd, 缓冲区, 写入选项);
// 更新读取位置
已读取大小 += 实际读取长度;
读取选项.offset = 已读取大小;
// 继续读取下一块
实际读取长度 = fs.readSync(源文件.fd, 缓冲区, 读取选项);
}
// 关闭文件
fs.closeSync(源文件);
fs.closeSync(目标文件);
console.log(`文件复制成功: ${源文件路径} -> ${目标文件路径}`);
} catch (错误) {
console.error(`文件复制失败: ${错误}`);
throw 错误;
}
}
}
// 使用示例
@Entry
@ComponentV2
struct 文件复制演示 {
@Local 复制结果: string = '';
build() {
Column({ space: 20 }) {
Text('🥦 文件复制小能手')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Button('复制文件测试')
.onClick(async () => {
await this.测试文件复制();
})
Text(this.复制结果)
.fontSize(14)
.fontColor('#666666')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.width('100%')
}
.padding(20)
}
async 测试文件复制() {
try {
const context = getContext() as Context;
const 复制器 = new 文件复制器(context);
const filesDir = context.filesDir;
// 先创建一个测试文件
const 测试文件 = `${filesDir}/源文件.txt`;
const 备份文件 = `${filesDir}/备份文件.txt`;
// 写入测试内容
const 测试内容 = "这是西兰花创建的测试文件!\n内容很丰富~";
const 文件句柄 = fs.openSync(测试文件, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE);
fs.writeSync(文件句柄.fd, 测试内容);
fs.closeSync(文件句柄);
// 复制文件
await 复制器.复制文件(测试文件, 备份文件);
// 验证复制结果
const 验证句柄 = fs.openSync(备份文件, fs.OpenMode.READ_ONLY);
const 验证缓冲区 = new ArrayBuffer(1024);
const 验证长度 = fs.readSync(验证句柄.fd, 验证缓冲区, { offset: 0, length: 1024 });
fs.closeSync(验证句柄);
const 验证内容 = new TextDecoder().decode(验证缓冲区.slice(0, 验证长度));
this.复制结果 = `
✅ 文件复制成功!
📁 源文件: 源文件.txt
📁 目标文件: 备份文件.txt
📄 复制内容: ${验证内容.trim()}
`;
} catch (错误) {
this.复制结果 = `❌ 复制失败: ${错误.message}`;
}
}
}
3. 流式操作:处理大文件的"秘密武器"
想象你在吃自助餐,如果一次性把整个餐厅的食物都盛到盘子里,肯定撑死!流式操作就是让你一口一口慢慢吃~
3.1 基础流式读写
import { fileIo as fs, ReadOptions } from '@kit.CoreFileKit';
import { Context } from '@ohos.base';
@Entry
@ComponentV2
struct 流式操作演示 {
@Local 流操作结果: string = '';
build() {
Column({ space: 20 }) {
Text('🥦 流式操作大师课')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Button('流式读写文件')
.onClick(async () => {
await this.流式读写演示();
})
Text(this.流操作结果)
.fontSize(14)
.fontColor('#666666')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.width('100%')
}
.padding(20)
}
async 流式读写演示() {
try {
const context = getContext() as Context;
const filesDir = context.filesDir;
// 第一步:创建输入流(读文件)
const 输入流 = fs.createStreamSync(`${filesDir}/输入文件.txt`, 'r+');
// 第二步:创建输出流(写文件)
const 输出流 = fs.createStreamSync(`${filesDir}/输出文件.txt`, 'w+');
// 第三步:设置缓冲区
const 缓冲区大小 = 4096;
let 已处理大小 = 0;
const 缓冲区 = new ArrayBuffer(缓冲区大小);
const 读取选项: ReadOptions = {
offset: 已处理大小,
length: 缓冲区大小
};
// 第四步:流式处理
let 实际读取长度 = await 输入流.read(缓冲区, 读取选项);
已处理大小 += 实际读取长度;
let 处理进度 = 0;
while (实际读取长度 > 0) {
// 根据实际读取长度处理数据
const 当前数据块 = 实际读取长度 < 缓冲区大小 ?
缓冲区.slice(0, 实际读取长度) : 缓冲区;
// 写入到输出流
await 输出流.write(当前数据块);
// 更新进度
处理进度++;
读取选项.offset = 已处理大小;
实际读取长度 = await 输入流.read(缓冲区, 读取选项);
已处理大小 += 实际读取长度;
}
// 第五步:关闭流(重要!)
输入流.closeSync();
输出流.closeSync();
this.流操作结果 = `
🌊 流式操作完成!
📊 处理进度: ${处理进度} 个数据块
📁 输入文件: 输入文件.txt
📁 输出文件: 输出文件.txt
💡 适用于大文件处理,不占用大量内存
`;
console.log('流式操作成功完成!');
} catch (错误) {
this.流操作结果 = `❌ 流操作失败: ${错误.message}`;
console.error(`流式操作失败: ${错误}`);
}
}
}
🥦 西兰花警告:
流操作一定要及时关闭!忘记关流会导致文件句柄泄漏,严重影响性能~
3.2 高级流操作:可读流和可写流
import { fileIo as fs } from '@kit.CoreFileKit';
import { Context } from '@ohos.base';
class 高级流处理器 {
private context: Context;
constructor(context: Context) {
this.context = context;
}
// 暂停模式复制(保证数据完整性)
async 暂停模式复制(源文件路径: string, 目标文件路径: string): Promise<void> {
return new Promise((resolve, reject) => {
try {
const 源流 = fs.createReadStream(源文件路径);
const 目标流 = fs.createWriteStream(目标文件路径);
// 暂停模式:读取数据后再写入
源流.on('readable', () => {
const 数据块 = 源流.read();
if (数据块) {
目标流.write(数据块);
} else {
// 数据读取完毕
目标流.end();
resolve();
}
});
源流.on('error', (错误) => {
reject(错误);
});
目标流.on('error', (错误) => {
reject(错误);
});
} catch (错误) {
reject(错误);
}
});
}
// 流动模式复制(实时处理)
async 流动模式复制(源文件路径: string, 目标文件路径: string): Promise<void> {
return new Promise((resolve, reject) => {
try {
const 源流 = fs.createReadStream(源文件路径);
const 目标流 = fs.createWriteStream(目标文件路径);
// 流动模式:数据流动时直接处理
源流.on('data', (发射数据) => {
const 实际数据 = 发射数据?.data;
if (实际数据) {
目标流.write(实际数据 as Uint8Array);
}
});
源流.on('end', () => {
目标流.end();
resolve();
});
源流.on('error', (错误) => {
reject(错误);
});
目标流.on('error', (错误) => {
reject(错误);
});
} catch (错误) {
reject(错误);
}
});
}
}
// 使用演示
@Entry
@ComponentV2
struct 高级流演示 {
@Local 高级流结果: string = '';
build() {
Column({ space: 20 }) {
Text('🥦 高级流操作实验室')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Row({ space: 10 }) {
Button('暂停模式复制')
.onClick(async () => {
await this.测试暂停模式();
})
Button('流动模式复制')
.onClick(async () => {
await this.测试流动模式();
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
Text(this.高级流结果)
.fontSize(14)
.fontColor('#666666')
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.width('100%')
}
.padding(20)
}
async 测试暂停模式() {
try {
const context = getContext() as Context;
const 处理器 = new 高级流处理器(context);
const filesDir = context.filesDir;
// 创建测试文件
const 测试内容 = "暂停模式测试:数据完整性优先~".repeat(100);
const 源文件 = `${filesDir}/暂停模式源文件.txt`;
const 目标文件 = `${filesDir}/暂停模式目标文件.txt`;
// 写入源文件
const 源句柄 = fs.openSync(源文件, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE);
fs.writeSync(源句柄.fd, 测试内容);
fs.closeSync(源句柄);
// 执行暂停模式复制
await 处理器.暂停模式复制(源文件, 目标文件);
this.高级流结果 = `
🛑 暂停模式复制完成
✨ 特点:数据完整性优先,适合重要文件
📊 复制进度:分批读取,确保每批数据完整
💡 场景:数据库文件、配置文件等
`;
} catch (错误) {
this.高级流结果 = `❌ 暂停模式失败: ${错误.message}`;
}
}
async 测试流动模式() {
try {
const context = getContext() as Context;
const 处理器 = new 高级流处理器(context);
const filesDir = context.filesDir;
// 创建测试文件
const 测试内容 = "流动模式测试:实时处理优先~".repeat(100);
const 源文件 = `${filesDir}/流动模式源文件.txt`;
const 目标文件 = `${filesDir}/流动模式目标文件.txt`;
// 写入源文件
const 源句柄 = fs.openSync(源文件, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE);
fs.writeSync(源句柄.fd, 测试内容);
fs.closeSync(源句柄);
// 执行流动模式复制
await 处理器.流动模式复制(源文件, 目标文件);
this.高级流结果 = `
🌊 流动模式复制完成
⚡ 特点:实时处理优先,性能更好
📊 复制进度:数据流动时直接处理
💡 场景:日志文件、媒体文件等
`;
} catch (错误) {
this.高级流结果 = `❌ 流动模式失败: ${错误.message}`;
}
}
}
🥦 西兰花小贴士:
- 暂停模式:适合对数据完整性要求高的场景
- 流动模式:适合对实时性要求高的场景
选择合适的模式,让你的文件处理更高效~
4. 文件管理技能:做文件的"贴心管家"
4.1 文件列表查看:你的"文件探索器"
import { fileIo as fs, Filter, ListFileOptions } from '@kit.CoreFileKit';
import { Context } from '@ohos.base';
interface 文件信息 {
名称: string;
大小: string;
修改时间: string;
类型: string;
}
@Entry
@ComponentV2
struct 文件列表管理器 {
@Local 文件列表: 文件信息[] = [];
@Local 搜索结果: string = '';
build() {
Column({ space: 20 }) {
Text('🥦 西兰花文件管理器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Button('扫描所有文件')
.onClick(() => {
this.扫描文件();
})
Button('查找图片文件')
.onClick(() => {
this.查找图片文件();
})
Button('查找最近修改的文件')
.onClick(() => {
this.查找最近修改文件();
})
Scroll() {
Column({ space: 10 }) {
ForEach(this.文件列表, (文件: 文件信息, 索引: number) => {
Row({ space: 10 }) {
Text(文件.类型)
.fontSize(16)
.fontColor('#4CAF50')
.width(30)
Column({ space: 5 }) {
Text(文件.名称)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(`${文件.大小} • ${文件.修改时间}`)
.fontSize(12)
.fontColor('#666666')
}
.layoutWeight(1)
}
.width('100%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(8)
})
}
}
.layoutWeight(1)
Text(this.搜索结果)
.fontSize(14)
.fontColor('#2196F3')
.padding(10)
.backgroundColor('#E3F2FD')
.borderRadius(6)
.width('100%')
}
.padding(20)
.height('100%')
}
扫描文件() {
try {
const context = getContext() as Context;
const filesDir = context.filesDir;
// 设置扫描选项
const 扫描选项: ListFileOptions = {
recursion: false, // 不递归扫描子目录
listNum: 0, // 列出所有文件(0表示不限制)
filter: {
suffix: [], // 不限制文件类型
displayName: [], // 不限制文件名模式
fileSizeOver: 0, // 不限制文件大小
lastModifiedAfter: 0 // 不限制修改时间
}
};
const 文件名列表 = fs.listFileSync(filesDir, 扫描选项);
this.文件列表 = 文件名列表.map((文件名) => {
try {
const 文件路径 = `${filesDir}/${文件名}`;
const 文件统计 = fs.statSync(文件路径);
// 判断文件类型
let 文件类型 = '📄';
if (文件名.endsWith('.txt')) 文件类型 = '📝';
else if (文件名.endsWith('.png') || 文件名.endsWith('.jpg')) 文件类型 = '🖼️';
else if (文件名.endsWith('.json')) 文件类型 = '📋';
else if (文件名.endsWith('.log')) 文件类型 = '📜';
return {
名称: 文件名,
大小: this.格式化文件大小(文件统计.size),
修改时间: new Date(文件统计.mtime).toLocaleString(),
类型: 文件类型
};
} catch (错误) {
return {
名称: 文件名,
大小: '未知',
修改时间: '未知',
类型: '❓'
};
}
});
this.搜索结果 = `📊 扫描完成,共发现 ${文件名列表.length} 个文件`;
} catch (错误) {
this.搜索结果 = `❌ 扫描失败: ${错误.message}`;
}
}
查找图片文件() {
try {
const context = getContext() as Context;
const filesDir = context.filesDir;
// 只查找图片文件
const 图片扫描选项: ListFileOptions = {
recursion: false,
listNum: 0,
filter: {
suffix: ['.png', '.jpg', '.jpeg', '.gif', '.webp'],
displayName: [],
fileSizeOver: 0,
lastModifiedAfter: 0
}
};
const 图片列表 = fs.listFileSync(filesDir, 图片扫描选项);
this.文件列表 = 图片列表.map((文件名) => ({
名称: 文件名,
大小: '图片文件',
修改时间: '最近',
类型: '🖼️'
}));
this.搜索结果 = `🖼️ 找到 ${图片列表.length} 个图片文件`;
} catch (错误) {
this.搜索结果 = `❌ 搜索失败: ${错误.message}`;
}
}
查找最近修改文件() {
try {
const context = getContext() as Context;
const filesDir = context.filesDir;
// 查找最近一天修改的文件
const 一天前时间戳 = Date.now() - 24 * 60 * 60 * 1000;
const 最近文件选项: ListFileOptions = {
recursion: false,
listNum: 0,
filter: {
suffix: [],
displayName: [],
fileSizeOver: 0,
lastModifiedAfter: 一天前时间戳
}
};
const 最近列表 = fs.listFileSync(filesDir, 最近文件选项);
this.文件列表 = 最近列表.map((文件名) => ({
名称: 文件名,
大小: '最近修改',
修改时间: '24小时内',
类型: '🕒'
}));
this.搜索结果 = `🕒 找到 ${最近列表.length} 个最近修改的文件`;
} catch (错误) {
this.搜索结果 = `❌ 搜索失败: ${错误.message}`;
}
}
// 格式化文件大小显示
private 格式化文件大小(字节数: number): string {
if (字节数 < 1024) return `${字节数} B`;
if (字节数 < 1024 * 1024) return `${(字节数 / 1024).toFixed(1)} KB`;
if (字节数 < 1024 * 1024 * 1024) return `${(字节数 / (1024 * 1024)).toFixed(1)} MB`;
return `${(字节数 / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
}
5. 文件哈希验证:你的"文件体检医生"
想象你网购了一套护肤品,怎么确认商家没给你掉包呢?哈希值就是文件的"身份证"~
5.1 文件哈希流操作
import { fileIo as fs } from '@kit.CoreFileKit';
import { hash } from '@kit.CoreFileKit';
import { Context } from '@ohos.base';
@Entry
@ComponentV2
struct 文件哈希验证器 {
@Local 验证结果: string = '';
@Local 文件信息: string = '';
build() {
Column({ space: 20 }) {
Text('🥦 文件哈希验证器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
Button('创建测试文件并验证哈希')
.onClick(async () => {
await this.创建并验证文件();
})
Text(this.文件信息)
.fontSize(14)
.fontColor('#666666')
.padding(15)
.backgroundColor('#FFF3E0')
.borderRadius(8)
.width('100%')
Text(this.验证结果)
.fontSize(14)
.fontColor('#666666')
.padding(15)
.backgroundColor('#E8F5E8')
.borderRadius(8)
.width('100%')
}
.padding(20)
}
async 创建并验证文件() {
try {
const context = getContext() as Context;
const filesDir = context.filesDir;
const 测试文件路径 = `${filesDir}/哈希测试文件.txt`;
// 第一步:创建测试文件
const 测试内容 = `
这是西兰花创建的哈希测试文件!
创建时间: ${new Date().toLocaleString()}
内容: 验证文件完整性的重要示例~
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
`;
const 文件句柄 = fs.openSync(测试文件路径, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE);
fs.writeSync(文件句柄.fd, 测试内容);
fs.closeSync(文件句柄);
// 第二步:获取文件统计信息
const 文件统计 = fs.statSync(测试文件路径);
this.文件信息 = `
📁 文件路径: ${测试文件路径}
📏 文件大小: ${this.格式化文件大小(文件统计.size)}
🕒 修改时间: ${new Date(文件统计.mtime).toLocaleString()}
`;
// 第三步:使用哈希流计算哈希值
const 流哈希值 = await this.计算流哈希值(测试文件路径);
// 第四步:使用直接哈希接口计算哈希值
const 直接哈希值 = await hash.hash(测试文件路径, 'sha256');
// 第五步:验证结果
const 验证通过 = 流哈希值 === 直接哈希值;
this.验证结果 = `
🧮 哈希验证结果
${验证通过 ? '✅' : '❌'} 两种方法结果一致: ${验证通过}
🌊 流式哈希值: ${流哈希值}
⚡ 直接哈希值: ${直接哈希值}
💡 哈希验证的应用场景:
• 确保文件下载完整性
• 验证文件未被篡改
• 检测文件重复
• 数字签名验证
`;
} catch (错误) {
this.验证结果 = `❌ 哈希验证失败: ${错误.message}`;
}
}
// 使用流式计算哈希值
private async 计算流哈希值(文件路径: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
// 创建文件可读流
const 文件流 = fs.createReadStream(文件路径);
// 创建SHA256哈希流
const 哈希流 = hash.createHash('sha256');
// 监听数据事件
文件流.on('data', (发射数据) => {
const 实际数据 = 发射数据?.data;
if (实际数据) {
// 将字符串数据转换为字节数组
const 字节数组 = new Uint8Array(
实际数据.split('').map((字符: string) => 字符.charCodeAt(0))
);
哈希流.update(字节数组.buffer);
}
});
// 监听关闭事件
文件流.on('close', async () => {
try {
const 哈希结果 = 哈希流.digest();
resolve(哈希结果);
} catch (错误) {
reject(错误);
}
});
// 监听错误事件
文件流.on('error', (错误) => {
reject(错误);
});
} catch (错误) {
reject(错误);
}
});
}
// 格式化文件大小
private 格式化文件大小(字节数: number): string {
if (字节数 < 1024) return `${字节数} B`;
if (字节数 < 1024 * 1024) return `${(字节数 / 1024).toFixed(1)} KB`;
if (字节数 < 1024 * 1024 * 1024) return `${(字节数 / (1024 * 1024)).toFixed(1)} MB`;
return `${(字节数 / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
}
🥦 西兰花警告:
哈希值是文件完整性的重要保障!重要文件传输后一定要验证哈希值,防止被恶意篡改~
6. 常见问题排查:文件操作的"救火队员"
Q1: 文件读写报"权限不足"错误?
A:
- 检查文件路径是否正确(用Context获取)
- 确认文件是否存在
- 验证OpenMode设置是否正确
Q2: 大文件操作时应用卡死?
A:
- 使用异步接口代替同步接口
- 采用流式操作分块处理
- 增加适当的延迟和进度反馈
Q3: 流操作忘记关闭导致文件句柄泄漏?
A:
- 使用try-finally确保流被关闭
- 考虑使用async-await简化错误处理
- 定期检查文件句柄使用情况
Q4: 文件路径在不同设备上不兼容?
A:
- 始终使用Context获取路径
- 避免硬编码绝对路径
- 使用相对路径(相对于应用目录)
🥦 西兰花小贴士:
文件操作一定要做好异常处理!用户数据无小事,一个小错误可能导致重要数据丢失~
7. 实战项目:西兰花文件管理器
最后给大家一个完整的文件管理器,整合所有知识点:
import { fileIo as fs, ReadOptions, WriteOptions, Filter, ListFileOptions } from '@kit.CoreFileKit';
import { hash } from '@kit.CoreFileKit';
import { Context } from '@ohos.base';
interface 文件详情 {
名称: string;
路径: string;
大小: number;
修改时间: number;
类型: string;
哈希值?: string;
}
@Entry
@ComponentV2
struct 西兰花完整文件管理器 {
@Local 当前路径: string = '';
@Local 文件列表: 文件详情[] = [];
@Local 操作日志: string = '';
@Local 搜索过滤器: string = '';
aboutToAppear() {
this.刷新文件列表();
}
build() {
Column({ space: 15 }) {
// 标题栏
Text('🥦 西兰花文件管理器 Pro')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2E7D32')
.margin({ bottom: 10 })
// 路径显示
Text(`📁 当前目录: ${this.当前路径 || 'files'}`)
.fontSize(12)
.fontColor('#666666')
.backgroundColor('#E8F5E8')
.padding(8)
.borderRadius(6)
// 搜索和过滤
Row({ space: 10 }) {
TextInput({ placeholder: '搜索文件名...' })
.onChange((值: string) => {
this.搜索过滤器 = 值;
this.筛选文件列表();
})
.layoutWeight(1)
Button('🔄 刷新')
.onClick(() => this.刷新文件列表())
}
.width('100%')
// 快速操作按钮
Row({ space: 8 }) {
Button('新建文件')
.fontSize(12)
.onClick(() => this.新建文件())
Button('新建文件夹')
.fontSize(12)
.onClick(() => this.新建文件夹())
Button('批量操作')
.fontSize(12)
.onClick(() => this.批量操作())
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
// 文件列表
Scroll() {
Column({ space: 8 }) {
ForEach(this.筛选后的文件列表(), (文件: 文件详情, 索引: number) => {
this.文件项组件(文件, 索引)
})
}
}
.layoutWeight(1)
// 操作日志
Scroll() {
Text(this.操作日志)
.fontSize(12)
.fontColor('#666666')
.backgroundColor('#F5F5F5')
.padding(10)
.borderRadius(6)
.width('100%')
.maxLines(3)
}
.height(80)
}
.padding(15)
.height('100%')
}
// 文件项组件
@Builder
文件项组件(文件: 文件详情, 索引: number) {
Row({ space: 10 }) {
Text(文件.类型)
.fontSize(18)
.width(30)
Column({ space: 3 }) {
Text(文件.名称)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
Text(`${this.格式化大小(文件.大小)} • ${new Date(文件.修改时间).toLocaleString()}`)
.fontSize(11)
.fontColor('#999999')
}
.layoutWeight(1)
// 操作按钮
Row({ space: 5 }) {
if (文件.类型 === '📁') {
Button('进入')
.fontSize(10)
.backgroundColor('#2196F3')
.onClick(() => this.进入目录(文件))
} else {
Button('查看')
.fontSize(10)
.backgroundColor('#4CAF50')
.onClick(() => this.查看文件(文件))
Button('哈希')
.fontSize(10)
.backgroundColor('#FF9800')
.onClick(() => this.计算哈希值(文件))
}
Button('删除')
.fontSize(10)
.backgroundColor('#F44336')
.onClick(() => this.删除文件(文件))
}
}
.width('100%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(8)
}
// 筛选后的文件列表
筛选后的文件列表(): 文件详情[] {
if (!this.搜索过滤器) {
return this.文件列表;
}
return this.文件列表.filter(文件 =>
文件.名称.toLowerCase().includes(this.搜索过滤器.toLowerCase())
);
}
// 刷新文件列表
刷新文件列表() {
try {
const context = getContext() as Context;
const filesDir = context.filesDir;
this.当前路径 = filesDir;
const 扫描选项: ListFileOptions = {
recursion: false,
listNum: 0,
filter: {
suffix: [],
displayName: [],
fileSizeOver: 0,
lastModifiedAfter: 0
}
};
const 文件名列表 = fs.listFileSync(filesDir, 扫描选项);
this.文件列表 = 文件名列表.map((文件名) => {
const 文件路径 = `${filesDir}/${文件名}`;
const 文件统计 = fs.statSync(文件路径);
return {
名称: 文件名,
路径: 文件路径,
大小: 文件统计.size,
修改时间: 文件统计.mtime,
类型: this.判断文件类型(文件名)
};
});
this.添加日志(`📊 刷新完成,发现 ${文件名列表.length} 个文件/文件夹`);
} catch (错误) {
this.添加日志(`❌ 刷新失败: ${错误.message}`);
}
}
// 新建文件
新建文件() {
try {
const context = getContext() as Context;
const 文件名 = `西兰花文件_${Date.now()}.txt`;
const 文件路径 = `${context.filesDir}/${文件名}`;
const 默认内容 = `这是西兰花创建的文件!
创建时间: ${new Date().toLocaleString()}
内容: 空文件,等待编辑~`;
const 文件句柄 = fs.openSync(文件路径, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE);
fs.writeSync(文件句柄.fd, 默认内容);
fs.closeSync(文件句柄);
this.刷新文件列表();
this.添加日志(`📝 新建文件成功: ${文件名}`);
} catch (错误) {
this.添加日志(`❌ 新建文件失败: ${错误.message}`);
}
}
// 新建文件夹
新建文件夹() {
try {
const context = getContext() as Context;
const 文件夹名 = `西兰花文件夹_${Date.now()}`;
const 文件夹路径 = `${context.filesDir}/${文件夹名}`;
fs.mkdirSync(文件夹路径);
this.刷新文件列表();
this.添加日志(`📁 新建文件夹成功: ${文件夹名}`);
} catch (错误) {
this.添加日志(`❌ 新建文件夹失败: ${错误.message}`);
}
}
// 批量操作
批量操作() {
this.添加日志('🔧 批量操作功能开发中...');
}
// 进入目录
进入目录(文件: 文件详情) {
this.添加日志(`📂 进入目录: ${文件.名称}`);
}
// 查看文件
查看文件(文件: 文件详情) {
try {
const 文件句柄 = fs.openSync(文件.路径, fs.OpenMode.READ_ONLY);
const 缓冲区 = new ArrayBuffer(1024);
const 读取长度 = fs.readSync(文件句柄.fd, 缓冲区, { offset: 0, length: 1024 });
fs.closeSync(文件句柄);
const 内容 = new TextDecoder().decode(缓冲区.slice(0, 读取长度));
this.添加日志(`📄 文件内容预览:\n${内容.substring(0, 200)}${内容.length > 200 ? '...' : ''}`);
} catch (错误) {
this.添加日志(`❌ 读取文件失败: ${错误.message}`);
}
}
// 计算哈希值
async 计算哈希值(文件: 文件详情) {
try {
const 哈希值 = await hash.hash(文件.路径, 'sha256');
文件.哈希值 = 哈希值;
this.添加日志(`🧮 ${文件.名称} 的SHA256: ${哈希值.substring(0, 16)}...`);
} catch (错误) {
this.添加日志(`❌ 计算哈希失败: ${错误.message}`);
}
}
// 删除文件
删除文件(文件: 文件详情) {
try {
if (文件.类型 === '📁') {
fs.rmdirSync(文件.路径);
} else {
fs.unlinkSync(文件.路径);
}
this.刷新文件列表();
this.添加日志(`🗑️ 删除成功: ${文件.名称}`);
} catch (错误) {
this.添加日志(`❌ 删除失败: ${错误.message}`);
}
}
// 判断文件类型
private 判断文件类型(文件名: string): string {
if (文件名.includes('.')) {
const 扩展名 = 文件名.substring(文件名.lastIndexOf('.'));
switch (扩展名.toLowerCase()) {
case '.txt': return '📝';
case '.json': return '📋';
case '.png':
case '.jpg':
case '.jpeg':
case '.gif': return '🖼️';
case '.log': return '📜';
default: return '📄';
}
}
return '📁';
}
// 格式化大小
private 格式化大小(字节数: number): string {
if (字节数 < 1024) return `${字节数}B`;
if (字节数 < 1024 * 1024) return `${(字节数 / 1024).toFixed(1)}KB`;
if (字节数 < 1024 * 1024 * 1024) return `${(字节数 / (1024 * 1024)).toFixed(1)}MB`;
return `${(字节数 / (1024 * 1024 * 1024)).toFixed(1)}GB`;
}
// 添加日志
private 添加日志(消息: string) {
const 时间 = new Date().toLocaleTimeString();
this.操作日志 = `[${时间}] ${消息}\n${this.操作日志}`;
}
}
总结
今天咱们一起掌握了文件操作的"十八般武艺":
- 基础操作:文件读写、复制、移动,像使用瑞士军刀一样顺手
- 流式操作:处理大文件的秘密武器,让应用不再卡顿
- 文件管理:查看、过滤、搜索,做文件的贴心管家
- 哈希验证:文件的"体检医生",确保数据完整性
- 错误处理:各种坑点的避雷指南,让你的代码更稳健
记住:文件操作无小事,异常处理要当先! 🥦
推荐资源
📚 推荐资料:
我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦
更多推荐




所有评论(0)