Node.js Streams:你须要知道的一切

Node.js Streams:你须要知道的一切

图像来源html

Node.js流以难以使用而闻名,甚至更难理解。好吧,我有个好消息 - 再也不是这样了。node

多年来,开发人员在那里建立了许多软件包,其惟一目的是简化流程。但在本文中,我将重点介绍本机Node.js流APIlinux

“Streams是Node最好,也是最容易被误解的想法。”
- Dominic Tarr

什么是溪流?

流是数据的集合 - 就像数组或字符串同样。不一样之处在于流可能没法一次所有可用,而且它们没必要适合内存。这使得流真正强大的大量数据,或者数据这是一个从外部来源有人来工做时,大块的时间。api

可是,流不只仅是处理大数据。它们还为咱们提供了代码中可组合性的强大功能。就像咱们能够经过管道其余较小的Linux命令来组成强大的linux命令同样,咱们能够在Node中使用流彻底相同。数组

与Linux命令的可组合性服务器

const wc = ... //用于wc输入的流const grep = ... //用于grep输出的流
const wc = ... //用于wc输入的流
grep.pipe(WC)

Node中的许多内置模块实现了流接口:异步

从个人Pluralsight课程 - Advanced Node.js中捕获的截图ide

上面的列表提供了一些本机Node.js对象的示例,这些对象也是可读写的流。其中一些对象是可读写的流,如TCP套接字,zlib和加密流。函数

请注意,对象也是密切相关的。虽然HTTP响应是客户端上的可读流,但它是服务器上的可写流。这是由于在HTTP状况下,咱们基本上从一个对象(http.IncomingMessage)读取并写入另外一个(http.ServerResponse)。学习

另外要注意的是如何stdio流(stdinstdoutstderr)有反流类型,当涉及到的子进程。这容许一种很是简单的方法来管理来自主流程stdio流的这些流。

一个实际的例子

理论很棒,但每每不是100%使人信服。让咱们看一个示例,演示在内存消耗方面流能够在代码中产生的差别。

让咱们先建立一个大文件:

const file = fs.createWriteStream('./ big.file'); 

for(let i = 0; i <= 1e6; i ++){ 
  file.write('Lorem ipsum dolor sit amet,consectetur adipisicing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam,quis nostrud练习ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in repreptderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur。Excepteur sint occaecat cupidatat non proident,sunt in culpa qui officia deserunt mollit anim id est laborum。\ n' ); 
} 

file.end();const fs = require('fs'); 
const file = fs.createWriteStream('./ big.file'); 

for(let i = 0; i <= 1e6; i ++){ 
  file.write('Lorem ipsum dolor sit amet,consectetur adipisicing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam,quis nostrud练习ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in repreptderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur。Excepteur sint occaecat cupidatat non proident,sunt in culpa qui officia deserunt mollit anim id est laborum。\ n' ); 
} 

file.end();

看看我用来建立那个大文件的东西。一个可写的流!

fs模块可用于使用流接口读取和写入文件。在上面的例子中,咱们big.file经过带有循环的可写流100万行来写入。

运行上面的脚本会生成大约约400 MB的文件。

这是一个简单的Node Web服务器,专门用于big.file

const server = require('http')。createServer(); 

server.on('request',(req,res)=> { 
  fs.readFile('./ big.file',(err,data)=> { 
    if(err)throw err; 
  
    res.end(data); 
  }); 
}); 

server.listen(8000);const fs = require('fs'); 
const server = require('http')。createServer(); 

server.on('request',(req,res)=> { 
  fs.readFile('./ big.file',(err,data)=> { 
    if(err)throw err; 
  
    res.end(data); 
  }); 
}); 

server.listen(8000);

当服务器收到请求时,它将使用异步方法为大文件提供服务fs.readFile。可是,嘿,这不像咱们阻止事件循环或任何事情。每件事都很棒,对吗?对?

好吧,让咱们看看当咱们运行服务器,链接到它并监视内存时会发生什么。

当我运行服务器时,它开始时具备正常的内存量,8.7 MB:

而后我链接到服务器。注意消耗的内存发生了什么:

哇 - 内存消耗跃升至434.8 MB。

在将big.file它们写入响应对象以前,咱们基本上将整个内容放在内存中。这是很是低效的。

