【问题标题】:Binary Serialization for Lists of Undefined Length in HaskellHaskell中未定义长度列表的二进制序列化
【发布时间】:2011-06-01 17:29:21
【问题描述】:

我一直在使用 Data.Binary 将数据序列化为文件。在我的应用程序中,我逐渐将项目添加到这些文件中。两个最流行的序列化包,二进制和谷物,都将列表序列化为一个计数,然后是列表项。因此,我无法附加到我的序列化文件中。我目前读入整个文件,反序列化列表,附加到列表,重新序列化列表,然后将其写回文件。但是,我的数据集变得越来越大,并且我开始耗尽内存。我可能会四处拆箱我的数据结构以获得一些空间,但这种方法无法扩展。

一种解决方案是使用文件格式来更改初始计数,然后添加我的元素。但这并不是很令人满意,更不用说由于破坏抽象而对文件格式的未来变化敏感。 Iteratees/Enumerators 在这里成为一个有吸引力的选择。我寻找了一个将它们与二进制序列化相结合的库,但没有找到任何东西。有谁知道这是否已经完成?如果没有,这方面的图书馆会有用吗?还是我错过了什么?

【问题讨论】:

  • 你能写一个二进制流实例吗?编写一个逐块(渴望)编码器相对容易,它一次写入一组 n 元素。
  • 哦,我不反对它相当简单。我主要想知道它是否已经完成。另外,如果没有,我应该从现有的抽象或类型类开始吗?
  • 我不知道。虽然您可能会在 Hackage 上找到可搜索的文件 API。
  • 你似乎正在考虑编写一个库来提供你想要的功能,假设已经不存在。如果你这样做,并将其上传到 Hackage,你会在之后返回这里并从这个问题链接到它吗?作为问题的答案或编辑。 Stack Overflow 有疯狂的谷歌果汁,所以将来任何搜索类似内容的人都可能会发现这个问题。
  • 当前的 Data.Binary 列表格式是一个相当严重的缺陷,需要对结构进行两次遍历。不幸的是,出于向后兼容性的原因,它不能轻易更改。但是,您可以按照 Don 在下面的建议进行操作,并构建一个新类型包装器,并在需要序列化列表的任何地方小心使用它。

标签: serialization haskell binary stream lazy-evaluation


【解决方案1】:

所以我说坚持使用Data.Binary,但为可增长列表编写一个新实例。这是当前(严格)实例:

instance Binary a => Binary [a] where
    put l  = put (length l) >> mapM_ put l
    get    = do n <- get :: Get Int
                getMany n

-- | 'getMany n' get 'n' elements in order, without blowing the stack.
getMany :: Binary a => Int -> Get [a]
getMany n = go [] n
 where
    go xs 0 = return $! reverse xs
    go xs i = do x <- get
                 x `seq` go (x:xs) (i-1)
{-# INLINE getMany #-}

现在,允许您流式传输(以二进制形式)以附加到文件的版本需要急切或懒惰。懒惰的版本是最琐碎的。比如:

import Data.Binary

newtype Stream a = Stream { unstream :: [a] }

instance Binary a => Binary (Stream a) where

    put (Stream [])     = putWord8 0
    put (Stream (x:xs)) = putWord8 1 >> put x >> put (Stream xs)

    get = do
        t <- getWord8
        case t of
            0 -> return (Stream [])
            1 -> do x         <- get
                    Stream xs <- get
                    return (Stream (x:xs))

Massaged 适用于流式传输。现在,为了处理静默追加,我们需要能够找到文件末尾,并在添加更多元素之前覆盖最终的0 标签。

【讨论】:

  • 嗯,这似乎适用于编码,但解码似乎并没有真正流式传输(当我尝试只使用第一个条目时,它会为我读取整个输入)
【解决方案2】:

这个问题已经回答了四年,但我在对唐斯图尔特的回答的评论中遇到了与 gatoatigrado 相同的问题。 put 方法像宣传的那样工作,但 get 读取整个输入。我认为问题在于case语句Stream xs &lt;- get中的模式匹配,它必须在返回之前确定剩余的get是否为Stream a

我的解决方案以 Data.Binary.Get 中的示例为起点:

import Data.ByteString.Lazy(toChunks,ByteString)
import Data.Binary(Binary(..),getWord8)
import Data.Binary.Get(pushChunk,Decoder(..),runGetIncremental)
import Data.List(unfoldr)

decodes :: Binary a => ByteString -> [a]
decodes = runGets (getWord8 >> get)

runGets :: Get a -> ByteString -> [a]
runGets g = unfoldr (decode1 d) . toChunks
  where d = runGetIncremental g

decode1 _ [] = Nothing
decode1 d (x:xs) = case d `pushChunk` x of
                     Fail _ _ str  -> error str
                     Done x' _ a   -> Just (a,x':xs)
                     k@(Partial _) -> decode1 k xs

注意getWord8 的使用,这是为了读取流实例的put 定义产生的编码[]:。另请注意,由于 getWord8 忽略了编码的 [] 和 : 符号,因此此实现不会检测到列表的结尾。我的编码文件只是一个列表,因此它适用,但否则您需要修改。

无论如何,这个decodes 在访问头元素和最后元素的两种情况下都在常量内存中运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-19
    • 1970-01-01
    • 2014-01-04
    • 1970-01-01
    • 2021-03-24
    • 1970-01-01
    • 2014-08-20
    • 2015-09-04
    相关资源
    最近更新 更多