stream和fs.open的区别、优势:
四种流的使用场景:
场景描述
通过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)
```
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。