当前位置: 首页 > news >正文

《HarmonyOS技术精讲-Core File Kit》第3篇:文件读写——从文本到二进制数据

文件读写:从文本到二进制数据

HarmonyOS NEXT 开发里,文件读写这个 API 经常被误用。很多人第一次接触 Core File Kit 时,会发现官方示例能运行,但实际项目里总会出现各种意外——要么文件路径不对,要么写进去的内容读不出来,要么读取大文件直接卡死。

这个功能本身不复杂,但真正麻烦的是沙箱路径的理解和流式读取的处理。这篇文章会从文本和二进制数据两个场景入手,把标准操作和常见问题一次说清楚。

Core File Kit 解决了什么问题

在 HarmonyOS 应用开发中,所有文件操作都基于沙箱机制。应用无法随意访问系统目录,只能操作自己的沙箱目录下的文件。Core File Kit 提供了统一的文件读写能力,底层封装了沙箱路径解析和文件描述符管理。

跟直接用fs模块相比,Core File Kit 的几个关键差异:

能力fs 模块Core File Kit
沙箱路径需手动拼接自动解析
流式读取需手动创建流内置流式接口
二进制支持需 ArrayBuffer直接支持
异步异常处理基础更完善

实际开发中,推荐优先使用 Core File Kit 提供的高阶 API,特别是fileManager.readTextwriteText这类封装好的方法,能减少不少错误处理代码。

环境说明

DevEco Studio 版本:DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上 目标设备:手机

核心实现:文本读写

先从最常见的文本文件开始。这段代码用于向沙箱目录写入一段文本,然后读取并打印出来。

// TextRW.etsimport{fileManager}from'@kit.CoreFileKit';import{common}from'@kit.AbilityKit';exportclassTextRW{privatecontext:common.Context;constructor(context:common.Context){this.context=context;}// 写入文本文件asyncwriteText(fileName:string,content:string):Promise<void>{try{// 获取沙箱目录路径constsandboxPath=this.context.cacheDir;constfilePath=sandboxPath+'/'+fileName;// 写入文本(自动创建文件,如果不存在)awaitfileManager.writeText(filePath,content);console.info(`写入成功:${filePath}`);}catch(error){console.error(`写入失败:${error.code},${error.message}`);throwerror;}}// 读取文本文件asyncreadText(fileName:string):Promise<string>{try{constsandboxPath=this.context.cacheDir;constfilePath=sandboxPath+'/'+fileName;// 读取文本constcontent=awaitfileManager.readText(filePath);console.info(`读取内容:${content}`);returncontent;}catch(error){console.error(`读取失败:${error.code},${error.message}`);throwerror;}}}

注意事项

  • writeText会覆盖文件内容,如果文件不存在则自动创建
  • readText要求文件必须存在,否则会抛出错误
  • 沙箱路径建议使用cacheDirtempDir,避免持久化数据累积

核心实现:二进制数据读写

实际项目中,文件读写不仅是文本,更多时候是图片、音频等二进制数据。

// BinaryRW.etsimport{fileManager}from'@kit.CoreFileKit';import{common}from'@kit.AbilityKit';exportclassBinaryRW{privatecontext:common.Context;constructor(context:common.Context){this.context=context;}// 写入二进制数据(如图片字节)asyncwriteArrayBuffer(fileName:string,buffer:ArrayBuffer):Promise<void>{try{constsandboxPath=this.context.cacheDir;constfilePath=sandboxPath+'/'+fileName;// 写入二进制数据awaitfileManager.writeArrayBuffer(filePath,buffer);console.info(`二进制写入成功:${filePath}`);}catch(error){console.error(`二进制写入失败:${error.code},${error.message}`);throwerror;}}// 读取二进制数据并验证asyncreadAndVerifyArrayBuffer(fileName:string,originalBuffer:ArrayBuffer):Promise<boolean>{try{constsandboxPath=this.context.cacheDir;constfilePath=sandboxPath+'/'+fileName;// 读取二进制数据constreadBuffer=awaitfileManager.readArrayBuffer(filePath);// 验证数据一致性constoriginalView=newUint8Array(originalBuffer);constreadView=newUint8Array(readBuffer);if(originalView.length!==readView.length){console.warn('数据长度不一致');returnfalse;}for(leti=0;i<originalView.length;i++){if(originalView[i]!==readView[i]){console.warn(`数据不一致: 位置${i}`);returnfalse;}}console.info('数据验证通过');returntrue;}catch(error){console.error(`读取验证失败:${error.code},${error.message}`);returnfalse;}}}

