文件读写从文本到二进制数据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.readText和writeText这类封装好的方法能减少不少错误处理代码。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机核心实现文本读写先从最常见的文本文件开始。这段代码用于向沙箱目录写入一段文本然后读取并打印出来。// TextRW.etsimport{fileManager}fromkit.CoreFileKit;import{common}fromkit.AbilityKit;exportclassTextRW{privatecontext:common.Context;constructor(context:common.Context){this.contextcontext;}// 写入文本文件asyncwriteText(fileName:string,content:string):Promisevoid{try{// 获取沙箱目录路径constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 写入文本自动创建文件如果不存在awaitfileManager.writeText(filePath,content);console.info(写入成功:${filePath});}catch(error){console.error(写入失败:${error.code},${error.message});throwerror;}}// 读取文本文件asyncreadText(fileName:string):Promisestring{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 读取文本constcontentawaitfileManager.readText(filePath);console.info(读取内容:${content});returncontent;}catch(error){console.error(读取失败:${error.code},${error.message});throwerror;}}}注意事项writeText会覆盖文件内容如果文件不存在则自动创建readText要求文件必须存在否则会抛出错误沙箱路径建议使用cacheDir或tempDir避免持久化数据累积核心实现二进制数据读写实际项目中文件读写不仅是文本更多时候是图片、音频等二进制数据。// BinaryRW.etsimport{fileManager}fromkit.CoreFileKit;import{common}fromkit.AbilityKit;exportclassBinaryRW{privatecontext:common.Context;constructor(context:common.Context){this.contextcontext;}// 写入二进制数据如图片字节asyncwriteArrayBuffer(fileName:string,buffer:ArrayBuffer):Promisevoid{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 写入二进制数据awaitfileManager.writeArrayBuffer(filePath,buffer);console.info(二进制写入成功:${filePath});}catch(error){console.error(二进制写入失败:${error.code},${error.message});throwerror;}}// 读取二进制数据并验证asyncreadAndVerifyArrayBuffer(fileName:string,originalBuffer:ArrayBuffer):Promiseboolean{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 读取二进制数据constreadBufferawaitfileManager.readArrayBuffer(filePath);// 验证数据一致性constoriginalViewnewUint8Array(originalBuffer);constreadViewnewUint8Array(readBuffer);if(originalView.length!readView.length){console.warn(数据长度不一致);returnfalse;}for(leti0;ioriginalView.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作为参数因为writeArrayBuffer和readArrayBuffer原生支持ArrayBuffer验证环节使用Uint8Array逐字节比较避免引用比较陷阱异步回调里处理所有异常防止未捕获错误导致崩溃核心实现流式读取大文件对于大文件一次性读取会撑爆内存。Core File Kit 提供了流式接口// StreamRW.etsimport{fileManager}fromkit.CoreFileKit;import{common}fromkit.AbilityKit;exportclassStreamRW{privatecontext:common.Context;constructor(context:common.Context){this.contextcontext;}// 流式写入大文件asyncwriteLargeFile(fileName:string,data:ArrayBuffer):Promisevoid{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 创建可写流conststreamawaitfileManager.createWriteStream(filePath);// 分段写入constchunkSize1024*1024;// 1MBletoffset0;while(offsetdata.byteLength){constendMath.min(offsetchunkSize,data.byteLength);constchunkdata.slice(offset,end);awaitstream.write(chunk);offsetend;}// 关闭流awaitstream.close();console.info(流式写入完成);}catch(error){console.error(流式写入失败:${error.code},${error.message});throwerror;}}// 流式读取大文件asyncreadLargeFile(fileName:string,onChunk:(chunk:ArrayBuffer)void):Promisevoid{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 创建可读流conststreamawaitfileManager.createReadStream(filePath);// 分段读取constchunkSize1024*1024;// 1MBletreadBuffer:ArrayBuffer;do{readBufferawaitstream.read(chunkSize);if(readBuffer.byteLength0){onChunk(readBuffer);}}while(readBuffer.byteLength0);// 关闭流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.filesDir和this.context.cacheDir或者手动拼接路径时用了错误的根目录。解决方案// 错误方式constwrongPath/data/storage/el2/base/haps/entry/cache/file.txt;// 正确方式constcorrectPaththis.context.cacheDir/file.txt;常见问题 2读取大文件导致 OOM现象读取超过 100MB 的文件时应用直接闪退。原因使用了readArrayBuffer一次性读取整个文件内存撑爆。解决方案改用流式读取分段处理。如果确实需要全部数据可以// 分段拼接constchunks:ArrayBuffer[][];conststreamawaitfileManager.createReadStream(filePath);letchunk:ArrayBuffer;while((chunkawaitstream.read(1024*1024)).byteLength0){chunks.push(chunk);}awaitstream.close();// 合并consttotalLengthchunks.reduce((sum,c)sumc.byteLength,0);constfullBuffernewArrayBuffer(totalLength);constfullViewnewUint8Array(fullBuffer);letoffset0;for(constcofchunks){fullView.set(newUint8Array(c),offset);offsetc.byteLength;}最佳实践统一管理文件路径不要到处写路径拼接封装一个工具类集中管理沙箱路径避免路径错误散落在各个模块中。优先使用流式接口即使当前文件不大也要养成用流的习惯。线上用户上传的文件大小不可控流式接口是防 OOM 的第一道防线。写后立即读校验特别是二进制数据写入后建议立即读取并逐字节校验防止写入过程中因为系统异常导致数据损坏。这点在处理图片、配置文件时尤其重要。Demo 入口// Index.etsimport{TextRW}from./TextRW;import{BinaryRW}from./BinaryRW;import{common}fromkit.AbilityKit;EntryComponentstruct Index{build(){Row(){Column(){Button(写入文本).onClick(async(){constcontextgetContext(this)ascommon.Context;consttextRWnewTextRW(context);awaittextRW.writeText(test.txt,Hello HarmonyOS);constcontentawaittextRW.readText(test.txt);console.info(读取结果: content);})Button(写入二进制).onClick(async(){constcontextgetContext(this)ascommon.Context;constbinaryRWnewBinaryRW(context);constbuffernewArrayBuffer(1024);constviewnewUint8Array(buffer);view.fill(0x41);// 填充 AawaitbinaryRW.writeArrayBuffer(test.bin,buffer);constresultawaitbinaryRW.readAndVerifyArrayBuffer(test.bin,buffer);console.info(验证结果: result);})}.width(100%)}.height(100%)}}FAQQ为什么写入后立即读取会返回空字符串A检查是否在写入完成前就开始读取。writeText和readText都是异步操作必须await确保顺序执行。Q读取文件时为什么有时候能读到内容有时候读不到A最常见原因是文件路径写死了没有根据沙箱环境动态获取。建议每次都通过this.context.cacheDir拼接路径而不是硬编码。QwriteArrayBuffer写入后读取的 ArrayBuffer 大小不一致A检查是否在写入过程中有其他线程修改了文件或者磁盘空间不足导致写入不完整。建议在写入后立即读取校验。示例代码地址项目地址
《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.readText和writeText这类封装好的方法能减少不少错误处理代码。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机核心实现文本读写先从最常见的文本文件开始。这段代码用于向沙箱目录写入一段文本然后读取并打印出来。// TextRW.etsimport{fileManager}fromkit.CoreFileKit;import{common}fromkit.AbilityKit;exportclassTextRW{privatecontext:common.Context;constructor(context:common.Context){this.contextcontext;}// 写入文本文件asyncwriteText(fileName:string,content:string):Promisevoid{try{// 获取沙箱目录路径constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 写入文本自动创建文件如果不存在awaitfileManager.writeText(filePath,content);console.info(写入成功:${filePath});}catch(error){console.error(写入失败:${error.code},${error.message});throwerror;}}// 读取文本文件asyncreadText(fileName:string):Promisestring{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 读取文本constcontentawaitfileManager.readText(filePath);console.info(读取内容:${content});returncontent;}catch(error){console.error(读取失败:${error.code},${error.message});throwerror;}}}注意事项writeText会覆盖文件内容如果文件不存在则自动创建readText要求文件必须存在否则会抛出错误沙箱路径建议使用cacheDir或tempDir避免持久化数据累积核心实现二进制数据读写实际项目中文件读写不仅是文本更多时候是图片、音频等二进制数据。// BinaryRW.etsimport{fileManager}fromkit.CoreFileKit;import{common}fromkit.AbilityKit;exportclassBinaryRW{privatecontext:common.Context;constructor(context:common.Context){this.contextcontext;}// 写入二进制数据如图片字节asyncwriteArrayBuffer(fileName:string,buffer:ArrayBuffer):Promisevoid{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 写入二进制数据awaitfileManager.writeArrayBuffer(filePath,buffer);console.info(二进制写入成功:${filePath});}catch(error){console.error(二进制写入失败:${error.code},${error.message});throwerror;}}// 读取二进制数据并验证asyncreadAndVerifyArrayBuffer(fileName:string,originalBuffer:ArrayBuffer):Promiseboolean{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 读取二进制数据constreadBufferawaitfileManager.readArrayBuffer(filePath);// 验证数据一致性constoriginalViewnewUint8Array(originalBuffer);constreadViewnewUint8Array(readBuffer);if(originalView.length!readView.length){console.warn(数据长度不一致);returnfalse;}for(leti0;ioriginalView.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作为参数因为writeArrayBuffer和readArrayBuffer原生支持ArrayBuffer验证环节使用Uint8Array逐字节比较避免引用比较陷阱异步回调里处理所有异常防止未捕获错误导致崩溃核心实现流式读取大文件对于大文件一次性读取会撑爆内存。Core File Kit 提供了流式接口// StreamRW.etsimport{fileManager}fromkit.CoreFileKit;import{common}fromkit.AbilityKit;exportclassStreamRW{privatecontext:common.Context;constructor(context:common.Context){this.contextcontext;}// 流式写入大文件asyncwriteLargeFile(fileName:string,data:ArrayBuffer):Promisevoid{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 创建可写流conststreamawaitfileManager.createWriteStream(filePath);// 分段写入constchunkSize1024*1024;// 1MBletoffset0;while(offsetdata.byteLength){constendMath.min(offsetchunkSize,data.byteLength);constchunkdata.slice(offset,end);awaitstream.write(chunk);offsetend;}// 关闭流awaitstream.close();console.info(流式写入完成);}catch(error){console.error(流式写入失败:${error.code},${error.message});throwerror;}}// 流式读取大文件asyncreadLargeFile(fileName:string,onChunk:(chunk:ArrayBuffer)void):Promisevoid{try{constsandboxPaththis.context.cacheDir;constfilePathsandboxPath/fileName;// 创建可读流conststreamawaitfileManager.createReadStream(filePath);// 分段读取constchunkSize1024*1024;// 1MBletreadBuffer:ArrayBuffer;do{readBufferawaitstream.read(chunkSize);if(readBuffer.byteLength0){onChunk(readBuffer);}}while(readBuffer.byteLength0);// 关闭流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.filesDir和this.context.cacheDir或者手动拼接路径时用了错误的根目录。解决方案// 错误方式constwrongPath/data/storage/el2/base/haps/entry/cache/file.txt;// 正确方式constcorrectPaththis.context.cacheDir/file.txt;常见问题 2读取大文件导致 OOM现象读取超过 100MB 的文件时应用直接闪退。原因使用了readArrayBuffer一次性读取整个文件内存撑爆。解决方案改用流式读取分段处理。如果确实需要全部数据可以// 分段拼接constchunks:ArrayBuffer[][];conststreamawaitfileManager.createReadStream(filePath);letchunk:ArrayBuffer;while((chunkawaitstream.read(1024*1024)).byteLength0){chunks.push(chunk);}awaitstream.close();// 合并consttotalLengthchunks.reduce((sum,c)sumc.byteLength,0);constfullBuffernewArrayBuffer(totalLength);constfullViewnewUint8Array(fullBuffer);letoffset0;for(constcofchunks){fullView.set(newUint8Array(c),offset);offsetc.byteLength;}最佳实践统一管理文件路径不要到处写路径拼接封装一个工具类集中管理沙箱路径避免路径错误散落在各个模块中。优先使用流式接口即使当前文件不大也要养成用流的习惯。线上用户上传的文件大小不可控流式接口是防 OOM 的第一道防线。写后立即读校验特别是二进制数据写入后建议立即读取并逐字节校验防止写入过程中因为系统异常导致数据损坏。这点在处理图片、配置文件时尤其重要。Demo 入口// Index.etsimport{TextRW}from./TextRW;import{BinaryRW}from./BinaryRW;import{common}fromkit.AbilityKit;EntryComponentstruct Index{build(){Row(){Column(){Button(写入文本).onClick(async(){constcontextgetContext(this)ascommon.Context;consttextRWnewTextRW(context);awaittextRW.writeText(test.txt,Hello HarmonyOS);constcontentawaittextRW.readText(test.txt);console.info(读取结果: content);})Button(写入二进制).onClick(async(){constcontextgetContext(this)ascommon.Context;constbinaryRWnewBinaryRW(context);constbuffernewArrayBuffer(1024);constviewnewUint8Array(buffer);view.fill(0x41);// 填充 AawaitbinaryRW.writeArrayBuffer(test.bin,buffer);constresultawaitbinaryRW.readAndVerifyArrayBuffer(test.bin,buffer);console.info(验证结果: result);})}.width(100%)}.height(100%)}}FAQQ为什么写入后立即读取会返回空字符串A检查是否在写入完成前就开始读取。writeText和readText都是异步操作必须await确保顺序执行。Q读取文件时为什么有时候能读到内容有时候读不到A最常见原因是文件路径写死了没有根据沙箱环境动态获取。建议每次都通过this.context.cacheDir拼接路径而不是硬编码。QwriteArrayBuffer写入后读取的 ArrayBuffer 大小不一致A检查是否在写入过程中有其他线程修改了文件或者磁盘空间不足导致写入不完整。建议在写入后立即读取校验。示例代码地址项目地址