HTTP响应对象(res在上面的代码中)也是可写流。这意味着若是咱们有一个表示内容的可读流big.file,咱们能够将这两个相互管道并实现大体相同的结果,而不会消耗~400 MB的内存。

Node的fs模块能够为使用该createReadStream方法的任何文件提供可读流。咱们能够将它传递给响应对象:

const server = require('http')。createServer(); 

server.on('request',(req,res)=> { 
  const src = fs.createReadStream('./ big.file'); 
  src.pipe(res);
 }); 

server.listen(8000);const fs = require('fs'); 
const server = require('http')。createServer(); 

server.on('request',(req,res)=> { 
  const src = fs.createReadStream('./ big.file'); 
  src.pipe(res);
 }); 

server.listen(8000);

如今当你链接到这个服务器时,会发生一件神奇的事情(看一下内存消耗):

发生了什么?

当客户端请求该大文件时,咱们一次流一个块,这意味着咱们根本不在内存中缓冲它。内存使用量增加了大约25 MB,就是这样。

您能够将此示例推到极限。big.file使用500万行而不是仅仅100万行从新生成,这将使文件超过2 GB,而且实际上大于Node中的默认缓冲区限制。

若是您尝试使用该文件fs.readFile,默认状况下您根本不能(您能够更改限制)。可是fs.createReadStream,对于请求者来讲,将2 GB的数据流传输到请求者是没有问题的,最重要的是,进程内存使用量大体相同。

准备好学习流了吗?

本文是 关于Node.js的Pluralsight课程的一部分。我在那里报道了相似的视频格式内容。

流101

Node.js中有四种基本流类型:可读,可写,双工和变换流。

  • 可读流是能够从中消费数据的源的抽象。一个例子是fs.createReadStream方法。
  • 可写流是能够写入数据的目标的抽象。一个例子是fs.createWriteStream方法。
  • 双工流是可读和可写的。一个例子是TCP套接字。
  • 变换流基本上是双工流,可用于在写入和读取数据时修改或转换数据。一个例子是zlib.createGzip使用gzip压缩数据的流。您能够将转换流视为一个函数,其中输入是可写流部分,输出是可读流部分。您可能还会听到称为“ 直通流 ”的转换

全部流都是EventEmitter。它们发出可用于读取和写入数据的事件。可是,咱们可使用该pipe方法以更简单的方式使用流数据。

管道方法

这是你须要记住的神奇线条:

 .pipe(writableDestreadableSrc .pipe(writableDest

在这个简单的行中,咱们管道输出可读流 - 数据源,做为可写流的输入 - 目的地。源必须是可读流,目标必须是可写的。固然,它们也能够是双工/变换流。事实上,若是咱们正在进入双工流,咱们能够像在Linux中同样连接管道调用:

  .pipe(transformStream1)
  .pipe(transformStream2)
  .pipe(finalWrtitableDest)readableSrc 
  .pipe(transformStream1)
  .pipe(transformStream2)
  .pipe(finalWrtitableDest)

pipe方法返回目标流,这使咱们可以进行上述连接。对于流a(读取),bc(双面),和d(可写的),咱们能够:

a.pipe(b)中.pipe(c)中.pipe(d)
a.pipe(b)
b.pipe(c)
c.pipe(d)#这至关于:
a.pipe(b)
b.pipe(c)
c.pipe(d)
$ a | b | c | d#在Linux中,至关于:
$ a | b | c | d

pipe方法是消费流的最简单方法。一般建议使用该pipe方法或使用事件消耗流,但避免混合这二者。一般,当您使用该pipe方法时,您不须要使用事件,但若是您须要以更自定义的方式使用流,则事件将是可行的方法。

流事件

除了从可读流源读取并写入可写目的地以外,该pipe方法还会自动管理一些事情。例如,它处理错误,文件结束以及一个流比另外一个流更慢或更快的状况。

可是,流也能够直接与事件一块儿使用。这是该pipe方法主要用于读写数据的简化事件等效代码:

#readed.pipe(可写)
  writable.write(chunk); 
});readable.on('data',(chunk)=> { 
  writable.write(chunk); 
});
  writable.end(); 
});readable.on('end',()=> { 
  writable.end(); 
});

如下是可与可读写流一块儿使用的重要事件和函数的列表:

