【发布时间】:2016-10-08 13:05:19
【问题描述】:
在为大型(<bloblength><blob>)* 编码二进制文件编写反序列化器时,我被各种 Haskell 生产-转换-消费库困住了。到目前为止,我知道有四个流媒体库:
- Data.Conduit: 使用广泛,资源管理非常细心
-
Pipes:类似于
conduit(Haskell Cast #6很好地揭示了conduit和pipes之间的区别) - Data.Binary.Get:提供了getWord32be等有用的函数,但是流式示例很尴尬
- System.IO.Streams: 好像是最容易用的一个
这是一个简化的示例,说明当我尝试使用conduit 进行Word32 流式传输时出现问题。一个稍微现实一点的例子是首先读取一个确定 blob 长度的Word32,然后产生一个该长度的惰性ByteString(然后进一步反序列化)。
但在这里我只是尝试从二进制文件中以流方式提取 Word32:
module Main where
-- build-depends: bytestring, conduit, conduit-extra, resourcet, binary
import Control.Monad.Trans.Resource (MonadResource, runResourceT)
import qualified Data.Binary.Get as G
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as BL
import Data.Conduit
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.List as CL
import Data.Word (Word32)
import System.Environment (getArgs)
-- gets a Word32 from a ByteString.
getWord32 :: C.ByteString -> Word32
getWord32 bs = do
G.runGet G.getWord32be $ BL.fromStrict bs
-- should read BytesString and return Word32
transform :: (Monad m, MonadResource m) => Conduit BS.ByteString m Word32
transform = do
mbs <- await
case mbs of
Just bs -> do
case C.null bs of
False -> do
yield $ getWord32 bs
leftover $ BS.drop 4 bs
transform
True -> return ()
Nothing -> return ()
main :: IO ()
main = do
filename <- fmap (!!0) getArgs -- should check length getArgs
result <- runResourceT $ (CB.sourceFile filename) $$ transform =$ CL.consume
print $ length result -- is always 8188 for files larger than 32752 bytes
程序的输出只是读取的 Word32 的数量。事实证明,流在读取第一个块(大约 32KiB)后终止。出于某种原因,mbs 永远不是Nothing,所以我必须检查null bs,它会在块被消耗时停止流。显然,我的导管transform 有问题。我看到了两种解决方案:
-
await不想去ByteStream的第二个块,那么还有另一个函数可以拉下一个块吗?在我见过的例子中(例如Conduit 101),这不是它的做法 - 这只是设置
transform的错误方式。
这是如何正确完成的?这是正确的方法吗? (性能确实很重要。)
更新:这是使用Systems.IO.Streams的BAD方法:
module Main where
import Data.Word (Word32)
import System.Environment (getArgs)
import System.IO (IOMode (ReadMode), openFile)
import qualified System.IO.Streams as S
import System.IO.Streams.Binary (binaryInputStream)
import System.IO.Streams.List (outputToList)
main :: IO ()
main = do
filename : _ <- getArgs
h <- openFile filename ReadMode
s <- S.handleToInputStream h
i <- binaryInputStream s :: IO (S.InputStream Word32)
r <- outputToList $ S.connect i
print $ last r
'Bad'表示:对时间和空间要求很高,不处理Decode异常。
【问题讨论】:
-
演示程序是否应该将整个输入分成 4 字节块并产生 word32s?
-
是的。更广泛的目标是读取 Word32 和可变大小的 blob(惰性字节字符串)。
-
不相关,但对于简单的 arg 解析,我会写
filename : _ <- getArgs而不是filename <- fmap (!!0) getArgs。 -
@danidiaz 确实如此。或者
head <$> getArgs
标签: haskell streaming bytestring haskell-pipes bytestream