Haskell 使用 Lazy mmap 读取最后一行
Haskell Read Last Line with a Lazy 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\",
太好了。
从here,我知道
takeWhileR p xs is equivalent to reverse (takeWhileL p (reverse
xs)).
让我们开始吧。也就是说,让我们通过反转我的惰性字节串来获取最后一行,像以前一样采用 while not "\n",然后将其反转。懒惰使我认为编译器会让我轻松地做到这一点。
所以尝试一下:
LB.reverse (LB.takeWhile (`notElem` "\n") (LB.reverse outh))
我希望看到的是:
"\"val1\",\"val2\",
相反,这会使我的会话崩溃。
Segmentation fault (core dumped)
问题:
- 我在懒惰、字节串、mmap 库或 Haskell 方面做错了什么?
- 我怎样才能正确且高效地获取这一行? (答案可能使用外部指针而不是惰性字节串?)
对于其他读者,如果您希望获得最后一行,您可能会在此处的答案中找到一种非常快速且合适的方法:
在此线程中,我专门寻找使用 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
我想阅读我文件的最后一行并确保它与我的第一行具有相同数量的字段---我不关心中间的任何内容。我正在使用 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 is equivalent to reverse (takeWhileL p (reverse xs)).
让我们开始吧。也就是说,让我们通过反转我的惰性字节串来获取最后一行,像以前一样采用 while not "\n",然后将其反转。懒惰使我认为编译器会让我轻松地做到这一点。
所以尝试一下:
LB.reverse (LB.takeWhile (`notElem` "\n") (LB.reverse outh))
我希望看到的是:
"\"val1\",\"val2\",
相反,这会使我的会话崩溃。
Segmentation fault (core dumped)
问题:
- 我在懒惰、字节串、mmap 库或 Haskell 方面做错了什么?
- 我怎样才能正确且高效地获取这一行? (答案可能使用外部指针而不是惰性字节串?)
对于其他读者,如果您希望获得最后一行,您可能会在此处的答案中找到一种非常快速且合适的方法:
在此线程中,我专门寻找使用 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