如何对哈希映射进行序列化/反序列化?

10

我有一个包含数百万条目的大型哈希表,我想将其持久化到磁盘上,这样当再次从磁盘读取它时,就不需要再将键值对插入回哈希表中,以减少开销。

我尝试使用cereal库来实现这个目的,但似乎HashMap数据类型需要派生一个通用类型。有没有办法解决这个问题?


泛型继承有什么问题? - Cubic
1
要为自定义类型派生通用类型,我们需要编写类似以下的代码:data Something = Something Int Int deriving Generic如果数据类型在Hackage库中(除了向库维护者提交补丁之外),该怎么做呢? - donatello
嗯...好吧,我个人怀疑像这样序列化HashMap不会起作用,你最终将不得不使用另一种支持所需序列化的实现,但让我们看看其他人的意见。 - Cubic
1
在你放弃toList/fromList方法之前,你估计需要多长时间?在我的四年老笔记本上插入1000万个(Int,String)键值对需要25秒。 - ja.
@ja. 它很快,但序列化结构本身似乎很直观且容易(而且更有效率)。似乎从List/toList是唯一的方法,因为“通用”方式不起作用(请参见我对mergeconflict答案的评论)。 - donatello
你说服我不再尝试这样做了。我会使用一个普通的Map,它对我来说性能足够好。 - Michael Fox
5个回答

5
你可能可以使用独立生成器HashMap生成自己的Generic实例。你可能会收到孤儿实例的警告,但你可能不在意 :) 无论如何,我还没有尝试过这个,但应该值得一试...

2
我认为这是一个很好的答案。另外一个要点也许是避免孤立实例的技巧:只需定义一个包装HashMap的新类型并为该类型定义实例。当您需要序列化HashMap时,只需将其包装在您的类型中并进行序列化即可。 - Tener
1
我添加了一行代码:deriving instance (Generic k, Generic v) => Generic (H.HashMap k v)。但是 GHC 报错说 HashMap 的所有数据构造函数不在作用域内(即 它们并不都被导出)。看起来这种方法行不通啊,<叹气>。 - donatello

1

我不确定使用泛型是否是实现高性能的最佳选择。我的最佳建议实际上是编写自己的Serializable实例,就像这样:

instance (Serializable a) => Serializable (HashMap a) where
  ...

为了避免创建孤立的实例,您可以使用newtype技巧:
newtype SerializableHashMap a = SerializableHashMap { toHashMap :: HashMap a }
instance (Serializable a) => SerializableHashMap a where
  ...

问题是如何定义...
在实际尝试、实现和基准测试可能的解决方案之前,没有明确的答案。
一种可能的解决方案是使用toList/fromList函数并存储/读取HashMap的大小。
另一种解决方案(类似于使用泛型)是基于内部HashMap结构编写直接序列化。考虑到您实际上没有导出内部内容,这将成为泛型的工作。

1

目前,没有办法在不修改HashMap库本身的情况下使其可序列化。

无法使用@mergeconflict答案中描述的独立派生方式将Data.HashMap作为Generic的实例(用于cereal),因为Data.HashMap未导出所有构造函数(这是GHC的要求)。

因此,似乎唯一的序列化HashMap的解决方案是使用toList/fromList接口。


0
如果您可以使用二进制,那么 binary-orphans 可以为无序容器提供实例。由于某些 cabal 冲突,我无法安装 binary-orphans,但我只是获取了我需要的部分,例如:
{-# LANGUAGE CPP           #-}
{-# LANGUAGE DeriveGeneric #-}

module Bin where

import           Data.Binary
import           Data.ByteString.Lazy.Internal
import           Data.Hashable                 (Hashable)
import qualified Data.HashMap.Strict           as M
import qualified Data.Text                     as T

#if !(MIN_VERSION_text(1,2,1))
import           Data.Text.Binary              ()
#endif

instance  (Hashable k, Eq k, Binary k, Binary v) => Binary (M.HashMap k v) where
  get = fmap M.fromList get
  put = put . M.toList

-- Note: plain `encode M.fromList []` without type annotations won't work
encodeModel :: M.HashMap T.Text Int -> ByteString
encodeModel m =
  encode m

-1

你能否给我们提供一个最小化的示例,以便我们可以在终端上运行? - Geoff Langenderfer
1
@GeoffLangenderfer 抱歉,但是我目前的 Haskell 水平有些生疏(无恶意)。 - Carlos Reyes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接