我正在尝试读取二进制文件并使用'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
将严格的字节串重新转换回惰性字符串。
我可以获得教程中描述的行为,还是现在不支持二进制文件?如果是后者,那么最好的方法是什么?我有一点使用管道的经验,但我不清楚如何在这里使用它。
答案 0 :(得分:2)
您可以使用pipes-binary
和pipes-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
void
和map 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