Serialization 对值列表进行序列化和计数

Serialization 对值列表进行序列化和计数,serialization,haskell,Serialization,Haskell,我需要使用一个自定义编码函数(我已经有了)序列化一个大的值列表。我已经这样做了,它可以工作了,但我还想让它计算有多少值被序列化并写入磁盘,同时仍然使用相对恒定的内存量(即,它不需要保留整个输入列表,因为它变得非常大) 无需保留计数、二进制、谷物和blaze builder所有工作(使用相当于B.writeFile“foo”.runPut.mapM_uu;encodeValue);但无论我如何处理这些库,结果ByteString似乎都会保留在内存中,直到它完成,而不是在块可用时(即使使用blaze

我需要使用一个自定义编码函数(我已经有了)序列化一个大的值列表。我已经这样做了,它可以工作了,但我还想让它计算有多少值被序列化并写入磁盘,同时仍然使用相对恒定的内存量(即,它不需要保留整个输入列表,因为它变得非常大)

无需保留计数、二进制、谷物和blaze builder所有工作(使用相当于
B.writeFile“foo”.runPut.mapM_uu;encodeValue
);但无论我如何处理这些库,结果ByteString似乎都会保留在内存中,直到它完成,而不是在块可用时(即使使用blaze builder)就开始写入磁盘

这是一个简单的示例,演示了我一直在尝试做的事情:

import Data.Binary
import Data.Binary.Put
import Control.Monad(foldM)
import qualified Data.ByteString.Lazy as B

main :: IO ()
main = do let ns = [1..10000000] :: [Int]
              (count,b) = runPutM $ foldM (\ c n -> c `seq` (put n >> return (c+1))) (0 :: Int) ns
          B.writeFile "testOut" b
          print count
当编译并使用
+RTS-hy
运行时,结果是一个几乎由testring值控制的三角形图


到目前为止,我发现的唯一解决方案(我不太喜欢)是使用
B.appendFile
在IO中进行循环(直接或使用
foldM
),而不是在Put中或直接构造一个构建器值,这在我看来不是很优雅。有更好的方法吗?

我有点惊讶于
toByteStringIO
不起作用,希望更熟悉该库的人能提供答案

也就是说,每当我想将流处理与IO操作混合使用时,我通常会发现迭代器是最优雅的解决方案。这是因为它们允许精确控制处理和保留的数据量,并允许将流方面与其他任意IO操作相结合。有关于黑客的报道;这个例子使用“iteratee”,因为它是我最熟悉的一个

import Data.Binary.Put
import Control.Monad
import Control.Monad.IO.Class
import qualified Data.ByteString.Lazy as B
import Data.ByteString.Lazy.Internal (defaultChunkSize)
import Data.Iteratee hiding (foldM)
import qualified Data.Iteratee as I

main :: IO ()
main = do 
  let ns = [1..80000000] :: [Int]
  iter <- enumPureNChunk ns (defaultChunkSize `div` 8)
                            (joinI $ serializer $ writer "testOut")
  count <- run iter
  print count

serializer = mapChunks ((:[]) . runPutM . foldM
   (\ !cnt n -> put n >> return (cnt+1)) 0)

writer fp = I.foldM
   (\ !cnt (len,ck) -> liftIO (B.appendFile fp ck) >> return (cnt+len))
   0
import Data.Binary.Put
进口管制
导入控制.Monad.IO.Class
将限定数据.ByteString.Lazy作为B导入
导入Data.ByteString.Lazy.Internal(defaultChunkSize)
导入数据。迭代对象隐藏(foldM)
导入符合条件的数据
main::IO()
main=do
设ns=[1..80000000]:[Int]
iter>返回(cnt+1))0
写入程序fp=I.foldM
(\!cnt(len,ck)->liftIO(B.appendFile fp ck)>>return(cnt+len))
0

这有三个部分
writer
是“迭代对象”,即数据消费者。它将每个数据块作为其接收的数据块写入,并保持长度的连续计数<代码>序列化程序是一种流转换器,也称为“枚举”。它接受类型为
[Int]
的输入块,并将其序列化为类型为
[(Int,B.ByteString)]
(元素数,ByteString)的流。最后,
enumPureNChunk
是“枚举器”,它从输入列表生成一个流。它需要从输入中提取足够的元素来填充单个lazy bytestring块(我使用64位,32位系统用4除),然后将它们写入磁盘,这样它们就可以被GC读取。

为什么8192与任何其他值不同?我在哪里可以找到
run
函数的定义和类型,因为我在iteratee中找不到它?尽管我认为您的解决方案说明了我在自己的解决方案中遇到的问题:似乎不可能通过testring构造
并同时计算有多少个值;您似乎必须在写入磁盘时执行此操作。哦,您所执行的操作有一个方面与我的原始版本不同:如果我理解您的代码,您将返回并打印保存到磁盘的字节数,而不是值的数目。因此,它是错误的:(@ivanm:8192是任意选择的,但是对于4字节的int,它非常接近
bytestring
s
defaultChunkSize
(来自Data.bytestring.Lazy.Internal)。最好使用
div defaultChunkSize 4
@ivanm:是的,它记录了字节数。如果您只使用
Int
s,您可以简单地除以
sizeOf(0::Int)
。但是,我编辑了代码,因此即使所有元素的大小不一定相同,它也可以工作。