无需缓冲即可读取大文件中的大行

Read large lines in huge file without buffering

我想知道是否有一种简单的方法可以从文件中一次获取一行,而无需最终将整个文件加载到内存中。我想用 attoparsec 解析器对线条进行折叠。我尝试将 Data.Text.Lazy.IOhGetLine 一起使用,这让我记忆犹新。我后来读到最终加载了整个文件。

我还尝试将 pipes-textfoldsview lines 一起使用:

s <- Pipes.sum $ 
    folds (\i _ -> (i+1)) 0 id (view Text.lines (Text.fromHandle handle))
print s

只是计算行数,它似乎在做一些奇怪的事情 "hGetChunk: invalid argument (invalid byte sequence)",它需要 11 分钟,而 wc -l 需要 1 分钟。我听说 pipes-text 可能有一些关于巨大线条的问题? (每行约1GB)

我非常乐于接受任何建议,除了新手 readLine 操作指南外找不到太多搜索。

谢谢!

以下代码使用 Conduit,并将:

  • UTF8-解码标准输入
  • 运行 lineC 组合器只要有更多可用数据
  • 对于每一行,只需 yield1 并丢弃该行内容,而不是一次将整行读入内存
  • 汇总生成的 1s 并打印出来

您可以将 yield 1 代码替换为将在各个行上进行处理的代码。

#!/usr/bin/env stack
-- stack --resolver lts-8.4 --install-ghc runghc --package conduit-combinators
import Conduit

main :: IO ()
main = (runConduit
     $ stdinC
    .| decodeUtf8C
    .| peekForeverE (lineC (yield (1 :: Int)))
    .| sumC) >>= print

这可能是最简单的折叠解码文本流

{-#LANGUAGE BangPatterns #-}
import Pipes 
import qualified Pipes.Prelude as P
import qualified Pipes.ByteString as PB
import qualified Pipes.Text.Encoding as PT
import qualified Control.Foldl as L
import qualified Control.Foldl.Text as LT
main = do
  n <- L.purely P.fold (LT.count '\n') $ void $ PT.decodeUtf8 PB.stdin
  print n

对于我制作的只有一长串逗号和数字的文件,它比 wc -l 长了大约 14%。如文档所述,IO 应该使用 Pipes.ByteString 正确完成,剩下的就是各种便利。

您可以在每一行上映射一个 attoparsec 解析器,以 view lines 区分,但请记住,attoparsec 解析器可以随意累积整个文本,这可能不是一个好主意 1千兆字节的文本块。如果每行有一个重复的数字(例如单词分隔的数字),您可以使用 Pipes.Attoparsec.parsed 来流式传输它们。