【问题标题】:Java : HashSet vs. HashMapJava:HashSet 与 HashMap
【发布时间】:2015-03-31 10:30:39
【问题描述】:

我有一个处理大量数据集的程序。对象最好存储在散列实现的容器中,因为程序会一直在容器中寻找对象。

第一个想法是使用HashMap,因为这个容器的get和remove方法更适合我需要的用途。

但是,我发现 HashMap 的使用非常消耗内存,这是一个主要问题,所以我认为切换到 HashSet 会更好,因为它只使用<E>,而不是每个元素使用<K,V>,但是当我查看了我了解到它使用底层 HashMap 的实现!这意味着它不会节省任何内存!

这是我的问题:

  • 我的所有假设都是真的吗?
  • HashMap 内存浪费吗?更具体地说,每个条目的开销是多少?
  • HashSet 和 HashMap 一样浪费吗?
  • 是否还有其他基于哈希的容器会显着减少内存消耗?

    更新

根据 cmets 中的要求,我将对我的程序进行一些扩展,hashMap 旨在保存一对其他对象和一些数值 - 一个从它们计算的浮点数。在此过程中,它会提取其中的一些并输入新的对。给定一对它需要确保它不持有这对或删除它。映射可以使用浮点值或pair对象的hashCode来完成。

另外,当我说“庞大的数据集”时,我指的是 ~ 4*10^9 个对象

【问题讨论】:

  • 你的假设是什么?
  • 这是一个主要问题:是吗?您是否测量并证明在您的用例中使用 HashSet 确实消耗了太多内存?用例是什么?
  • @almasshaikh 我的假设是我的帖子中写的所有内容,特别是接下来的问题......
  • 您首先说您使用的是 HashMap 而不是使用 HashSet,尽管它们是完全不同类型的集合。我们所知道的关于您的用例(即您的应用程序必须做什么)是它必须处理“巨大的数据集”。我们不能用这么少的信息推荐任何解决方案。此外,我看到很多关于“巨大”数据集的问题,其中“巨大”实际上意味着“大约 1000 个”,这实际上很小,我更愿意问。
  • @petric,我编辑了我的answer,虽然它没有回答你所有的问题,但它包含一些提示可能有用。

标签: java memory-management hash hashmap hashset


【解决方案1】:

this site 上有关于 java 中的集合性能的非常有用的提示。

HashSet 建立在 HashMap< T, Object > 之上,其中 value 是 单例“当前”对象。这意味着 the memory consumption of aHashSet is identical to HashMap: 为了存储 SIZE 值,您需要 32 * SIZE + 4 * CAPACITY 字节(加上值的大小)。这绝对不是一个内存友好的集合。

THashSet 可能是HashSet 最简单的替换集合——它实现了 Set 和 Iterable,这意味着您应该只在集合的初始化中更新一个字母。

THashSet 使用单个对象数组作为其值,因此它使用 4 * CAPACITY 字节进行存储。如您所见,与 JDK HashSet 相比,在相同的负载因子的情况下,您将节省 32 * SIZE 个字节,这是一个巨大的改进。

下面这张来自here的图片可以帮助我们记住一些东西来选择合适的收藏

【讨论】:

  • @gknicker 为什么不链接 Image 的原始来源,而不是链接我之前没见过的答案!!??无论如何,谢谢您的评论。
【解决方案2】:

我所有的假设都是真的吗?

HashSet 是使用HashMap 实现的,这是正确的,因此使用HashSet 不会节省任何内存。

如果您要创建包含大量元素的地图,则应尽您所知使用initialCapacity 构造您的HashMaps,以防止重复重新散列(因此内存抖动)。

HashMap 内存浪费吗?更具体地说,它的开销是多少 每个条目?

不,这不是浪费。开销是一个底层数组(大小由loadFactor 修改)和每个键值对的Entry 对象。除了存储键和值之外,条目对象还存储指向槽中下一个条目的指针(以防两个或更多条目占用底层数组中的同一个槽)。 0.75 的默认 loadFactor 将底层数组大小保持在条目数的 133%。

非常具体地说,每个条目的内存开销是:

  • 入口对象对键的引用,
  • 入口对象对值的引用,
  • 条目对象对下一个条目的引用,
  • 以及底层数组对条目的引用(除以负载因子)。

与基于哈希的集合相比,很难获得更多的修剪。

HashSet 和 HashMap 一样浪费吗?

使用HashSet 代替HashMap 不会提高内存效率。

是否还有其他基于哈希的容器会显着 内存消耗少?

如果您的键是原语(例如ints),则有自定义的MapSet 实现(在third party libraries 中)使用内存效率更高的数据结构。

【讨论】:

  • 感谢您的回答,当使用“wastfull”这个词时,我的意思不是“不正确地完成他们的工作”,我的意思是选择使用它们会消耗每个项目的大量内存,因为使用许多引用,除了实际对象和键的大小之外,每个项目 2 个,对吗?
  • 不客气。我知道你的意思。我已经更新了我的答案,以更具体地说明开销。
【解决方案3】:

确实,HashSet 使用与 HashMap 一样多的内存。 HasSet 实现 Set 两者之间的区别,即它不关心与键关联的任何值,只关心特定值的存在与否。 HashMap 关注每个键的值的存储/检索(放置/获取)。

虽然 HashMap/HashSet 将数据存储在一个通常比元素数量略大的数组中,但这应该不是太大的问题,因为负载因子是 0.75。这意味着当元素数量达到底层数组大小的 75% 时,HashMap 会增长。

比大地图更令人担忧的是大量空地图,因为 HashMap 的默认大小是 16。这可以通过将初始容量设置为 0 来抵消。

您也可以改用 TreeMap,但是,由于 TreeMap 是基于引用而不是数组,您可能会浪费更多空间,尤其是对于较大的地图,此外还会损失一些速度。 TreeMap 的主要好处是它将键保持在有序状态,因此如果您需要对它们进行排序,这就是要走的路。

此外,当您不能或不想自定义实现您的键类型的 equalshashCode 方法时,可以出于编程原因使用 TreeMap。您可以改为为键类型制作比较器。例如,要基于不区分大小写的字符串制作地图/集合,请使用 String.CASE_INSENSITIVE_ORDER 作为 TreeSet 的比较器

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-23
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    • 2011-04-09
    • 1970-01-01
    • 2012-10-20
    • 2010-11-25
    相关资源
    最近更新 更多