【问题标题】:Iterate through lines in a file with Node.js and CoffeeScript使用 Node.js 和 CoffeeScript 遍历文件中的行
【发布时间】:2013-02-05 07:12:08
【问题描述】:

我正在使用带有 CoffeScript 的 Node.js 和 以下函数:

each_line_in = (stream, func) ->
    fs.stat stream.path, (err, stats) ->
        previous = []
        stream.on 'data', (d) ->
            start = cur = 0
            for c in d
                cur++
                if c == 10
                    previous.push(d.slice(start, cur))
                    func previous.join('')
                    previous = []
                    start = cur
            previous.push(d.slice(start, cur)) if start != cur

有没有更好的方法来做到这一点,而无需将整个文件读入 内存?“更好”是指更简洁、内置于 Node.js、更快或 更正确。如果我正在编写 Python,我会这样做:

def each_line_in(file_obj, func):
    [ func(l) for l in file_obj ]

我看到this question 它使用Peteris Krumin's "lazy" module,但我想完成 这没有添加外部依赖项。

【问题讨论】:

  • 当您说“遍历行”时,您的意思是“继续阅读直到您遇到\n”还是“继续阅读直到您阅读了 10 个字符”(就像您的示例代码一样) ?
  • 上面的代码肯定不会在仅仅读取 10 个字符后停止。如果您尝试运行代码,您可能会看到“10”是换行符的 ASCII 字符。

标签: node.js coffeescript


【解决方案1】:

这是一个相当有效的方法:

eachLineIn = (filePath, func) ->

  blockSize = 4096
  buffer = new Buffer(blockSize)
  fd = fs.openSync filePath, 'r'
  lastLine = ''

  callback = (err, bytesRead) ->
    throw err if err
    if bytesRead is blockSize
      fs.read fd, buffer, 0, blockSize, null, callback

    lines = buffer.toString('utf8', 0, bytesRead).split '\n'
    lines[0] = lastLine + lines[0]
    [completeLines..., lastLine] = lines
    func(line) for line in completeLines
    return

  fs.read fd, buffer, 0, blockSize, 0, callback
  return

您应该在您的硬件和操作系统上对此进行基准测试,以找到适合大文件的 blockSize 的最佳值。

请注意,这假定文件行仅由\n 划分。如果您不确定您的文件使用什么,您应该使用 split 的正则表达式,例如:

.split(/(\\r\\n)|\\r|\\n/)

【讨论】:

  • 这不能解决我的问题;它不太简洁,可能更慢,因为“toString”和“split”方法需要读取缓冲区的长度,并且不再正确,因为你错误地解释了我的代码(如果你尝试运行它,你会看到这个) .因此,我对您的回答投了反对票。
  • 很公平,但我只是运行了一个基准测试:您的函数读取一个文件需要 11,101 毫秒,而我的函数读取同一个文件需要 1,647 毫秒。诚然,它的行为与您的代码略有不同:它采用文件路径而不是 ReadableStream,并且它提供的行不包括结尾的 \n。但我不会忽视整体方法。如果您丢弃额外的空格、错误抛出和明确的returns,它也同样简洁。
【解决方案2】:

这是一个使用 ReadStream 的简洁版本,例如 stream = fs.createReadStream(filepath)

for_each_line = (stream, func) ->
  last = ""
  stream.on('data', (chunk) ->
    lines = (last + chunk).split("\n")
    [lines...,last] = lines
    for line in lines
      func(line)
  )
  stream.on('end', () ->
    func(last)
  )

createReadStream 的选项可以根据需要设置缓冲区大小和编码。

这会去除“\n”,但如果需要,可以添加回来。它还处理最后一行,但如果文件以“\n”结尾,那将是空的。

这三个版本的时间差别不大。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-22
    • 2021-01-27
    • 2020-08-12
    • 2012-05-07
    • 1970-01-01
    • 2015-10-14
    • 1970-01-01
    • 2021-09-12
    相关资源
    最近更新 更多