做鸿蒙开发几乎都会遇到本地文件需求:保存用户输入的文本、缓存下载的图片、导出日志、记录本地数据……很多新手一上来就被「沙箱目录」「文件URI」「权限申请」绕晕,要么写的代码报权限错误,要么存完文件找不到在哪。
在这里插入图片描述

在鸿蒙 7(API 26)的 @kit 体系中,CoreFileKit 是官方统一的文件操作套件,替代了旧版零散的 @ohos.file.fs@ohos.file.fileuri 等模块,API 更规整,沙箱安全机制更清晰,新手也能快速上手。

本文从最基础的沙箱概念讲起,覆盖常用文件操作,附带完整可运行的简易文件工具箱 Demo,小白照着做也能轻松实现本地文件读写。

一、先搞懂基础:沙箱、目录与 CoreFileKit

1.1 什么是 CoreFileKit

@kit.CoreFileKit 是鸿蒙系统原生文件操作能力的统一套件,涵盖文件读写、目录管理、路径与 URI 转换、文件信息查询等全部基础能力,是所有本地文件操作的官方入口。

它解决了旧版的几个痛点:

  • 模块分散,文件、URI、权限分属不同包,导入繁琐
  • 沙箱规则不清晰,新手容易踩权限坑
  • API 命名不统一,同步异步方法混淆

1.2 小白必懂:应用沙箱是什么

这是文件操作最核心的概念,搞懂了能避开 80% 的报错。

每个鸿蒙应用都有一个独立的「私有沙箱目录」,就像应用自己的专属抽屉:

  • 应用可以随便读写,不需要申请任何权限
  • ❌ 其他应用无法直接访问,保证数据安全
  • ✅ 应用卸载时,沙箱里的文件会一起被清理

除了私有沙箱,还有系统公共目录(相册、下载、文档等),相当于公共储物柜:

  • ❌ 不能直接随便读写,必须申请对应权限
  • ✅ 所有应用都可以按规则访问
  • ✅ 卸载应用不会被删除

新手入门建议:先只操作应用沙箱内的文件,不用碰权限,代码写起来最简单,不容易报错。

1.3 常用沙箱目录说明

通过应用上下文可以直接获取系统分配的标准目录,千万不要自己硬编码路径,不同设备路径可能不一样。

目录 获取方式 用途说明
持久文件目录 context.filesDir 长期保存的重要数据,不会自动清理
缓存目录 context.cacheDir 临时缓存文件,系统存储空间不足时可能被清理
临时文件目录 context.tempDir 非常短生命周期的临时文件

示例:

const context = getContext(this);
console.info(`沙箱文件目录: ${context.filesDir}`);

1.4 版本与导入

  • 最低支持版本:API 12,API 26 能力最完善
  • 导入方式:
import { fs, fileUri } from '@kit.CoreFileKit';
  • fs:负责文件读写、目录管理等核心操作
  • fileUri:负责文件路径与系统 URI 互相转换,分享文件时必用

二、核心 API 速查:80% 场景常用的文件操作

日常开发绝大多数场景,只需要掌握下面这几组方法就够了,全部以同步方法为例,写法最简单,适合小白入门。

2.1 最常用:文本文件读写

写入文本
import { fs } from '@kit.CoreFileKit';

const context = getContext(this);
const filePath = `${context.filesDir}/note.txt`;

// 写入文本,文件不存在会自动创建,已存在会覆盖
fs.writeText(filePath, '这是我保存的第一条笔记内容');
读取文本
// 读取文本内容
const content = fs.readText(filePath);
console.info(`读取到的内容:${content}`);

2.2 目录操作:创建、列出文件

创建目录
const dirPath = `${context.filesDir}/my_folder`;
// 创建目录,已存在不会报错
fs.mkdir(dirPath);
列出目录下所有文件
// 获取目录下所有文件名数组
const fileList = fs.listFile(context.filesDir);
console.info(`目录下的文件:${JSON.stringify(fileList)}`);

2.3 文件管理:删除、判断存在、重命名

判断文件/目录是否存在
const isExist = fs.exists(filePath);
console.info(`文件是否存在:${isExist}`);
删除文件/目录
// 删除文件
fs.delete(filePath);

// 删除目录(目录必须为空才能删除)
fs.delete(dirPath);
重命名/移动文件
// 重命名
fs.rename(filePath, `${context.filesDir}/new_note.txt`);

2.4 关键能力:文件路径与 URI 互转

这是新手最容易忽略的一步:应用沙箱的本地路径,不能直接传给其他应用/系统分享使用,必须转换成标准 File URI。

最典型的场景就是 ShareKit 分享文件,必须转 URI 才能正常分享。

import { fileUri } from '@kit.CoreFileKit';

