Haskell:懒惰地用二进制文件读取二进制文件

时间:2013-11-08 15:24:55

标签: parsing haskell binary conduit

我正在尝试读取二进制文件并使用'binary'包来懒散地解析它。包文档提供了一个如何执行此操作的示例,而不强制与我的场景非常类似的所有输入:

 example2 :: BL.ByteString -> [Trade]
 example2 input
  | BL.null input = []
  | otherwise =
    let (trade, rest, _) = runGetState getTrade input 0
    in trade : example2 rest

但是,这会使用已弃用的runGetState函数,该函数本身会指向runGetIncremental函数。

问题是'runGetIncremental'函数似乎强制剩下的输入是严格的字节字符串,因此强制它将整个文件加载到内存中。实际上,当我尝试运行时,我看到内存使用量约为6GB。甚至runGetState的实现现在似乎都基于runGetIncremental,然后使用chunk将严格的字节串重新转换回惰性字符串。

我可以获得教程中描述的行为,还是现在不支持二进制文件?如果是后者,那么最好的方法是什么?我有一点使用管道的经验,但我不清楚如何在这里使用它。

1 个答案:

答案 0 :(得分:2)

您可以使用pipes-binarypipes-bytestring执行此操作。这是帮助您的好处的辅助功能:

import Control.Monad (void)
import Data.Binary
import Pipes
import Pipes.Binary (decodeMany)
import Pipes.ByteString (fromHandle)
import qualified Pipes.Prelude as P
import System.IO

decodeHandle :: (Binary a) => Handle -> Producer a IO ()
decodeHandle handle = void $ decodeMany (fromHandle handle) >-> P.map snd

voidmap snd存在,因为decodeMany实际上返回了更多信息(如字节偏移和解析错误)。如果您确实需要这些信息,那么只需删除它们即可。

以下是一个如何使用decodeHandle的示例,使用Trade的快速骨架,我将它放在一起:

data Trade = Trade

instance Binary Trade where
    get   = return Trade
    put _ = return ()

instance Show Trade where show _ = "Trade"

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    for (decodeHandle handle) $ \trade -> do
        lift $ print (trade :: Trade)
        -- do more with the parsed trade

您可以使用for循环解码的交易并处理它们,或者如果您愿意,可以使用管道组合:

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    decodeHandle handle >-> P.print

这将是懒惰的,只能解码您实际需要的交易数量。因此,如果您在解码器和打印机之间插入take,它将只读取所需的输入以处理所请求的交易次数:

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    for (decodeHandle handle >-> P.take 4) $ \trade -> do
        ... -- This will only process the first 4 trades

-- or using purely pipe composition:

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    decodeHandle handle >-> P.take 4 >-> P.print