我想阅读我文件的最后一行并确保它与我的第一行具有相同数量的字段 - 我不关心中间的任何内容。我正在使用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\",
大
来自here,我知道
takeWhileR p xs相当于反向(takeWhileL p(反向) XS))。
所以让我们这样做。也就是说,让我们通过颠倒我的懒字节字符串来获取最后一行,而不是像以前那样取“\ n”,然后取消它。懒惰让我觉得编译器会让我轻松地做到这一点。
所以试试这个:
LB.reverse (LB.takeWhile (`notElem` "\n") (LB.reverse outh))
我期望看到的是:
"\"val1\",\"val2\",
相反,这会导致我的会话崩溃。
Segmentation fault (core dumped)
问题:
对于其他读者,如果您希望获得最后一行,您可以在答案中找到一种非常快速且合适的方法:hSeek and SeekFromEnd in Haskell
在这个帖子中,我正在寻找一个使用mmap的解决方案。
答案 0 :(得分:3)
我更希望使用同一作者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
字符。
另外,我不建议反转字节串 - 这至少需要一些分配,在这种情况下完全可以避免。即使你使用一个惰性字节串,你仍然需要支付遍历bytestring的所有块的成本(希望在这一点上甚至不应该构造它)。也就是说,你的反转代码应该工作。我认为有些问题与mmap
有关(可能是包,因为使用严格的字节串做同样的事情就可以了)。
我不确定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