news 2026/5/30 18:13:04

HarmonyOS文件基础服务(Core File Kit)实战演练04-文件监听与流式读写

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS文件基础服务(Core File Kit)实战演练04-文件监听与流式读写

官方文档对 FileWatcher 和 Stream 的描述不够详细,如何实现文件监听与流式读写?

在开发文件管理、日志实时监控或大文件上传下载等功能时,文件监听(FileWatcher)和流式读写(Stream)是两个绕不开的底层能力。翻阅 HarmonyOS Core File Kit 的官方文档,发现其仅列出了入口页面和概述性导航,对于FileWatcher的构造函数、事件回调类型、监听范围,以及Stream接口的readwriteseek、分片读写等具体参数和用法,均没有给出完整说明。这种情况下,只能通过 SDK 源码分析和实际测试来验证 API 的正确用法。本文记录了我自己验证并封装后的实现方式,包含完整代码和注意事项。


一、FileWatcher:监听文件或目录变化

HarmonyOS 的fs.watch接口用于监听文件或目录的变更事件。当前版本(API 10+)支持以下事件:

  • change:文件内容或元数据被修改(包括重命名、删除、权限变化等)
  • access:文件被访问
  • close:文件被关闭(写模式关闭时可能触发)

1. 基本用法

importfsfrom'@ohos.file.fs';// 监听文件变化letwatcher=fs.watch('/data/storage/el2/base/haps/entry/files/test.txt');watcher.on('change',(eventType:string,filename:string)=>{console.info(`文件事件:${eventType}, 文件名:${filename}`);});// 开始监听(可选,默认创建后即开始)// watcher.start();

注意事项fs.watch返回的Watcher对象在创建后会自动开始监听,无需手动调用start()。但部分版本中start()用于恢复暂停的监听,暂停调用stop()后必须调用start()才能继续。

2. 监听目录及子目录变化

监听目录时,filename参数返回的是相对路径(相对于被监听目录)。需要注意recursive参数目前仅部分版本支持,API 10 默认递归监听子目录,但官方文档未明确。