为什么这样写更稳定

  • 使用ArrayBuffer而不是Uint8Array作为参数,因为writeArrayBufferreadArrayBuffer原生支持ArrayBuffer
  • 验证环节使用Uint8Array逐字节比较,避免引用比较陷阱
  • 异步回调里处理所有异常,防止未捕获错误导致崩溃

核心实现:流式读取大文件

对于大文件,一次性读取会撑爆内存。Core File Kit 提供了流式接口:

// StreamRW.etsimport{fileManager}from'@kit.CoreFileKit';import{common}from'@kit.AbilityKit';exportclassStreamRW{privatecontext:common.Context;constructor(context:common.Context){this.context=context;}// 流式写入大文件asyncwriteLargeFile(fileName:string,data:ArrayBuffer):Promise<void>{try{constsandboxPath=this.context.cacheDir;constfilePath=sandboxPath+'/'+fileName;// 创建可写流conststream=awaitfileManager.createWriteStream(filePath);// 分段写入constchunkSize=1024*1024;// 1MBletoffset=0;while(offset<data.byteLength){constend=Math.min(offset+chunkSize,data.byteLength);constchunk=data.slice(offset,end);awaitstream.write(chunk);offset=end;}// 关闭流awaitstream.close();console.info('流式写入完成');}catch(error){console.error(`流式写入失败:${error.code},${error.message}`);throwerror;}}// 流式读取大文件asyncreadLargeFile(fileName:string,onChunk:(chunk:ArrayBuffer)=>void):Promise<void>{try{constsandboxPath=this.context.cacheDir;constfilePath=sandboxPath+'/'+fileName;// 创建可读流conststream=awaitfileManager.createReadStream(filePath);// 分段读取constchunkSize=1024*1024;// 1MBletreadBuffer:ArrayBuffer;do{readBuffer=awaitstream.read(chunkSize);if(readBuffer.byteLength>0){onChunk(readBuffer);}}while(readBuffer.byteLength>0);// 关闭流awaitstream.close();console.info('流式读取完成');}catch(error){console.error(`流式读取失败:${error.code},${error.message}`);throwerror;}}}

流式读写要点

  • createWriteStream返回的流会自动处理文件创建和写入
  • read方法返回的ArrayBuffer长度可能小于请求的大小,这是正常行为
  • 必须手动调用close()释放资源,否则会触发系统警告

常见问题 1:沙箱路径错误

现象:写入文件后,在指定路径找不到文件,或读取时报错 “No such file or directory”。

原因:开发者混用了this.context.filesDirthis.context.cacheDir,或者手动拼接路径时用了错误的根目录。

解决方案

// 错误方式constwrongPath='/data/storage/el2/base/haps/entry/cache/file.txt';// 正确方式constcorrectPath=this.context.cacheDir+'/file.txt';

常见问题 2:读取大文件导致 OOM

现象:读取超过 100MB 的文件时,应用直接闪退。

原因:使用了readArrayBuffer一次性读取整个文件,内存撑爆。

解决方案:改用流式读取,分段处理。如果确实需要全部数据,可以:

// 分段拼接constchunks:ArrayBuffer[]=[];conststream=awaitfileManager.createReadStream(filePath);letchunk:ArrayBuffer;while((chunk=awaitstream.read(1024*1024)).byteLength>0){chunks.push(chunk);}awaitstream.close();// 合并consttotalLength=chunks.reduce((sum,c)=>sum+c.byteLength,0);constfullBuffer=newArrayBuffer(totalLength);constfullView=newUint8Array(fullBuffer);letoffset=0;for(constcofchunks){fullView.set(newUint8Array(c),offset);offset+=c.byteLength;}

最佳实践

  1. 统一管理文件路径:不要到处写路径拼接,封装一个工具类集中管理沙箱路径,避免路径错误散落在各个模块中。

  2. 优先使用流式接口:即使当前文件不大,也要养成用流的习惯。线上用户上传的文件大小不可控,流式接口是防 OOM 的第一道防线。

  3. 写后立即读校验:特别是二进制数据写入后,建议立即读取并逐字节校验,防止写入过程中因为系统异常导致数据损坏。这点在处理图片、配置文件时尤其重要。

