Haskell 中的 hSeek 和 SeekFromEnd

hSeek and SeekFromEnd in Haskell

我希望在 Haskell 中快速检索文件的最后一行---从结尾开始,而不是从开头---并且在正确使用 hSeek 时遇到一些困难。

似乎 SeekFromEnd N 的行为不同于查找文件长度 sz 和使用 AbsoluteSeek(sz - N) 字节。

outh <- openFile "test.csv" ReadMode

λ> hIsSeekable outh
True

λ> hFileSize outh
81619956
λ> hSeek outh AbsoluteSeek 1000
λ> hTell outh
1000

λ> hSeek outh SeekFromEnd 1000
λ> hTell outh
81620956

λ> hSeek outh AbsoluteSeek 0
λ> hGetLine outh
"here's my data"

λ> hSeek outh SeekFromEnd 10000
-*** Exception: test.csv: hGetLine: end of file

嗯,这很奇怪。

所以,我创建了一个函数,用 absolute 代替:

λ> hSeek outh SeekFromEnd 100000
λ> hTell outh
81719956

fromEnd outh = do
  sz <- hFileSize outh
  hSeek outh AbsoluteSeek (sz - 100000)

λ> fromEnd outh

λ> hTell outh
81519956

所以在输出方面,他们有不同的答案,这很奇怪。此外,我现在还可以使用 hGetLine,SeekFromEnd 失败于:

λ> hGetLine outh
"partial output"
λ> hGetLine outh
"full output, lots of fields, partial output"

我不清楚这里发生了什么。为什么我的 fromEnd 在允许 hGetLine 方面的行为与 SeekFromEnd 不同?

问题的第二部分:从文件末尾开始并向后查找第一个换行符(EOF 换行符之后的第一个 \n)的正确策略是什么?

在这个问题中,我专门使用 SeekFromEnd 寻找答案。

SeekFromEnd 的偏移量预计为负数。

关于获取文件的最后一行,我们遇到了一个烦恼,我们必须从末尾扫描每个字符,一个一个地扫描,每次重置位置。就是说,我们 可以 做到这一点 - 我们只是继续往回移动,直到遇到第一个 \n 字符。

import System.IO

-- | Given a file handle, find the last line. There are no guarantees as to the 
-- position of the handle after this call, and it is expected that the given
-- handle is seekable.
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)

你可能想在这里加一个减号,因为大多数文件通常以 \n 结尾(而那个空行可能不是你想要的)