【问题标题】:HashMap alternatives for memory-efficient data storage用于内存高效数据存储的 HashMap 替代方案
【发布时间】:2011-04-27 16:37:36
【问题描述】:

我目前有一个电子表格类型的程序,它将其数据保存在 HashMap 的 ArrayList 中。当我告诉你这并不理想时,你无疑会感到震惊。开销似乎比数据本身使用了 5 倍以上的内存。

This question 询问高效的收藏库,答案是使用 Google 收藏。 我的跟进是“哪一部分?。我一直在阅读文档,但不觉得它可以很好地了解哪些类适合这个。 (我也愿意接受其他图书馆或建议)。

所以我正在寻找可以让我以最小的内存开销存储密集的电子表格类型数据的东西。

  • 我的列当前由 Field 对象引用,行由索引引用,值是对象,几乎总是字符串
  • 有些列会有很多重复值
  • 主要操作是根据某些字段的值更新或删除记录,以及添加/删除/组合列

我知道 H2 和 Derby 等选项,但在这种情况下,我不打算使用嵌入式数据库。

编辑:如果您建议图书馆,如果您能指出其中适用的一两个特定类,我也将不胜感激。虽然 Sun 的文档通常包含有关哪些操作是 O(1)、哪些是 O(N) 等的信息,但我在第三方库中没有看到太多这样的信息,也没有真正描述哪些类最适合什么.

【问题讨论】:

标签: java collections guava


【解决方案1】:

有些列会有很多 重复值

立即向我建议FlyWeight pattern 的可能用途,无论您为收藏选择何种解决方案。

【讨论】:

【解决方案2】:

Trove 集合 应该特别注意占用的空间(如果您坚持原始类型,我认为它们也有定制的数据结构).. 看看 here

否则,您可以尝试使用 Apache collections.. 进行基准测试!