从个人Pluralsight课程 - Advanced Node.js中捕获的截图

事件和函数以某种方式相关,由于它们一般一块儿使用。

可读流上最重要的事件是:

  • data每当流将一大块数据传递给使用者时发出的事件
  • end事件,在没有更多数据要从流中消耗时发出。

可写流上最重要的事件是:

  • drain事件,是可写流能够接收更多数据的信号。
  • finish事件,在将全部数据刷新到基础系统时发出。

能够组合事件和功能,以实现流的自定义和优化使用。要使用可读流,咱们可使用pipeunpipemethods或readunshiftresume方法。要使用可写流,咱们能够将它做为pipe/ 的目标unpipe,或者只是使用write方法写入它,并end在完成后调用方法。

可读流的暂停和流动模式

可读流有两种主要模式影响咱们使用它们的方式:

  • 它们能够处于暂停模式
  • 或者在流动模式下

这些模式有时被称为拉动和推进模式。

默认状况下,全部可读流都以暂停模式启动,但它们能够轻松切换为流动,并在须要时返回暂停状态。有时,切换会自动进行。

当可读流处于暂停模式时,咱们可使用该read()方法根据须要从流中读取,可是,对于流动模式中的可读流,数据不断流动,咱们必须监听事件以使用它。

在流动模式下,若是没有消费者能够处理数据,实际上可能会丢失数据。这就是为何当咱们在流动模式下有可读流时,咱们须要一个data事件处理程序。实际上,只需添加一个data事件处理程序就能够将暂停的流切换为流动模式,并删除data事件处理程序会将流切换回暂停模式。其中一些是为了向后兼容旧的Node流接口而完成的。

要在这两种流模式之间手动切换,可使用resume()pause()方法。

从个人Pluralsight课程 - Advanced Node.js中捕获的截图

使用该pipe方法消耗可读流时,咱们没必要担忧这些模式会pipe自动管理它们。

实现流

当咱们在Node.js中讨论流时,有两个主要的不一样任务:

  • 实现流的任务。
  • 消费它们的任务。

到目前为止,咱们一直在谈论只消耗流。让咱们实施一些!

流实现者一般requirestream模块的人。

实现可写流

要实现可写流,咱们须要使用Writable流模块中的构造函数。

const {Writable} = require('stream');

咱们能够经过多种方式实现可写流。例如,Writable若是须要,咱们能够扩展构造函数

class myWritableStream扩展Writable { 
}
}

可是,我更喜欢更简单的构造方法。咱们只是从Writable构造函数建立一个对象,并传递一些选项。惟一须要的选项是write暴露要写入的数据块的函数。

const {Writable} = require('stream');
const outStream = new Writable({ 
  write(chunk,encoding,callback){ 
    console.log(chunk.toString()); 
    callback(); 
  } 
}); 

process.stdin.pipe(outStream);
  write(chunk,encoding,callback){ 
    console.log(chunk.toString()); 
    callback(); 
  } 
}); 

process.stdin.pipe(outStream);

这个write方法有三个参数。

  • 一般是一个缓冲区,除非咱们配置不一样的数据流。
  • 编码参数,须要在这种状况下,但一般咱们能够忽略它。
  • 回调是咱们须要咱们完成处理数据块以后调用一个函数。这就是写入是否成功的信号。要发出故障信号,请使用错误对象调用回调。

outStream,咱们只是console.log将块做为字符串,并callback在没有错误的状况下调用以后表示成功。这是一个很是简单且可能不那么有用的回声流。它将回应它收到的任何东西。

要使用这个流,咱们能够简单地使用它process.stdin,这是一个可读的流,因此咱们能够直接process.stdin进入咱们的流outStream

当咱们运行上面的代码时,咱们输入的任何内容都process.stdin将使用该outStream console.log行回显。

这不是一个很是有用的实现流,由于它实际上已经实现和内置。这很是至关于process.stdout。咱们能够直接stdin进入stdout,咱们将经过这一行得到彻底相同的回声功能:

process.stdin.pipe(process.stdout);

实现可读流

要实现可读流,咱们须要Readable接口,并从中构造一个对象,并read()在流的配置参数中实现一个方法:

const {Readable} = require('stream');
const inStream = new Readable({ 
  read(){} 
});
  read(){} 
});

