互联网上再次有人错了-在昨天的“ 节点周刊”中,有一个指向该帖子的链接,作者正在其中尝试衡量和比较Node.js中Stream API的性能。 悲伤会导致作者如何处理信息流,并基于此得出以下结论:
...这在较小的文件上效果很好,但是一旦到达最大文件,就会发生相同的错误。 尽管Node.js正在流式处理输入和输出,但是在执行操作时它仍然尝试将整个文件保存在内存中
让我们尝试找出作者的结论和代码出了什么问题。
从我的角度来看,问题在于本文的作者不知道如何使用Stream'ami,这是一个必须经常处理的问题。 我认为这种现象有三个原因:
- Node.js Stream API的复杂故事- 此处描述的痛苦和苦难
- 如果您尝试在没有任何包装的情况下使用它,则不是最直观的API
- 非常奇怪的文档,将流呈现为非常复杂和低级的内容
总而言之,这导致开发人员经常不知道如何并且不想使用Stream API。
作者代码有什么问题?
首先,让我们在这里重复任务(在帖子中可以找到英文原件和指向文件的链接):
有一个2.5 GB的文件,其格式为:
C00084871|N|M3|P|201703099050762757|15|IND|COLLINS, DARREN ROBERT|SOUTHLAKE|TX|760928782|CELANESE|VPCHOP&TECH|02282017|153||PR2552193345215|1151824||P/R DEDUCTION ($76.92 BI-WEEKLY)|4030920171380058715
您需要对其进行分析,并找出以下信息:
- 文件中的行数
- 第432行和第43243行上的名称(此处出现了如何从0或1开始计数的问题)
- 最常用的名称及其出现的次数
- 每月分期付款数
怎么了 -作者诚实地说,他将整个文件加载到内存中,因此,Node“挂起”,作者给了我们一个有趣的事实。
有趣的事实:Node.js一次最多只能容纳1.67GB的内存
作者从这个事实得出了一个奇怪的结论,那就是Streams将整个文件加载到内存中,并且他没有编写错误的代码。
让我们反驳这一论点:“ 尽管Node.js正在流式传输输入和输出,但仍试图通过保存一个小程序来计算整个文件的大小,该程序将计算任意大小的文件中的行数:
const { Writable } = require('stream') const fs = require('fs') const split = require('split') let counter = 0 const linecounter = new Writable({ write(chunk, encoding, callback) { counter = counter + 1 callback() }, writev(chunks, callback) { counter = counter + chunks.length callback() } }) fs.createReadStream('itcont.txt') .pipe(split()) .pipe(linecounter) linecounter.on('finish', function() { console.log(counter) })
注意 :故意将代码编写得尽可能简单。 全局变量不好!
您要注意的是:
- split-npm一个在“输入”处接收行流的数据包-通过单独的换行符将一组行流返回到“输出”。 最有可能作为Transformation流的实现而实现的。 我们将它与文件的ReadStream传递给它,然后将其自身传递给...
- linecounter-WritableStream的实现。 在其中,我们实现了两种方法:处理一件(块)和数种方法。 在这种情况下,“行”是代码行。 反向-将所需的数字添加到计数器。 重要的是要了解,在这种情况下,我们不会将整个文件加载到内存中,API会将所有内容分成最便于处理的“片段”
- “完成”-当数据到达ReadableStream的“结束”时,“发生”的事件。 发生这种情况时,我们保证提供计数器数据
好吧,让我们在一个大文件上测试我们的创建:
> node linecounter.js 13903993
如您所见,一切正常。 从我们可以得出的结论来看,Stream API可以很好地处理任何大小的文件,而帖子作者的说法,实际上是不正确的。 大致以相同的方式,我们可以计算问题中所需的任何其他值。
告诉:
- 您是否有兴趣阅读如何完全解决问题以及如何将生成的代码转换为便于维护的形式?
- 您是否使用Stream API,遇到了什么困难?