const filePath = `${context.filesDir}/note.txt`;

// 本地路径 → 系统可识别的 File URI
const uri = fileUri.getUriFromFilePath(filePath);
console.info(`转换后的URI:${uri}`);

// URI → 本地路径(反向转换)
const path = fileUri.getFilePathFromUri(uri);

三、实战:手写简易本地文件工具箱

下面我们做一个完整可运行的 Demo,实现四个核心功能:

  1. 输入文本保存到沙箱文件
  2. 读取文件内容并展示
  3. 列出沙箱目录下所有文件
  4. 删除指定文件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

全程只操作应用沙箱,不需要申请任何权限,代码复制到 Index.ets 就能直接运行。

import { fs, fileUri } from '@kit.CoreFileKit';
import { promptAction } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = 'FileToolDemo';

@Entry
@Component
struct FileToolPage {
  @State inputText: string = '';
  @State showContent: string = '';
  @State fileList: string[] = [];

  // 统一文件名
  private readonly demoFileName = 'demo_note.txt';

  aboutToAppear() {
    // 进入页面先刷新文件列表
    this.refreshFileList();
  }

  /**
   * 1. 保存文本到文件
   */
  saveToFile() {
    if (!this.inputText.trim()) {
      promptAction.showToast({ message: '请输入内容' });
      return;
    }

    try {
      const context = getContext(this);
      const filePath = `${context.filesDir}/${this.demoFileName}`;

      // 写入文本
      fs.writeText(filePath, this.inputText);
      hilog.info(0x0000, TAG, `文件已保存:${filePath}`);

      promptAction.showToast({ message: '保存成功' });
      this.refreshFileList();
    } catch (err) {
      hilog.error(0x0000, TAG, `保存失败:${JSON.stringify(err)}`);
      promptAction.showToast({ message: '保存失败' });
    }
  }

  /**
   * 2. 读取文件内容
   */
  readFromFile() {
    try {
      const context = getContext(this);
      const filePath = `${context.filesDir}/${this.demoFileName}`;

      if (!fs.exists(filePath)) {
        promptAction.showToast({ message: '文件不存在,请先保存' });
        return;
      }

      this.showContent = fs.readText(filePath);
      promptAction.showToast({ message: '读取成功' });
    } catch (err) {
      hilog.error(0x0000, TAG, `读取失败:${JSON.stringify(err)}`);
      promptAction.showToast({ message: '读取失败' });
    }
  }

  /**
   * 3. 刷新文件列表
   */
  refreshFileList() {
    try {
      const context = getContext(this);
      // 列出 filesDir 下所有文件
      this.fileList = fs.listFile(context.filesDir);
    } catch (err) {
      hilog.error(0x0000, TAG, `获取列表失败:${JSON.stringify(err)}`);
    }
  }