有一种实现可读流的简单方法。咱们能够直接push使用咱们但愿消费者使用的数据。

const {Readable} = require('stream');
const inStream = new Readable({ 
  read(){} 
});
  read(){} 
});
inStream中。('ABCDEFGHIJKLM'); 
inStream中。('NOPQRSTUVWXYZ');
('ABCDEFGHIJKLM'); 
inStream中。('NOPQRSTUVWXYZ');
inStream中。(null); //没有更多数据
(null); //没有更多数据
inStream.pipe(process.stdout);

当咱们push成为一个null对象时,这意味着咱们想要发信号通知该流没有更多数据。

要使用这个简单的可读流,咱们能够简单地将其传输到可写流中process.stdout

当咱们运行上面的代码时,咱们将从中读取全部数据inStream并将其回显到标准输出。很简单,但也不是颇有效率。

咱们基本上推流中的全部数据以前,它管道到process.stdout。当消费者要求时,更好的方法是按需推送数据。咱们能够经过read()在配置对象中实现该方法来实现:

const inStream = new Readable({ 
  read(size){ 
    //对数据有需求......有人想读它。
  } 
});
  read(size){ 
    //对数据有需求......有人想读它。
  } 
});

当在可读流上调用read方法时,实现能够将部分数据推送到队列。例如,咱们能够一次推送一个字母,从字符代码65(表明A)开始,并在每次推送时递增:

const inStream = new Readable({ 
  read(size){ 
    this.push(String.fromCharCode(this.currentCharCode ++)); 
    if(this.currentCharCode> 90){ 
      this.push(null); 
    } 
  } 
});
  read(size){ 
    this.push(String.fromCharCode(this.currentCharCode ++)); 
    if(this.currentCharCode> 90){ 
      this.push(null); 
    } 
  } 
});
inStream.currentCharCode = 65;
inStream.pipe(process.stdout);

当消费者正在阅读可读流时,该read方法将继续触发,而且咱们将推送更多字母。咱们须要在某个地方中止这个循环,这就是当currentCharCode大于90(表示Z)时if语句推送null的缘由。

这段代码至关于咱们开始使用的更简单的代码,但如今咱们在消费者要求时按需推送数据。你应该老是那样作。

实现双工/转换流

使用Duplex流,咱们可使用相同的对象实现可读和可写流。就像咱们从两个接口继承同样。

这是一个示例双工流,它结合了上面实现的两个可写和可读示例:

const {Duplex} = require('stream'); 

const inoutStream = new Duplex({ 
  write(chunk,encoding,callback){ 
    console.log(chunk.toString()); 
    callback(); 
  },

  read(size){ 
    this.push(String.fromCharCode(this.currentCharCode ++) ); 
    if(this.currentCharCode> 90){ 
      this.push(null); 
    } 
  } 
}); 

inoutStream.currentCharCode = 65;
const inoutStream = new Duplex({ 
  write(chunk,encoding,callback){ 
    console.log(chunk.toString()); 
    callback(); 
  },

  read(size){ 
    this.push(String.fromCharCode(this.currentCharCode ++) ); 
    if(this.currentCharCode> 90){ 
      this.push(null); 
    } 
  } 
}); 

inoutStream.currentCharCode = 65;
<strong>process.stdin.pipe(inoutStream).pipe(process.stdout);</strong>

经过组合这些方法,咱们可使用此双工流来读取A到Z中的字母,咱们也能够将其用做其回声功能。咱们将可读stdin流传输到此双工流中以使用echo功能,咱们将双工流自己stdout传输到可写流中以查看字母A到Z.

重要的是要理解双工流的可读和可写侧彻底独立地操做。这仅仅是将两个特征分组到一个对象中。

变换流是更有趣的双工流,由于其输出是根据其输入计算的。

对于转换流,咱们没必要实现readwrite方法,咱们只须要实现一个transform结合它们的方法。它具备write方法的签名,咱们也能够将它用于push数据。

这是一个简单的变换流,在将其转换为大写格式以后回显您输入的任何内容:

const {Transform} = require('stream'); 

const upperCaseTr = new Transform({ 
  transform(chunk,encoding,callback){ 
    this.push(chunk.toString()。toUpperCase()); 
    callback(); 
  } 
}); 