无论如何,如果您有很多关于相同元素的引用,请尝试设计一些合适的模式(例如flyweight

【讨论】:

  • Trove 对我不起作用,因为我没有使用原语。我看到 Apache 集合中的 HashedMap 是一种“通用替代方案”,但他们没有给出任何与常规 HashMap 不同的解释。有什么见解吗?
  • 实际上,我看到它确实提到了添加迭代功能。但是,我的问题是性能不缺少功能。
【解决方案3】:

Chronicle Map 每个条目的开销可能少于 20 个字节(请参阅a test 证明这一点)。相比之下,java.util.HashMap 的开销从带有-XX:+UseCompressedOops 的 37-42 字节到没有压缩 oops (reference) 的 58-69 字节不等。

此外,Chronicle Map 将键和值存储在堆外,因此它不存储 Object 标头,这些标头不计入上述 HashMap 的开销。 Chronicle Map integratesChronicle-Values,一个用于生成接口的享元实现的库,另一个答案中的模式 suggested by Brian Agnew

【讨论】:

    【解决方案4】:

    所以我假设您有一张Map<ColumnName,Column> 的地图,其中的列实际上类似于ArrayList<Object>

    一些可能性 -

    • 您完全确定内存是个问题吗?如果您通常只是担心大小,那么值得确认这确实是正在运行的程序中的一个问题。填充 JVM 需要大量的行和映射。

    • 您可以使用集合中不同类型的地图来测试您的数据集。根据您的数据,您还可以使用可能有帮助的预设大小/负载因子组合来初始化地图。我过去曾搞砸过这个,如果幸运的话,你可能会减少 30% 的内存。

    • 如何将数据存储在一个类似矩阵的数据结构(现有的库实现或类似列表列表的包装器)中,使用一个将列键映射到矩阵列的映射?

    【讨论】:

    • 其实每条记录都是一个Map,其中Object就是每个字段的值。所有记录都包含在一个 ArrayList 中。内存肯定是个问题。加载 50MB 的文件有时会超过 1GB 的内存,这让我相信我当前的实现非常幼稚。
    • 我会用不同的选项做一些测试;我在这里要做的是将字段缩小到我可以比较的不同库中的几个特定类。
    • @bemace:您是否为每个记录 Map 实例重复使用相同的 Field 对象?
    • 第三点让我找到了使用 Colt 的 SparseMatrix 的最终解决方案(请参阅下面的答案)
    【解决方案5】:

    假设您的所有行都有大部分相同的列,您可以只为每一行使用一个数组,并使用一个 Map 来查找哪些列引用了哪个单元格。这样每个单元只有 4-8 字节的开销。

    如果字符串经常重复,您可以使用字符串池来减少字符串的重复。其他不可变类型的对象池可能有助于减少内存消耗。

    编辑:您可以将数据结构化为基于行或基于列。如果其基于行(每行一个单元格数组)添加/删除该行只是删除该行的问题。如果它基于列,则每列可以有一个数组。这可以使处理原始类型更加有效。即你可以有一列是int[],另一列是double[],更常见的是整列具有相同的数据类型,而不是整行具有相同的数据类型。

    但是,无论采用哪种方式构建数据,都将针对行或列修改进行优化,并且执行其他类型的添加/删除将导致整个数据集的重建。

    (我所做的是拥有基于行的数据并在末尾添加列,假设如果一行不够长,则该列具有默认值,这可以避免在添加列时重建。而不是删除列,我有办法忽略它)

    【讨论】:

    • 如果原始发帖人的价值观真的很密集,这会很好用。对象[][] 或列表。不要打折旧备用!添加 Field#getNumber() 并且你是金子。至于重复值,guava-libraries 的“内部”界面似乎符合要求。
    • 不错的主意,但您如何处理添加和删除具有这种结构的行/列?
    【解决方案6】:

    Guava 确实包含一个Table 接口和一个基于哈希的实现。似乎很适合您的问题。请注意,这仍被标记为测试版。

    【讨论】:

    • Guava Table 实现被实现为带有 Map 值的 Map。因此,它们不会减少内存使用量。
    • @Jared 我认为这将取决于使用的 Map 的实现?
    • @bemace 主要的 Map 实现——HashMap 和 TreeMap——在它们很多时会产生很大的内存开销。我创建了一个基于数组的表,它在密集时内存效率很高,但需要固定的行和列键,但它尚未在 Guava 中开源。
    【解决方案7】:

    将其数据保存在 HashMap 的 ArrayList 中
    好吧,这部分对我来说似乎非常低效。空 HashMap 已经分配了16 * size of a pointer 字节(16 代表默认初始容量),以及一些散列对象变量(14 + psize)。如果您有很多稀疏填充的行,这可能是个大问题。

    一种选择是使用单个大散列和复合键(组合行和列)。虽然,这并不能使整行的操作非常有效。

    另外,由于您没有提及添加单元格的操作,您可以创建只需要内部存储的哈希(initialCapacity 参数)。

    我对谷歌收藏了解不多,所以无法提供帮助。另外,如果您发现任何有用的优化,请在此处发布!知道会很有趣。

    【讨论】:

    • 我向你保证它非常低效,这就是我在这里的原因:) 在我的情况下,稀疏行不是一个大问题。
    【解决方案8】:

    我一直在尝试使用来自Colt 项目的SparseObjectMatrix2D。我的数据非常密集,但它们的 Matrix 类并没有真正提供任何放大它们的方法,所以我使用了一个设置为最大尺寸的稀疏矩阵。

    对于相同的数据,它似乎减少了大约 10% 的内存和大约 15% 的加载速度,并提供了一些巧妙的操作方法。不过仍然对其他选项感兴趣。

    【讨论】:

      【解决方案9】:

      根据您的描述,您似乎更想要一个 (Linked)HashMap 的 ArrayList(每个 ArrayList 将是一列),而不是 HashMaps 的 ArrayList。

      我会添加一个从字段名到列号的双重映射,以及一些从不抛出 IndexOutOfBoundsException 的聪明的 getter/setter。

      您还可以使用ArrayList<ArrayList<Object>>(基本上是一个锯齿状动态增长的矩阵)并将到字段(列)名称的映射保留在外部。

      有些列会有很多 重复值

      我怀疑这很重要,特别是如果它们是字符串(它们是内部化的)并且您的集合将存储对它们的引用。

      【讨论】:

        【解决方案10】:

        您为什么不尝试使用像EHCache 这样的缓存实现。 当我遇到同样的情况时,这对我来说非常有效。
        您可以简单地将您的集合存储在 EHcache 实现中。 有如下配置:

        Maximum bytes to be used from Local heap.
        

        一旦应用程序使用的字节溢出缓存中配置的内容,缓存实现就会负责将数据写入磁盘。您还可以配置使用最近最少使用算法将对象写入磁盘的时间量。 使用这种类型的缓存实现,您可以确保避免任何内存不足错误。 它只会稍微增加应用程序的 IO 操作。
        这只是配置的鸟瞰图。有很多配置可以优化您的要求。

        【讨论】:

          【解决方案11】:

          对我来说,apache commons 集合并没有节省任何空间,这里有两个类似的堆转储,就在 OOME 之前比较 Java 11 HashMap 和 Apache Commons HashedMap:

          Apache Commons HashedMap 似乎没有任何意义。

          【讨论】:

            猜你喜欢
            • 2012-01-31
            • 1970-01-01
            • 1970-01-01
            • 2010-09-24
            • 2011-10-28
            • 2020-09-06
            • 2020-07-02
            • 2016-04-16
            相关资源
            最近更新 更多