Haskell用一个懒惰的mmap读最后一行



我想读取文件的最后一行,并确保它的字段数与我的第一行相同---我不在乎在中间的任何内容。我之所以使用mmap,是因为它对大文件的随机访问速度很快,但遇到了不理解Haskell或懒惰的问题。

λ> import qualified Data.ByteString.Lazy.Char8 as LB
λ> import System.IO.MMap
λ> outh <- mmapFileByteStringLazy fname Nothing 
λ> LB.length outh
87094896
λ> LB.takeWhile (`notElem` "n") outh
""Field1","Field2",

太好了。

从这里,我知道

takeWhileR p xs等价于reverse(takeWhilleL p(reversexs))。

所以让我们来做这个。也就是说,让我们通过反转我的懒惰字节串来获得最后一行,取while而不是像以前那样的"\n",然后撤消它。懒惰让我认为编译器会让我很容易做到这一点。

所以尝试一下:

LB.reverse (LB.takeWhile (`notElem` "n") (LB.reverse outh))

我期望看到的是:

""val1","val2",

相反,这会破坏我的会话。

Segmentation fault (core dumped)

问题:

  1. 懒惰、字节串、mmap库或Haskell做错了什么
  2. 我如何才能正确且高效地获得这条线?(答案可能是使用外来指针而不是惰性字节串?)

对于其他读者来说,如果你想得到最后一行,你可能会在这里的答案中找到一个非常快速和合适的方法:Haskell 中的hSeek和SeekFromEnd

在这个线程中,我专门寻找一个使用mmap的解决方案。

我更喜欢使用与bytestring由同一作者制作的bytestring-mmap。无论哪种情况,您只需要

import System.IO.Posix.MMap (unsafeMMapFile)
import qualified Data.ByteString.Char8 as BS
main = do
-- can be swapped out for `mmapFileByteString` from `mmap`
bs <- unsafeMMapFile "file.txt"
let (firstLine, _) = BS.break (== 'n') bs
(_, lastLine) = BS.breakEnd (== 'n') bs
putStrLn $ "First line: " ++ BS.unpack firstLine
putStrLn $ "Last line: " ++ BS.unpack lastLine

这也立即运行,没有额外的分配。和以前一样,需要注意的是,许多文件都以换行符结尾,因此可能希望使用BS.breakEnd (== 'n') (init bs)来忽略最后一个n字符。

此外,我不建议反转字节串——这至少需要一些分配,在这种情况下是完全可以避免的。即使你使用了一个懒惰的字节串,你仍然要为遍历字节串的所有块(希望在这一点上根本不应该构建)付出代价。也就是说,您的反向代码应该有效。我认为mmap有问题(可能是因为用严格的字节串做同样的事情,所以这个包运行得很好)。

上一个答案,来自OP编辑之前

我不确定System.IO中的函数有什么问题。以下内容立即在我的笔记本电脑上运行,文件file.txt几乎为4GB。它不优雅,但肯定很有效率。

import System.IO
hGetLastLine :: Handle -> IO String
hGetLastLine hdl = go "" (negate 1)
where
go s i = do
hSeek hdl SeekFromEnd i
c <- hGetChar hdl
if c == 'n'
then pure s
else go (c:s) (i-1)

main = do
handle <- openFile "file.txt" ReadMode
firstLine <- hGetLine handle
putStrLn $ "First line: " ++ firstLine
lastLine <- hGetLastLine handle
putStrLn $ "Last line: " ++ lastLine

最新更新