在Haskell中处理大型二进制文件的最有效方法是什么?
标准答案是将整个文件读取为惰性ByteString,然后使用“二进制”数据包之类的内容在其上编写解析器。那里有两个问题...
首先,像Binary这样的库并不能真正处理解析失败,而我明确希望解析有时会失败。
其次,我没有解析整个文件的内容。我将跳过其中的大部分。从磁盘读取GB的数据到RAM只是为了让垃圾收集器再次丢弃它,这似乎表现不佳。
与此相关的是,我需要能够判断我想执行的跳过是否会使我离开文件末尾(如果出错了,则会出错)。
我可能还需要向后搜索,或者到文件中的特定字节偏移量,而懒惰的ByteString方法似乎并不能很好地支持该偏移量。 (存在将整个文件保存在RAM中的严重危险。)
当然,另一种选择是与hSeek
命令交错读取单个字节。但是现在的问题是,一次读取一个字节的文件效率如何?听起来可能还很慢。我不确定hSetBuffering
是否会对此产生影响。 (?)
当然,还有mmap
。但是,如果将其用于大文件,这似乎会使虚拟内存系统崩溃。 (这很奇怪,考虑到这是其存在的全部目的...)
伙计们,我们怎么想?就I / O性能和代码可维护性而言,解决此问题的最佳方法是什么?
最佳答案
在使用pdf解析器时,我遇到了类似的问题。最初,我使用了iteratee
包(supports random access)。 AFAIK是唯一具有随机IO支持的IO库。
我的current approach是基于io-streams
包的。我发现它更方便。性能已足够,开箱即用的attoparsec
集成,包括许多组合器。
这是一个基本示例,如何将iteratee
用于随机IO:
shum@shum-laptop:/tmp/shum$ cat test.hs
import qualified Data.Iteratee as I
import qualified Data.Attoparsec.Iteratee as I
import qualified Data.Attoparsec.Char8 as P
import Control.Monad.IO.Class
import System.Environment
main :: IO ()
main = do
[file] <- getArgs
flip I.fileDriverRandom file $ do
I.seek 20
num1 <- I.parserToIteratee P.number
liftIO $ print num1
I.seek 10
num2 <- I.parserToIteratee P.number
liftIO $ print num2
shum@shum-laptop:/tmp/shum$ cat in.data
111111111
222222222
333333333
shum@shum-laptop:/tmp/shum$ runhaskell test.hs in.data
333333333
222222222
shum@shum-laptop:/tmp/shum$