Demo 入口

// Index.etsimport{TextRW}from'./TextRW';import{BinaryRW}from'./BinaryRW';import{common}from'@kit.AbilityKit';@Entry@Componentstruct Index{build(){Row(){Column(){Button('写入文本').onClick(async()=>{constcontext=getContext(this)ascommon.Context;consttextRW=newTextRW(context);awaittextRW.writeText('test.txt','Hello HarmonyOS');constcontent=awaittextRW.readText('test.txt');console.info('读取结果: '+content);})Button('写入二进制').onClick(async()=>{constcontext=getContext(this)ascommon.Context;constbinaryRW=newBinaryRW(context);constbuffer=newArrayBuffer(1024);constview=newUint8Array(buffer);view.fill(0x41);// 填充 'A'awaitbinaryRW.writeArrayBuffer('test.bin',buffer);constresult=awaitbinaryRW.readAndVerifyArrayBuffer('test.bin',buffer);console.info('验证结果: '+result);})}.width('100%')}.height('100%')}}

FAQ

Q:为什么写入后立即读取会返回空字符串?
A:检查是否在写入完成前就开始读取。writeTextreadText都是异步操作,必须await确保顺序执行。

Q:读取文件时,为什么有时候能读到内容,有时候读不到?
A:最常见原因是文件路径写死了,没有根据沙箱环境动态获取。建议每次都通过this.context.cacheDir拼接路径,而不是硬编码。

Q:writeArrayBuffer写入后,读取的 ArrayBuffer 大小不一致?
A:检查是否在写入过程中有其他线程修改了文件,或者磁盘空间不足导致写入不完整。建议在写入后立即读取校验。

示例代码地址:项目地址

http://www.gsyq.cn/news/1615303.html

相关文章:

  • 双开钢制防火门五金配置、闭门器联动调试技术规范
  • 别被‘大功率’带偏,真正该看的是污水泵的过流能力与密封设计
  • 储能BMS微控制器选型难题怎么破:2026五大主流方案专业解析
  • SLO2016与STM32F446RE硬件协同设计与优化实践
  • Liquibase 入门指南:数据库版本控制的最佳实践
  • STM32与LV30模组打造高效低功耗条码识别系统
  • 万物沙石厂管理系统、万物水泥厂管理系统 重构建材全流程管理
  • 3步搞定音乐文件解锁:让加密音乐在任何设备自由播放
  • LangGraph核心揭秘:让AI「想一步、停一步、判断一步」的大模型学习之旅(收藏版)
  • Unity Profiler连接抖音开发者工具
  • 《HarmonyOS技术精讲-Core File Kit》第4篇:目录操作与文件遍历
  • EM3080-W与PIC18F67K40的条形码识别系统设计
  • NcmpGui专业工具:高效解锁网易云音乐NCM格式的终极解决方案
  • 【深度指南】5大核心模块:全面掌握AMD Ryzen硬件调试工具SMUDebugTool
  • STM32CUBEMX没有配置sys导致的问题
  • Sunshine游戏串流服务器终极指南:免费打造个人专属云游戏平台
  • Outfit字体完全指南:9种字重免费开源几何无衬线字体的专业使用教程
  • 王二明配方茶商城小程序开发指南
  • 75.可直接运行!CODESYS/TwinCAT 通用 ST 物料分拣源码|标准四状态机架构
  • 掌握Microsoft Orleans状态管理:从持久化配置到事务处理
  • 5个Nucleus Co-op分屏技巧:让单机游戏变多人派对
  • WiFi热图工具终极指南:3步解决家庭网络信号盲区问题
  • 求职季,还在四处到处找面试题?快来试试这款程序员面试口袋书吧✨(前一百名自动升级pro)
  • 2026深度实测:个人AI编程软件选型推荐
  • 74HC32与MKV42F64VLH16构建2x2键盘控制系统
  • 遗传算法实战:N皇后问题的工程化求解与性能优化
  • 解放双手的革命性方案:MAA明日方舟智能自动化助手深度解析
  • ChatLog:三分钟解锁QQ群聊天记录的终极数据分析工具
  • Sunshine游戏串流服务器:打造你的终极跨平台游戏娱乐系统
  • 【大语言模型】一文彻底搞懂大模型显存占用机制:推理、训练与典型场景的量化估算