【问题标题】:HashSet of Strings taking up too much memory, suggestions...?字符串的 HashSet 占用太多内存,建议...?
【发布时间】:2011-03-14 18:33:10
【问题描述】:

我目前在 HashSet 中存储了一个单词列表(大约 120,000 个),目的是用作一个列表来检查输入的单词是否拼写正确,然后只返回是或否。

我想知道是否有一种方法可以占用更少的内存。目前 120,000 个单词大约是 12meg,实际读取单词的文件大约是 900kb。

有什么建议吗?

提前致谢

【问题讨论】:

  • 你怎么确定数据结构是12MB?
  • 只需要写一个小测试类

标签: java memory


【解决方案1】:

12MB 存储 120,000 个字大约是每个字 100 个字节。可能至少有 32 个字节是字符串开销。如果单词平均有 10 个字母并且它们被存储为 2 字节字符,那么这将占用另外 20 个字节。然后是对 HashSet 中每个字符串的引用,这可能是另外 4 个字节。剩下的 44 个字节可能是 HashSet 条目和索引开销,或者我上面没有考虑过的东西。

最容易解决的问题是 String 对象本身的开销,这可能会占用比存储实际字符数据所需的内存更多的内存。因此,您的主要方法是开发一种自定义表示,以避免为每个字符串存储单独的对象。在执行此操作的过程中,您还可以摆脱 HashSet 开销,因为您真正需要的只是一个简单的单词查找,这可以通过对将成为您的自定义实现的一部分的数组进行简单的二进制搜索来完成。

您可以将自定义实现创建为 int 类型的数组,每个单词都有一个元素。这些 int 元素中的每一个都将被分解为包含长度和偏移量的子字段,这些子字段指向一个单独的 char 类型的后备数组。将这两者放入管理它们的类中,并支持公共方法,允许您检索和/或转换给定字符串索引和可选字符索引的数据和单个字符,并在单词列表上执行简单搜索您的拼写检查功能所需的。

如果基础字符串数据的字符数不超过 16777216 个(例如,120,000 个字符串乘以 10 个字符的平均长度 = 120 万个字符),则可以取每个 int 的低 24 位并存储起始偏移量将每个字符串放入您的 char 数据的后备数组中,并取每个 int 的高 8 位并将相应字符串的大小存储在那里。

您的 char 数据会将您以前的字符串挤在一起,没有任何分隔符,完全依赖 int 数组来了解每个字符串的开始和结束位置。

采用上述方法,您的 120,000 个单词(平均每个单词 10 个字母)将需要大约 2,400,000 字节的支持数组数据和 480,000 字节的整数索引数据(120,000 x 4 字节),总共需要 2,880,000 字节,这比您上面报告的当前 12MB 量节省了大约 75%。

数组中的单词将按字母顺序排序,您的查找过程可以是对 int 数组的简单二进制搜索(从 char 数组中为每个测试检索相应的单词),这应该非常有效。

如果您的文字恰好完全是 ASCII 数据,您可以通过将支持数据存储为字节而不是字符来额外节省 1,200,000 字节。

如果您需要更改这些字符串,这可能会变得更加困难。显然,在你的情况下(拼写检查器),你不需要(除非你想支持用户添加到列表中,这无论如何都是不常见的,因此重写 char 数据和索引以添加或删除单词可能可以接受)。

【讨论】:

    【解决方案2】:

    这可能有点晚了,但是使用谷歌你可以很容易地找到我不久前发布的 DAWG 调查和 C 代码。

    http://www.pathcom.com/~vadco/dawg.html

    TWL06 - 178,691 字 - 适合 494,676 字节

    压缩共享节点结构的缺点是它不能作为列表中单词的哈希函数。也就是说,它会告诉你一个词是否存在,但它不会为一个确实存在的词返回相关数据的索引。

    如果您想要完美且完整的哈希功能,在处理器缓存大小的结构中,您将必须阅读、理解和修改称为 ADTDAWG 的数据结构。它会比传统的 DAWG 稍大,但速度更快,更有用。

    http://www.pathcom.com/~vadco/adtdawg.html

    一切顺利,

    约翰·保罗·阿达莫夫斯基

    【讨论】:

      【解决方案3】:

      你也可以试试Radix Tree(Wiki,Implementation)。这有点像trie,但内存效率更高。

      【讨论】:

        【解决方案4】:

        查看布隆过滤器或布谷鸟哈希。 Bloom filter or cuckoo hashing?

        我不确定这是否是您问题的答案,但值得研究这些替代方案。布隆过滤器主要用于拼写检查类型的用例。

        【讨论】:

        【解决方案5】:

        节省内存的一种方法是使用radix tree。这比trie 好,因为前缀没有冗余存储。

        由于您的字典是固定的,另一种方法是为它构建一个完美的哈希函数。您的哈希集不需要存储桶(以及相关的开销),因为不会发生冲突。使用 open addressing 的哈希表/哈希集的每个实现都可以用于此目的(如 google 集合的 ImmutableSet)。

        【讨论】:

          【解决方案6】:

          问题出在设计上:在 HashSet 中存储如此大量的单词以进行拼写检查并不是一个好主意:

          您可以使用拼写检查器(例如:http://softcorporation.com/products/spellcheck/),也可以使用前缀树(描述:http://en.wikipedia.org/wiki/Trie)构建“自动单词补全”。

          在这种设计中没有办法减少内存使用。

          【讨论】:

            【解决方案7】:

            HashSet 可能不是正确的结构。请改用Trie

            【讨论】:

              【解决方案8】:

              您可以使用前缀树或 trie:http://en.wikipedia.org/wiki/Trie

              【讨论】:

              • 我已经尝试实现来自 forums.sun.com/thread.jspa?threadID=5295936 的 trie 代码,底部有额外的建议,它的大小是 31meg 的 3 倍,这是怎么回事?!
              • 乍一看,children = new TrieNode[26];这行似乎不合理,它总是为所有潜在的孩子分配内存。
              • 虽然内存可能不大……如果您只想检查一个单词是否在字典中,那么保留对​​父级的引用也可能是多余的。
              猜你喜欢
              • 1970-01-01
              • 2015-10-14
              • 2013-07-18
              • 2013-07-11
              • 2020-05-18
              • 1970-01-01
              • 1970-01-01
              • 2010-09-18
              • 1970-01-01
              相关资源
              最近更新 更多