  /**
   * 4. 删除指定文件
   */
  deleteFile(fileName: string) {
    try {
      const context = getContext(this);
      const filePath = `${context.filesDir}/${fileName}`;

      fs.delete(filePath);
      promptAction.showToast({ message: '已删除' });
      this.refreshFileList();

      // 如果删的是演示文件,清空显示内容
      if (fileName === this.demoFileName) {
        this.showContent = '';
      }
    } catch (err) {
      hilog.error(0x0000, TAG, `删除失败:${JSON.stringify(err)}`);
      promptAction.showToast({ message: '删除失败' });
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('简易文件工具箱')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .width('90%')
        .margin({ top: 40 });

      // 输入区
      Column({ space: 10 }) {
        Text('输入保存内容')
          .fontSize(16)
          .width('100%');

        TextArea({ text: this.inputText, placeholder: '请输入要保存的文本' })
          .width('100%')
          .height(120)
          .borderRadius(10)
          .padding(12)
          .backgroundColor('#F5F5F5')
          .onChange((val) => {
            this.inputText = val;
          });

        Row({ space: 12 }) {
          Button('保存到文件')
            .layoutWeight(1)
            .height(44)
            .borderRadius(22)
            .backgroundColor('#0A59F7')
            .onClick(() => this.saveToFile());

          Button('读取文件')
            .layoutWeight(1)
            .height(44)
            .borderRadius(22)
            .backgroundColor('#00B578')
            .onClick(() => this.readFromFile());
        }
        .width('100%');
      }
      .width('90%')
      .alignItems(HorizontalAlign.Start);

      // 读取结果展示
      if (this.showContent) {
        Column({ space: 8 }) {
          Text('读取到的文件内容')
            .fontSize(16)
            .width('100%');

          Text(this.showContent)
            .width('100%')
            .padding(12)
            .backgroundColor('#F0F7FF')
            .borderRadius(8)
            .fontSize(14)
            .fontColor('#333333');
        }
        .width('90%')
        .alignItems(HorizontalAlign.Start);
      }

      // 文件列表
      Column({ space: 10 }) {
        Row() {
          Text('沙箱文件列表')
            .fontSize(16)
            .layoutWeight(1);

          Text('刷新')
            .fontSize(14)
            .fontColor('#0A59F7')
            .onClick(() => this.refreshFileList());
        }
        .width('100%');

        if (this.fileList.length === 0) {
          Text('暂无文件')
            .width('100%')
            .textAlign(TextAlign.Center)
            .fontColor('#AAAAAA')
            .padding({ top: 20, bottom: 20 });
        } else {
          List({ space: 8 }) {
            ForEach(this.fileList, (fileName: string) => {
              ListItem() {
                Row() {
                  Text(fileName)
                    .fontSize(14)
                    .layoutWeight(1);

                  Text('删除')
                    .fontSize(13)
                    .fontColor('#FF4D4F')
                    .onClick(() => this.deleteFile(fileName));
                }
                .width('100%')
                .padding(12)
                .backgroundColor('#FFFFFF')
                .borderRadius(8);
              }
            })
          }
          .width('100%');
        }
      }
      .width('90%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .shadow({ radius: 4, color: '#1A000000' });

      Blank();

      Text('提示:所有文件均保存在应用私有沙箱,无需权限')
        .fontSize(12)
        .fontColor('#AAAAAA')
        .margin({ bottom: 20 });
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA');
  }
}

运行验证步骤

  1. 在输入框输入任意文字,点击「保存到文件」,提示保存成功
  2. 点击「读取文件」,下方会展示刚才保存的内容
  3. 文件列表会实时显示沙箱内的所有文件
  4. 点击文件名右侧的删除按钮,可移除对应文件

整个 Demo 没有申请任何权限,纯沙箱内操作,不会出现权限报错,非常适合新手练手。

四、小白高频踩坑避坑指南

1. 报错:Permission denied(权限不足)

  • 原因:试图访问应用沙箱以外的目录,比如直接读写系统根目录、相册目录
  • 解决:新手先只操作 context.filesDircontext.cacheDir 下的文件;需要访问公共目录请申请对应权限并通过系统文件选择器访问

2. 报错:No such file or directory

  • 原因:文件路径写错,或者上级目录不存在
  • 解决:不要自己拼接复杂路径,优先用 context.filesDir 拼接;写入深层目录文件前,先用 fs.mkdir 创建上级目录

3. ShareKit 分享文件提示找不到文件

  • 原因:直接把沙箱本地路径传给了分享接口,其他应用没有权限访问
  • 解决:必须用 fileUri.getUriFromFilePath() 转换成标准 File URI 再分享,系统会自动授予临时读权限

4. 读取大文件界面卡顿

  • 原因:在主线程用同步方法读取大文件,阻塞了 UI 渲染
  • 解决:几 KB 的小文本用同步方法没问题;几 MB 以上的大文件建议用异步方法(fs.writeText 不加 Sync 后缀的版本),配合 async/await 使用

5. 导入 @kit.CoreFileKit 报错

  • 原因:SDK 版本低于 API 12,或使用了 OpenHarmony SDK
  • 解决:升级 SDK 到 26.0.0 及以上,确认工程 runtimeOS 为 HarmonyOS

五、旧版 @ohos.file.fs 迁移对照表

如果你的项目还在用旧版文件接口,迁移到 CoreFileKit 非常简单,核心 API 几乎一致,只改导入即可。

操作 旧版 @ohos.file.fs 新版 @kit.CoreFileKit
导入 import fs from '@ohos.file.fs' import { fs } from '@kit.CoreFileKit'
写文本 fs.writeText(path, content) fs.writeText(path, content)
读文本 fs.readText(path) fs.readText(path)
列目录 fs.listFile(path) fs.listFile(path)
删除 fs.delete(path) fs.delete(path)
判断存在 fs.exists(path) fs.exists(path)

注意:旧版部分方法名有细微差异,迁移时建议对照官方文档核对,优先使用同步方法保持简单。

六、总结

CoreFileKit 是鸿蒙本地文件操作的基础入口,新手入门记住三个关键点就够了:

  1. 先玩沙箱:私有目录随便读写,不用权限,报错最少
  2. 路径别硬编:用 context.filesDir 拼接,兼容性最好
  3. 分享转 URI:给其他应用传文件,必须走 fileUri 转换

掌握本文的基础读写、目录管理、删除操作,就能覆盖 80% 的日常文件需求;后续进阶可以再学习二进制文件读写、大文件流式处理、跨应用文件共享等能力。

Logo

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

更多推荐