- 原文地址:Using Node.js to Read Really, Really Large Files (Pt 1)
- 原文做者:Paige Niedringhaus
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:lucasleliane
- 校对者:sunui,Jane Liao
这篇博文有一个很是有趣的启发点。上周,某我的在个人 Slack 频道上发布了一个编码挑战,这个挑战是他在申请一家保险技术公司的开发岗位时收到的。html
这个挑战激起了个人兴趣,这个挑战要求读取联邦选举委员会的大量数据文件,而且展现这些文件中的某些特定数据。因为我没有作过什么和原始数据相关的工做,而且我老是乐于接受新的挑战,因此我决定用 Node.js 来解决这个问题,看看我是否可以完成这个挑战,而且从中找到乐趣。前端
下面是提出的四个问题,以及这个程序须要解析的数据集的连接。node
数据的连接:www.fec.gov/files/bulk-…android
当你解压完这个文件夹,你能够看到一个大小为 2.55 GB 的 .txt
主文件,以及一个包含了主文件部分数据的文件夹(这个是我在跑主文件以前,用来测试个人解决方案的)。ios
不是很是可怕,对吧?彷佛是可行的。因此让咱们看看我是怎么实现的。git
处理大型文件对于 JavaScript 来讲并非什么新鲜事了,实际上,在 Node.js 的核心功能当中,有不少标准的解决方案能够进行文件的读写。github
其中,最直接的就是 fs.readFile()
,这个方法会将整个文件读入到内存当中,而后在 Node 读取完成后当即执行操做,第二个选择是 fs.createReadStream()
,这个方法以数据流的形式处理数据的输入输出,相似于 Python 或者是 Java。spring
因为个人解决方案涉及到计算行的总数以及解析每一行的数据来获取捐赠名和日期,因此我选择第二个方法:fs.createReadStream()
。而后在遍历文件的时候,我可使用 rl.on('line',...)
函数来从文件的每一行中获取必要的数据。docker
对我来讲,这比将整个文件读入到内存中,而后再逐行读取更加简单。npm
下面是我用 Node.js 的 fs.createReadStream()
函数实现的代码。我会在下面将其分解。
我所要作的第一件事就是从 Node.js 中导入须要的函数:fs
(文件系统),readline
,以及 stream
。导入这些内容后,我就能够建立一个 instream
和 outstream
而后调用 readLine.createInterface()
,它们让我能够逐行读取流,而且从中打印出数据。
我还添加了一些变量(和注释)来保存各类数据:一个 lineCount
、names
数组、donation
数组和对象,以及 firstNames
数组和 dupeNames
对象。你能够稍后看到它们发挥做用。
在 rl.on('line',...)
函数里面,我能够完成数据的逐行分析。在这里,我为数据流的每一行都进行了 lineCount
的递增。我用 JavaScript 的 split()
方法来解析每个名字,而且将其添加到 names
数组当中。我会进一步将每一个名字都缩减为 first name,同时在 JavaScript 的 trim()
,includes()
以及 split()
方法的帮助下,计算 middle name 的首字母,以及名字出现的次数等信息。而后我将时间列的年份和时间进行分割,将其格式化为更加易读的 YYYY-MM
格式,而且添加到 dateDonationCount
数组当中。
在 rl.on('close',...)
函数中,我对我收集到数组中的数据进行了转换,而且在 console.log
的帮助下将个人全部数据展现给用户。
找到第 432 个以及第 43243 个下标处的 lineCount
和 names
不须要进一步的操做了。而找到最常出现的名字和每月的捐款数量比较棘手。
对于最多见的名字,我首先须要建立一个键值对对象用于存储每一个名字(做为 key)和这个名字出现的次数(做为 value),而后我用 ES6 的函数 Object.entries()
来将其转换为数组。以后再对这个数组进行排序而且打印出最大值,就是一件很是简单的事情了。
获取捐赠数量也须要一个相似的键值对对象,咱们建立一个 logDateElements()
函数,咱们可使用 ES6 的字符串插值来展现每月捐赠数量的键值。而后,建立一个 new Map()
将 dateDonations
对象转换为嵌套数组,而且对于每一个数组元素调用 logDateElements()
函数。呼!并不像我开始想的那么简单。
至少对于我测试用的 400 MB 大小的文件是奏效的……
在用 fs.createReadStream()
方法完成后,我回过头来尝试使用 fs.readFile()
来实现个人解决方案,看看有什么不一样。下面是这个方法的代码,可是我不会在这里详细介绍全部细节。这段代码和第一个代码片十分类似,只是看起来更加同步(除非你使用 fs.readFileSync()
方法,可是不用担忧,JavaScript 会和运行其余异步代码同样执行这段代码)。
若是你想要看个人代码的完整版,能够在这里找到。
使用个人解决方案,我将传入到 readFileStream.js
的文件路径替换成了那个 2.55 GB 的怪物文件,而且看着个人 Node 服务器由于 JavaScript heap out of memory
错误而崩溃。
事实证实,虽然 Node.js 采用流来进行文件的读写,可是其仍然会尝试将整个文件内容保存在内存中,而这对于这个文件的大小来讲是没法作到的。Node 能够一次容纳最大 1.5 GB 的内容,可是不可以再大了。
所以,我目前的解决方案都不可以完成这整个挑战。
我须要一个新的解决方案。一个基于 Node 的,可以处理更大的数据集的解决方案。
EventStream
是一个目前很流行的 NPM 模块,它每周有超过 200 万的下载量,号称可以“让流的建立和使用更加简单”。
在 EventStream 文档的帮助下,我再次弄清楚了如何逐行读取代码,而且以更加 CPU 友好的方式来实现。
这个是我使用 EventStream NPM 模块实现的新代码。
最大的变化是以文件开头的管道命令 —— 全部这些语法,都是 EventStream 文档所建议的方法,经过 .txt
文件每一行末尾的 \n
字符来进行流的分解。
我惟一改变的内容是修改了 names
的结果。我不得不实话实说,由于我尝试将 1300 万个名字放到数组里面,结果仍是发生了内存不足的问题。我绕过了这个问题,只收集了第 432 个和第 43243 个名字,而且将它们加入到了它们本身的数组当中。并非由于其余什么缘由,我只是想有点本身的创意。
好了,新的解决方案实现好了,又一次,我使用 2.55 GB 的文件启动了 Node.js,同时双手合十起到此次可以成功。来让咱们看看结果。
成功了!
最后,Node.js 的纯文件和大数据处理功能与我须要的能力还有些差距,可是只要使用一个额外的 NPM 模块,好比 EventStream,我就可以解析巨大的数据而不会形成 Node 服务器的崩溃。
请继续关注本系列的第二部分,我对在 Node.js 中读取数据的三种方式的性能进行了测试和比较,看看哪种方式的性能可以优于其余方式。结果变得很是瞩目 —— 特别是随着数据量的变大……
感谢你的阅读,我但愿本文可以帮助你了解如何使用 Node.js 来处理大量数据。感谢你的点赞和关注!
若是您喜欢阅读本文,你可能还会喜欢个人其余一些博客:
引用和继续阅读资源:
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。