鸿蒙学习实战之路-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.操作日志}`;
  }
}

总结

今天咱们一起掌握了文件操作的"十八般武艺":

  1. 基础操作:文件读写、复制、移动,像使用瑞士军刀一样顺手
  2. 流式操作:处理大文件的秘密武器,让应用不再卡顿
  3. 文件管理:查看、过滤、搜索,做文件的贴心管家
  4. 哈希验证:文件的"体检医生",确保数据完整性
  5. 错误处理:各种坑点的避雷指南,让你的代码更稳健

记住:文件操作无小事,异常处理要当先! 🥦


推荐资源

📚 推荐资料:


我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦

Logo

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

更多推荐