process.stdin.pipe(upperCaseTr).pipe(process.stdout);
const upperCaseTr = new Transform({ 
  transform(chunk,encoding,callback){ 
    this.push(chunk.toString()。toUpperCase()); 
    callback(); 
  } 
}); 

process.stdin.pipe(upperCaseTr).pipe(process.stdout);

在这个咱们正在消耗的变换流中,就像前面的双工流示例同样,咱们只实现了一个transform()方法。在该方法中,咱们将chunk其转换为大写版本,而后push将该版本转换为可读部分。

流对象模式

默认状况下,流指望缓冲区/字符串值。objectMode咱们能够设置一个标志,让流接受任何JavaScript对象。

这是一个简单的例子来证实这一点。如下转换流组合使得一个功能能够将逗号分隔值的字符串映射到JavaScript对象中。所以“a,b,c,d”变得{a: b, c: d}

const {Transform} = require('stream');
const commaSplitter = new Transform({ 
  readableObjectMode:true,
  readableObjectMode:true,
transform(chunk,encoding,callback){ 
    this.push(chunk.toString()。trim()。split(',')); 
    打回来(); 
  } 
};
    this.push(chunk.toString()。trim()。split(',')); 
    打回来(); 
  } 
};
const arrayToObject = new Transform({ 
  readableObjectMode:true,
  writableObjectMode:true,
  readableObjectMode:true,
  writableObjectMode:true,
transform(chunk,encoding,callback){ 
    const obj = {}; 
    for(let i = 0; i <chunk.length; i + = 2){ 
      obj [chunk [i]] = chunk [i + 1]; 
    } 
    this.push(obj); 
    打回来(); 
  } 
};
    const obj = {}; 
    for(let i = 0; i <chunk.length; i + = 2){ 
      obj [chunk [i]] = chunk [i + 1]; 
    } 
    this.push(obj); 
    打回来(); 
  } 
};
const objectToString = new Transform({ 
  writableObjectMode:true,
  writableObjectMode:true,
transform(chunk,encoding,callback){ 
    this.push(JSON.stringify(chunk)+'\ n'); 
    打回来(); 
  } 
};
    this.push(JSON.stringify(chunk)+'\ n'); 
    打回来(); 
  } 
};
process.stdin 
  .pipe(commaSplitter)
  .pipe(arrayToObject)
  .pipe(
  objectToString ).pipe(process.stdout)
  .pipe(commaSplitter)
  .pipe(arrayToObject)
  .pipe(
  objectToString ).pipe(process.stdout)

咱们传递输入字符串(例如“a,b,c,d”),经过commaSplitter该字符串将数组推送为可读数据([“a”, “b”, “c”, “d”])。readableObjectMode在该流上添加标志是必要的,由于咱们在那里推送一个对象,而不是字符串。

而后咱们将数组并将其arrayToObject传输到流中。咱们须要一个writableObjectMode标志来使该流接受一个对象。它还会推送一个对象(映射到对象的输入数组),这就是为何咱们也须要readableObjectMode那里的标志。最后一个objectToString流接受一个对象,但推出一个字符串,这就是为何咱们只须要一个writableObjectMode标志。可读部分是普通字符串(字符串化对象)。

使用上面的例子

Node的内置转换流

Node有一些很是有用的内置变换流。即,zlib和加密流。

这是一个使用zlib.createGzip()流与fs可读/可写流相结合来建立文件压缩脚本的示例:

const zlib = require('zlib'); 
const file = process.argv [2]; 

fs.createReadStream(file)
  .pipe(zlib.createGzip())。
  pipe(fs.createWriteStream(file +'.gz'));const fs = require('fs'); 
const zlib = require('zlib'); 
const file = process.argv [2]; 

fs.createReadStream(file)
  .pipe(zlib.createGzip())。
  pipe(fs.createWriteStream(file +'.gz'));

您可使用此脚本将您传递的任何文件做为参数进行gzip。咱们将该文件的可读流传输到zlib内置转换流中,而后传输到新gzip压缩文件的可写流中。简单。

使用管道的一个很酷的事情是,若是须要,咱们实际上能够将它们与事件结合起来。好比说,我但愿用户在脚本工做时看到进度指示器,在脚本完成时看到“完成”消息。因为该pipe方法返回目标流,咱们也能够连接事件处理程序的注册:

