【问题标题】:What is wrong with Map<Object, Collection<Object>>?Map<Object, Collection<Object>> 有什么问题?
【发布时间】:2013-04-05 15:22:01
【问题描述】:

我正在尝试在 Java 中构建一个数据结构,我将在其中插入大约 200,000 个字符串键,每个“平均”为 1000 个整数 Map&lt;String, Arraylist&lt;Integer&gt;&gt;。该地图最终将有大约 2 亿个值。

问题是在插入时,我必须首先检查映射中是否存在键,如果为真,则获取存储在临时集合中的所有值,然后将新整数添加到集合中并将它们放回映射中,或者用一个新的整数实例化一个新的集合。

当我到达一个集合包含大约 50000 个整数的地步时,这太慢了。我通常会遇到 java out of heap space 错误。

有没有办法摆脱 get 过程?我只检查键是否存在,然后立即将值添加到现有集合中,例如将 posh 添加到堆栈中,尤其是映射在内存中,或者它是导致 Java 和 C++ 之间差异的原因,在 C++ 中我可以从使用指针中受益吗?

保持这样一个事实,即我不喜欢通过使用多图之类的东西来增加地图的大小,因为结构看起来几乎很简单。

非常感谢。

【问题讨论】:

  • Multimap 实现不会比Map&lt;String, ArrayList&lt;Integer&gt;&gt; 消耗更多的内存。
  • 你为什么不给我们看一些代码?
  • 实际上,如果在地图中找不到键,您只需要执行 put。添加相关的 SSCCE。
  • 200,000,000 个对象?希望你有一个 64 位系统。第一步是从Integer 移动到至少int(需要int[] 而不是List),或者您可以使用BitSet 之类的东西。 IIRC,它是-Xmx 来提高最大堆大小。
  • 告诉我们更多关于键和值的结构。您可以使用一些技巧来提高效率,甚至比下面的建议还要多。我在想,如果您的按键设计得很好,TrieMap 可能会很有用。

标签: java data-structures map multimap


【解决方案1】:

如果您的代码确实按照您的问题建议进行操作,则说明您工作太努力了。一旦你的 Key 与 ArrayList 相关联。只需将 ArrayList 从地图中取出并将新整数添加到该列表中即可。你不需要“放回去”。对 List 的引用是您更改 List 所需的全部内容。

    Map<String, ArrayList<Integer>> m = new HashMap<String, ArrayList<Integer>>();
    for ( int i = 0; i < 5; i++ ) {
        String key = ( i % 2 == 0 ) ? "Bob" : "Robert";
        ArrayList<Integer> l = m.get( key );
        if ( l == null ) {
            l = new ArrayList<Integer>();
            m.put( key, l );
        }
        l.add( i );
    }
    System.out.println( "m is " + m );

不过,在我看来,Guava Multimap 是解决这个问题的更好方法:http://guava-libraries.googlecode.com/svn/tags/release03/javadoc/com/google/common/collect/Multimap.html

【讨论】:

  • 在紧要关头,您可能可以使用Multimaps.newListMultimap 和fastutil 的IntList 来避免被装箱的Integers。
  • 你知道,200,000,000 个整数将迫使你对 Java 的堆空间做一些事情,而不管实现如何。我的 microbenchmarked 几乎同时被 Guava 的 MultiMap 和上面的中上层纯 Java 卡住了; 51,250,000。如果我的数学是正确的,那么 200,000,000 个整数就需要大约 3GB 的内存(我认为它们每个最终都是 16 个字节)。我认为您需要对预期大小进行一些数学运算,以弄清楚如何调整 JVM 以适应这一点。
【解决方案2】:
  1. HashMap 调整大小会产生巨大的性能开销。当您使用无参数构造函数创建新的 HashMap 时,其大小默认为 16。您将越来越多的元素放入其中,因此任何时候超出可用空间时,它都需要调整大小。调整大小涉及计算每个键的哈希码以及在哈希表之间移动键。它非常昂贵。

如果你知道你的 HashMap 会存储很多键,你可以创建它,例如 200,000。

  1. ArrayList 的默认容量为 10。如果放置更多元素,则需要调整大小。这涉及创建新数组(其中 ArrayList 内部存储元素)并将元素从旧数组复制到新数组。这在大型 ArrayList 上也可能非常昂贵。

我建议改用 LinkedList。向其中添加新元素非常便宜,因为元素被存储为独立节点。但是,也有一些缺点。详情请见this question

  1. 您必须能够为 200,000,000 个对象保留足够的内存。正如 Tom Hawtin 所建议的,增加 JVM 使用的最大堆空间可能是必要的。 Java 不是 C++,不能只是使用越来越多的内存。

【讨论】:

    猜你喜欢
    • 2019-05-14
    • 2018-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 2016-06-16
    • 1970-01-01
    相关资源
    最近更新 更多