stream和fs.open的区别、优势:
stream | fs.open | |
---|---|---|
简介 | 用来读取、写入、处理和传输数据,它们通常用于处理大量数据或连续的数据流,例如文件、网络传输等。 | 使用文件系统模块打开一个文件,获取文件描述符(File Descriptor),以便进行文件的低级别操作,如读取、写入、关闭等。 |
特点 | - 逐块地处理数据,而不需要一次性加载整个数据集到内存中。 |
- 可以用于处理文件、网络数据、标准输入输出、HTTP请求等各种场景。
- 提供了丰富的事件和方法,例如data事件、end事件、pipe方法等,用于对数据进行流式处理和操作。 | - 打开文件是文件操作的第一步,它会返回一个文件描述符,后续的文件操作通常需要使用这个文件描述符。
- 可以对打开的文件进行底层的读写操作,这些操作更为灵活,但也需要更多的代码来管理文件描述符和数据缓冲区。 |
| 优势 | - 内存效率:使用流可以逐块地读取或写入数据,而不是一次性将整个文件加载到内存中。这使得处理大文件时能够节省内存,并且能够处理比内存更大的数据。 - 速度:流允许数据以流式方式传输,可以在数据还在传输的过程中进行处理,无需等待整个文件加载完成。这可以提高处理速度,特别是在网络传输或处理大文件时更为明显。
- 灵活性:流提供了丰富的事件和方法,可以方便地对数据进行处理、转换和管道连接,满足各种复杂的需求。
- 可组合性:可以将多个流串联或并联,构建复杂的数据处理管道,实现更复杂的数据操作和转换。 | |
| 总结 | 是一种用于处理数据的抽象接口,适合处理大量数据或连续的数据流,提供了丰富的事件和方法,用于对数据进行流式处理和操作。 | 是使用文件系统模块打开文件,获取文件描述符,以便进行底层的文件操作,需要手动管理文件描述符和数据缓冲区。 |
四种流的使用场景:
简介 | 使用场景 | |
---|---|---|
Writable(可写流) | 可写流用于将数据写入某个目标,例如文件、网络连接等。你可以通过调用write()方法向流中写入数据,并在写完所有数据后调用end()方法。 | - 将数据写入文件 |
- 发送数据到网络服务
- 保存日志信息 |
| Readable(可读流) | 可读流用于从数据源读取数据,例如文件、网络连接等。你可以监听事件(例如data事件)来获取数据块,或使用pipe()方法将数据传输到可写流或其他处理机制。 | - 从文件中读取数据 - 接收来自网络的数据
- 处理标准输入 |
| Duplex(双工流) | 双工流是既可读又可写的流。它们通常用于实现与网络协议相关的通信,因为在这些场景中,数据需要同时进行读取和写入操作。Duplex类继承Readable,支持Readable中所有的方法。 | - 网络套接字 - 实现复杂的网络协议
- 一些数据处理算法 |
| Transform(转换流) | 转换流是一种特殊类型的双工流,在读写过程中可以修改或转换数据。转换流对于需要对数据进行修改、压缩或解压缩等操作的场景非常有用。Transform类继承Duplex,支持Duplex中所有的方法。 | - 数据压缩和解压缩(如使用Zlib) - 加密和解密
- 文件转换 |
场景描述
通过web加载网址,onInterceptRequest拦截资源请求,web离线缓存的文件需要通过查看/data/storage/el2/base/cache/web/Cache目录来判断是否存在离线缓存,若存在缓存使用Context.cacheDir获取文件路径后,通过pathDir路径,再使用stream进行对文件读写。
代码实现
核心类介绍:
-
通过web加载网址,onInterceptRequest拦截资源请求, web的缓存在指定目录/data/storage/el2/base/cache/web/Cache下。
Web({ src: 'web地址', controller: this.controller }) .cacheMode(this.mode) .domStorageAccess(true) .onInterceptRequest((event) => { return this.responseweb })
-
通过Context.cacheDir获取缓存文件的目录,根据具体具体缓存文件路径将缓存对应的压缩包通过zlib.decompressFile解压缩,并将文件解压到指定路径 outFile中。
export function unZlibFile(inFile: string, outFile: string){ let options:zlib.Options = { level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION, memLevel: zlib.MemLevel.MEM_LEVEL_DEFAULT, strategy: zlib.CompressStrategy.COMPRESS_STRATEGY_DEFAULT_STRATEGY }; try { zlib.decompressFile(inFile, outFile, options, (errData) => { if (errData !== null) { console.log(`errData is errCode:${errData.code} message:${errData.message}`); } }) } catch(errData) { console.log(`errData is errCode:${errData.code} message:${errData.message}`); } }
-
通过流读取文件里的资源。
编写读写数据流,针对读写数据流添加光标设置。在ReadStream中实现doRead方法,在doRead中通过stream.read读取文件内容并移动光标位置,在执行push方法时会自动执行doRead。在WriteStream中实现doWrite方法,在doWrite中通过stream.write将数据写入流文件中并移动光标位置,在执行write方法时会自动执行doWrite方法。
export interface ReadStreamOptions { start?: number; // 光标起始位置 end?: number; // 光标结束位置 } export interface WriteStreamOptions { start?: number; // 光标起始位置 mode?: number; // 写入模式 }
通过fs.createStreamSync创建一个文件流this.stream,在doRead方法中使用this.stream.read (注:文件流的read方法,不是可读流readStream的read 方法) 从文件流中读取文件的内容,最后使用ReadStream的push方法将this.stream.read读取的数据推送到可读流缓冲区中。后续可以通过监听data事件来读取缓冲区中的内容 (注:执行push方法时系统会自动调用doRead方法)。
// 读数据流 export class ReadStream extends stream.Readable { private pathInner: string; // 文件路径 private bytesReadInner: number; // 读取长度 private offset: number; // 光标位置 private start?: number; // 光标起始位置 private end?: number; // 光标结束位置 private stream?: fs.Stream; // 数据流 constructor(path: string, options?: ReadStreamOptions) { super(); this.pathInner = path; this.bytesReadInner = 0; this.start = options?.start; this.end = options?.end; this.stream = fs.createStreamSync(this.pathInner, 'r'); this.offset = this.start ?? 0; } close() { this.stream?.close(); } //doInitialize 函数在可写流第一次使用 on 监听时被调用。 doInitialize(callback: Function) { callback(); } // 读取时设置光标位置移动,doRead 方法在数据被 push 时自动调用,而不需要用户手动调用。 doRead(size: number) { let readSize = size; if (this.end !== undefined) { if (this.offset > this.end) { this.push(null); return; } if (this.offset + readSize > this.end) { readSize = this.end - this.offset; } } let buffer = new ArrayBuffer(readSize); const off = this.offset; this.offset += readSize; // 从流文件读取数据 this.stream?.read(buffer, { offset: off, length: readSize }) .then((readOut: number) => { if (readOut > 0) { this.bytesReadInner += readOut; this.push(new Uint8Array(buffer.slice(0, readOut))); } if (readOut != readSize || readOut < size) { this.offset = this.offset - readSize + readOut; this.push(null); } }) } };
通过fs.createStreamSync创建一个文件流this.stream,在doWrite方法中使用this.stream.write (注:文件流的write方法,不是可写流 WriteStream的 write方法) 将数据写入文件流中。后续可以使用WriteStream的write方法将数据写入文件流中 (注:执行write方法时系统会自动调用doWrite方法)。
// 写数据流 export class WriteStream extends stream.Writable { private pathInner: string; // 文件路径 private bytesWrittenInner: number; // 写入长度 private offset: number; // 光标位置 private mode: string; // 写入模式 private start?: number; // 光标起始位置 private stream?: fs.Stream; // 数据流 constructor(path: string, options?: WriteStreamOptions) { super(); this.pathInner = path; this.bytesWrittenInner = 0; this.start = options?.start; this.mode = this.convertOpenMode(options?.mode); this.stream = fs.createStreamSync(this.pathInner, this.mode); this.offset = this.start ?? 0; } close() { this.stream?.close(); } //doInitialize 函数在可读流第一次使用 on 监听时被调用。 doInitialize(callback: Function) { callback(); } //doWrite 方法在数据被写出时自动调用,而不需要用户手动调用。 doWrite(chunk: string | Uint8Array, encoding: string, callback: Function) { // 将数据写入流文件 this.stream?.write(chunk, { offset: this.offset }) .then((writeIn: number) => { this.offset += writeIn; this.bytesWrittenInner += writeIn; callback(); }) .finally(() => { this.stream?.flush(); }) } // 创建文件流 fs.createStreamSync 时设置文件流写入模式,默认为 'w' convertOpenMode(mode?: number): string { let modeStr = 'w'; if (mode === undefined) { return modeStr; } if (mode & fs.OpenMode.WRITE_ONLY) { modeStr = 'w'; } if (mode & fs.OpenMode.READ_WRITE) { modeStr = 'w+'; } if ((mode & fs.OpenMode.WRITE_ONLY) && (mode & fs.OpenMode.APPEND)) { modeStr = "a" } if ((mode & fs.OpenMode.READ_WRITE) && (mode & fs.OpenMode.APPEND)) { modeStr = "a+" } return modeStr; } }
最后对文件进行读写,使用on监听error事件和finish事件,来监听读写是否出错和完成,在读取过程中通过data事件来监听数据是否更新并打印输出。
Button('文件初始化') .onClick(() => { let applicationContext = getContext().getApplicationContext(); // 获取应用上下文对象 let cacheDir = applicationContext.cacheDir; // 应用缓存文件目录 this.pathZip = cacheDir + '/web/Cache/Cache_Data/streamTest.zip' // 缓存文件路径 this.pathDir = cacheDir + '/web/Cache/Cache_Data'; FileInit(getContext(this), 'streamTest.zip', this.pathZip); }) .width(100) .height(50) Button('解压文件') .onClick(() => { try { //解压文件 unZlibFile(this.pathZip, this.pathDir); console.log('Success unzip!'); } catch (error) { let err: BusinessError = error as BusinessError; console.error("Unzip failed with error message: " + err.message + ", error code: " + err.code); AlertDialog.show({ title: '错误提示', message: '需要在/data/storage/el2/base/cache/web/Cache/Cache_Data下存在streamTest.zip', buttons: [{ value: '退出', action: () => { } }] }) } }) .width(100) .height(50) Button('创建读写数据流') .onClick(() => { let readPath: string = this.pathDir + '/streamTest.txt'; //解压后文件的路径 let writePath: string = this.pathDir + '/streamTestBack.txt'; try { readableStream = new ReadStream(readPath); writableStream = new WriteStream(writePath); console.log('Success create stream!'); } catch (error) { let err: BusinessError = error as BusinessError; console.error("Create stream failed with error message: " + err.message + ", error code: " + err.code); AlertDialog.show({ title: '错误提示', message: '文件不存在,请先初始化文件并解压', buttons: [{ value: '退出', action: () => { } }] }) } }) .width(200) .height(50) Button('读取') .onClick(() => { try { if (this.isFlag) { // 监听数据事件,如果数据跟新就会触发 readableStream.on('data', (chunk) => { readableStream.push(chunk['data'] as Uint8Array); this.readStr = JSON.stringify(chunk['data']) console.log('读取到数据:', this.readStr); this.isFlag = false; }); // 监听可读流是否出错 readableStream.on('error', () => { console.info('error event called read'); }); } readableStream.read(10); } catch (error) { let err: BusinessError = error as BusinessError; console.error("Read file failed with error message: " + err.message); AlertDialog.show({ title: '错误提示', message: '读取文件失败', buttons: [{ value: '退出', action: () => { } }] }) } }) .width(150) .height(50) Button('写入') .onClick(() => { try { // 监听可写流是否出错 writableStream.on('error', () => { console.info('Writable event test error'); }); // 监听可写流是否完成 writableStream.on('finish', () => { console.info('Writable event test finish'); }); let isWrite = writableStream.write(JSON.parse(this.readStr), 'utf8'); if (isWrite) { console.info('Write succeeded'); } } catch (error) { let err: BusinessError = error as BusinessError; console.error("Write file failed with error message: " + err.message); AlertDialog.show({ title: '错误提示', message: '写入文件失败', buttons: [{ value: '退出', action: () => { } }] }) } }) .width(150) .height(50) Text(this.readStr) .width(200) .height(50)
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下:
内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!
鸿蒙【北向应用开发+南向系统层开发】文档
鸿蒙【基础+实战项目】视频
鸿蒙面经
为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!