const zlib = require('zlib'); 
const file = process.argv [2]; 

fs.createReadStream(file)
  .pipe(zlib.createGzip())。
  on('data',()=> process.stdout.write('。'))。
   pipe(fs.createWriteStream(file +'.zz' ))。
  on('finish',()=> console.log('完成'));const fs = require('fs'); 
const zlib = require('zlib'); 
const file = process.argv [2]; 

fs.createReadStream(file)
  .pipe(zlib.createGzip())。
 on('data',()=> process.stdout.write('。'))。
   pipe(fs.createWriteStream(file +'.zz' ))。
  on('finish',()=> console.log('完成'));

所以,经过该pipe方法,咱们能够轻松地使用流,但咱们仍然可使用须要的事件进一步自定义与这些流的交互。

这个pipe方法的优势在于咱们能够用一种可读的方式逐个编写程序。例如,data咱们能够简单地建立一个转换流来报告进度,而不是监听上面的事件,并.on()用另外一个  .pipe()调用替换该  调用:

const zlib = require('zlib'); 
const file = process.argv [2]; 

const {Transform} = require('stream'); 

const reportProgress = new Transform({ 
  transform(chunk,encoding,callback){ 
    process.stdout.write('。'); 
    callback(null,chunk); 
  } 
}); 

fs.createReadStream(file)
  .pipe(zlib.createGzip())。
  pipe(reportProgress)
   .pipe(fs.createWriteStream(file +'。。'))。
  on('finish',()=> console.log( '完成'));const fs = require('fs'); 
const zlib = require('zlib'); 
const file = process.argv [2]; 

const {Transform} = require('stream'); 

const reportProgress = new Transform({ 
  transform(chunk,encoding,callback){ 
    process.stdout.write('。'); 
    callback(null,chunk); 
  } 
}); 

fs.createReadStream(file)
  .pipe(zlib.createGzip())。
 pipe(reportProgress)
   .pipe(fs.createWriteStream(file +'。。'))。
  on('finish',()=> console.log( '完成'));

reportProgress流是一个简单的直通流,但它也会将进度报告给标准输出。请注意我如何使用函数中的第二个参数callback()来推送transform()方法中的数据。这至关于首先推送数据。

组合流的应用是无止境的。例如,若是咱们须要在gzip以前或以后加密文件,咱们须要作的就是按照咱们须要的确切顺序管道另外一个转换流。咱们可使用Node的crypto模块:

// ...const crypto = require('crypto'); // ...
  .pipe(zlib.createGzip())。
  pipe(crypto.createCipher('aes192','a_secret'))。
   pipe(reportProgress)
  .pipe(fs.createWriteStream(file +'.zz') )
  .on('finish',()=> console.log('完成'));fs.createReadStream(file)
  .pipe(zlib.createGzip())。
 pipe(crypto.createCipher('aes192','a_secret'))。    pipe(reportProgress)
  .pipe(fs.createWriteStream(file +'.zz') )
  .on('finish',()=> console.log('完成'));

上面的脚本压缩而后加密传递的文件,只有拥有秘密的人才能使用输出的文件。咱们没法使用普通的解压缩实用程序解压缩此文件,由于它已加密。

要实际可以解压缩上面脚本压缩的任何内容,咱们须要以相反的顺序使用相反的流加密和zlib,这很简单:

  .pipe(加密createDecipher( 'AES192', 'a_secret'))
  .pipe(ZLIB。createGunzip())
  .pipe(reportProgress)
  .pipe(fs.createWriteStream(file.slice(0, - 3)))。
  on('finish',()=> console.log('Done'));fs.createReadStream(文件)
  .pipe(加密createDecipher( 'AES192', 'a_secret'))
  .pipe(ZLIB。createGunzip())
  .pipe(reportProgress)
  .pipe(fs.createWriteStream(file.slice(0, - 3)))。
  on('finish',()=> console.log('Done'));

假设传递的文件是压缩版本,上面的代码将从中建立一个读取流,将其传递到加密createDecipher()流(使用相同的秘密),将其输出createGunzip()传递到zlib 流,而后将内容写回没有扩展部分的文件。

这就是我对这个话题的所有见解。谢谢阅读!直到下一次!


学习反应仍是节点?查看个人书:

相关文章
相关标签/搜索