2011-06-01 36 views
18

我一直在使用Data.Binary将数据序列化到文件。在我的应用程序中,我逐渐向这些文件添加项目。两种最流行的序列化软件包,二进制和谷类,都将列表序列化为列表项,然后列出项目。因此,我不能追加到我的序列化文件。我目前在整个文件中读取,反序列化列表,追加到列表,重新序列化列表,然后将其写回到文件中。但是,我的数据集越来越大,而且我开始用尽内存。我可能会解开我的数据结构以获得一些空间,但这种方法不能扩展。Haskell中未定义长度列表的二进制序列化

一个解决方案是弄脏文件格式来改变初始计数,然后再追加我的元素。但这并不令人满意,更不用说因为打破抽象而对未来文件格式的变化敏感。在这里,迭代者/调查员会想到这是一个有吸引力的选择。我查找了一个将它们与二进制序列化相结合的库,但没有找到任何东西。任何人都知道这是否已经完成?如果没有,这个图书馆会有用吗?或者我错过了什么?

+0

你能为Binary写一个流式实例吗?编写一个chunk-wise(渴望)编码器相对容易,它一次编写多组* n *个元素。 – 2011-06-01 17:38:00

+0

噢,我并不认为这很简单。我主要想知道它是否已经完成。如果不是,我是否应该从现有的抽象或类型类? – mightybyte 2011-06-01 17:46:19

+0

没有我知道的。尽管你可能有幸在Hackage上寻找可搜索的文件API。 – 2011-06-01 17:57:17

回答

6

所以我说坚持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)) 

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

+0

嗯,这似乎对编码工作正常,但解码似乎并没有真正的流式传输(当我尝试使用第一个条目时,它会读取整个输入内容) – gatoatigrado 2014-02-04 22:04:34

1

这个问题已经回答了四年了,但我在Don Stewart的回答的评论中遇到了与gatoatigrado相同的问题。 put方法按照广告方式工作,但get读取整个输入。我认为问题在于案例陈述Stream xs <- 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) 
        [email protected](Partial _) -> decode1 k xs 

使用注意事项的getWord8这是读取编码的[]:从用于流的put定义所得实例。另请注意,由于getWord8忽略了编码的[]和:符号,因此此实现不会检测列表的结尾。我的编码文件只是一个单一的列表,所以它的工作原理,但否则你需要修改。

在任何情况下,这个decodes在访问头部元素和最后一个元素的情况下都运行在恒定内存中。