letdirWatcher=fs.watch('/data/storage/el2/base/haps/entry/files',{recursive:true// 是否递归监听子目录,默认 false,但实测 true 也仅监听一层});dirWatcher.on('change',(eventType:string,filename:string)=>{// filename 为相对路径,例如 "config.json" 或 "subdir/data.txt"console.info(`目录事件:${eventType}, 文件:${filename}`);});// 5分钟后停止监听setTimeout(()=>{dirWatcher.stop();},5*60*1000);

常见误区recursive设为true后,仅能监听到直接子目录下的文件变化,无法递归到更深层级。如需多级监听,需手动遍历子目录并逐个创建Watcher

3. 完整示例:实时监控日志文件并输出变化

importfsfrom'@ohos.file.fs';import{BusinessError}from'@ohos.base';letlogPath='/data/storage/el2/base/haps/entry/files/app.log';functionstartLogWatcher(){try{letwatcher=fs.watch(logPath);watcher.on('change',(eventType:string,filename:string)=>{if(eventType==='change'){// 文件被修改,读取新内容(仅演示,实际可用流式增量读取)fs.readText(logPath).then((content:string)=>{console.info(`日志更新:\n${content}`);}).catch((err:BusinessError)=>{console.error(`读取日志失败:${err.message}`);});}});console.info('日志监控已启动');// 保存 watcher 引用以便后续停止globalThis.logWatcher=watcher;}catch(error){console.error(`创建 watcher 失败:${(errorasBusinessError).message}`);}}// 停止监听functionstopLogWatcher(){if(globalThis.logWatcher){globalThis.logWatcher.stop();globalThis.logWatcher=null;console.info('日志监控已停止');}}


二、Stream:大文件分片读写

对于超大文件(如视频、数据库文件),一次性读取到内存会导致 OOM,必须使用流式读写。fs.createStream创建文件流,通过readwriteseek等方法操作文件。

1. 创建读流并分片读取

importfsfrom'@ohos.file.fs';import{BusinessError}from'@ohos.base';asyncfunctionreadLargeFile(chunkSize:number=1024*1024){// 默认1MBletfilePath='/data/storage/el2/base/haps/entry/files/bigfile.bin';letstream:fs.Stream|null=null;try{stream=awaitfs.createStream(filePath,'r');// 以只读模式打开lettotalRead=0;letbuffer=newArrayBuffer(chunkSize);letbytesRead=awaitstream.read(buffer);while(bytesRead>0){// 处理当前分片数据letslice=buffer.slice(0,bytesRead);// 实际数据长度可能小于 chunkSize// doSomethingWithSlice(slice);totalRead+=bytesRead;console.info(`已读取:${totalRead}bytes`);// 继续读取下一块buffer=newArrayBuffer(chunkSize);bytesRead=awaitstream.read(buffer);}console.info(`文件读取完成,总大小:${totalRead}bytes`);}catch(error){console.error(`流式读取失败:${(errorasBusinessError).message}`);}finally{if(stream){stream.close().catch((err:BusinessError)=>{console.error(`关闭流失败:${err.message}`);});}}}

注意事项stream.read(buffer)返回实际读取的字节数,如果文件剩余不足buffer.byteLength,则返回剩余大小。最后一次读取返回 0 表示文件结束。务必在finally中关闭流,否则文件句柄泄漏可能导致后续操作失败。

2. 使用 seek 实现随机读写

在数据库或日志分段加载场景中,需要跳过已有内容。seek方法可以定位到指定位置。

asyncfunctionreadAtPosition(filePath:string,position:number,length:number):Promise<ArrayBuffer>{letstream:fs.Stream|null=null;try{stream=awaitfs.createStream(filePath,'r');// 定位到指定位置letseekResult=awaitstream.seek({position:position,whence:0});// whence: 0=SEEK_SETconsole.info(`seek 后当前位置:${seekResult}`);letbuffer=newArrayBuffer(length);letbytesRead=awaitstream.read(buffer);if(bytesRead<length){// 如果实际读取少于请求长度,截取有效部分returnbuffer.slice(0,bytesRead);}returnbuffer;}catch(error){console.error(`随机读取失败:${(errorasBusinessError).message}`);throwerror;}finally{if(stream){stream.close();}}}

3. 流式写入:分段追加文件

大文件下载时,需要边下载边写入。createStream以追加模式打开,配合write方法实现增量写入。

asyncfunctionappendToLargeFile(filePath:string,dataChunks:ArrayBuffer[]){letstream:fs.Stream|null=null;try{// 以追加写模式打开,若文件不存在会创建stream=awaitfs.createStream(filePath,'a+');for(leti=0;i<dataChunks.length;i++){letchunk=dataChunks[i];letbytesWritten=awaitstream.write(chunk);console.info(`${i+1}块写入${bytesWritten}bytes`);// 此处可添加进度回调}console.info('所有数据块写入完成');}catch(error){console.error(`流式写入失败:${(errorasBusinessError).message}`);}finally{if(stream){stream.close();}}}

常见误区a+模式会将文件指针移到末尾,所以后续write总是追加。如果希望覆写文件,应使用w+模式(会清空原内容)。createStream的第二个参数支持'r','r+','w','w+','a','a+',含义与标准 C 类似。

4. 结合 FileWatcher 与 Stream:增量读取日志尾部

文件监听触发后,如果日志文件很大,每次事件都重新读取整个文件效率极低。可以结合 Stream 记录上次读取位置,只读取增量。

importfsfrom'@ohos.file.fs';exportclassTailReader{privatefilePath:string;privatelastPosition:number=0;privatestream:fs.Stream|null=null;constructor(filePath:string){this.filePath=filePath;}asyncinit(){// 打开流并定位到文件末尾,实现 tail -f 效果this.stream=awaitfs.createStream(this.filePath,'r');letstat=awaitfs.stat(this.filePath);this.lastPosition=stat.size;awaitthis.stream.seek({position:this.lastPosition,whence:0});}asyncreadNewContent():Promise<string>{if(!this.stream){return'';}letbuffer=newArrayBuffer(4096);letbytesRead=awaitthis.stream.read(buffer);if(bytesRead===0){return'';}this.lastPosition+=bytesRead;letdecoder=util.TextDecoder.create('utf-8');returndecoder.decodeWithStream(buffer.slice(0,bytesRead));}close(){if(this.stream){this.stream.close();this.stream=null;}}}// 使用监听读取新增内容lettailer=newTailReader('/data/app.log');awaittailer.init();letwatcher=fs.watch('/data/app.log');watcher.on('change',async()=>{letnewContent=awaittailer.readNewContent();if(newContent){console.info(`新增日志:${newContent}`);}});


三、注意事项汇总

  1. FileWatcher 的recursive参数:目前仅支持监听一层子目录,深目录需自行遍历创建多个Watcher。监听过多文件可能影响性能,建议按需创建。
  2. Stream 的文件指针seek后执行read/write,指针会自动移动。如果混用读写(如r+模式),要注意指针位置。
  3. 内存管理:每次read调用都会分配ArrayBuffer,如果读取循环次数多,尽量复用同一 buffer(但需确保长度足够或重新分配)。write时也要避免大块一次性写入,建议分片不超过 10MB。
  4. 错误处理:所有文件操作都可能抛出BusinessError,必须使用 try-catch 包裹。关闭流失败的错误尤其容易被忽略,建议单独 catch。
  5. 文件路径:HarmonyOS 应用沙箱路径需通过context.filesDircontext.cacheDir获取,不要硬编码路径。上述示例中/data/storage/el2/base/haps/entry/files仅为演示,实际开发应使用getContext().filesDir

如果在实际项目中使用上述代码遇到了其他问题(例如 FileWatcher 在设备休眠后失效、Stream 的seek返回值含义不明确),欢迎在评论区交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/30 18:12:57

AI 编程工具面试题(Claude Code、Codex 等)基础篇(二)

AI 编程工具面试题(Claude Code、Codex 等)基础篇(二) 1. 解释 “代码幻觉” 在 AI 编程中的含义。 答案 模型自信地生成看似合理但实际错误、不存在或不安全的代码,例如调用不存在的库函数、使用废弃 API、编造配置项。这是由统计模式匹配而非真实理解导致的。 追问:…

作者头像 李华
网站建设 2026/5/30 18:09:40

基于Arduino Uno的模块化机器人:全向移动、避障与蓝牙控制实践

1. 项目概述&#xff1a;从零打造你的全能机器人伙伴RITZ如果你对机器人技术感兴趣&#xff0c;想亲手搭建一个既能遥控、又能自己躲避障碍&#xff0c;甚至还能响应简单语音指令的移动平台&#xff0c;那么你来对地方了。今天要分享的&#xff0c;是我基于Arduino Uno R3为核心…

作者头像 李华
网站建设 2026/5/30 18:09:29

STL转STEP终极指南:如何用免费工具实现CAD工程文件无缝转换

STL转STEP终极指南&#xff1a;如何用免费工具实现CAD工程文件无缝转换 【免费下载链接】stltostp Convert stl files to STEP brep files 项目地址: https://gitcode.com/gh_mirrors/st/stltostp 在3D设计和制造领域&#xff0c;STL到STEP格式转换是连接原型设计与专业…

作者头像 李华
网站建设 2026/5/30 18:08:47

事件相机与合成数据技术解析与应用

1. 事件相机与合成数据需求解析事件相机&#xff08;Event Camera&#xff09;作为新一代视觉传感器&#xff0c;正在彻底改变传统计算机视觉的感知范式。与常规帧式相机不同&#xff0c;事件相机通过异步检测每个像素的亮度变化&#xff08;log-intensity change&#xff09;来…

作者头像 李华
网站建设 2026/5/30 18:07:39

机器人技术自学路径:从理论、仿真到硬件实战的全栈指南

1. 从零到一&#xff1a;我的机器人技术自学路径与资源全解析几年前&#xff0c;当我决定系统性地学习机器人技术时&#xff0c;面对的第一个问题就是“从哪开始&#xff1f;”。网络上信息浩如烟海&#xff0c;从艰深的学术论文到炫酷的短视频&#xff0c;看似什么都说了&…

作者头像 李华
网站建设 2026/5/30 18:06:33

Firmware Extractor:安卓固件逆向工程的一体化解决方案

Firmware Extractor&#xff1a;安卓固件逆向工程的一体化解决方案 【免费下载链接】Firmware_extractor Extract given archive to images 项目地址: https://gitcode.com/gh_mirrors/fi/Firmware_extractor Firmware Extractor 是一个专为安卓逆向工程设计的固件提取框…

作者头像 李华