【问题标题】:Why is Java HashMap slowing down?为什么 Java HashMap 变慢了?
【发布时间】:2012-05-24 22:57:51
【问题描述】:

我尝试使用文件内容构建地图,我的代码如下:

    System.out.println("begin to build the sns map....");
    String basePath = PropertyReader.getProp("oldbasepath");
    String pathname = basePath + "\\user_sns.txt";
    FileReader fr;
    Map<Integer, List<Integer>> snsMap = 
            new HashMap<Integer, List<Integer>>(2000000);
    try {
        fr = new FileReader(pathname);
        BufferedReader br = new BufferedReader(fr);
        String line; 
        int i = 1;
        while ((line = br.readLine()) != null) {
            System.out.println("line number: " + i);
            i++;

            String[] strs = line.split("\t");
            int key = Integer.parseInt(strs[0]);
            int value = Integer.parseInt(strs[1]);
            List<Integer> list = snsMap.get(key);
            //if the follower is not in the map
            if(snsMap.get(key) == null) 
                list = new LinkedList<Integer>();
            list.add(value);
            snsMap.put(key, list);
            System.out.println("map size: " + snsMap.size());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println("finish building the sns map....");
    return snsMap;

程序一开始非常快,但当打印的信息是:

 map size: 1138338
 line number: 30923602
 map size: 1138338
 line number: 30923603 
 ....

我尝试用两个 System.out.println() 子句来判断 BufferedReader 和 HashMap 的性能,而不是 Java 分析器。 有时获取行号信息后需要一段时间才能获取地图大小的信息,有时获取地图大小后需要一段时间才能获取行号信息的信息。我的问题是:这让我的程序变慢了?大文件的 BufferedReader 或大地图的 HashMap?

【问题讨论】:

  • 为什么要打两次电话get?不要将现有列表放到地图上。
  • 使用分析器,您不必猜测什么是慢什么是快...
  • 从 while 循环中删除 System.out.println 并重试。
  • 地图中有超过 110 万个 LinkedList。内存不足了吗?
  • 我对这个问题投了反对票,因为它不能用给出的信息客观地回答,正如所有答案中的推测所证明的那样。为了有效地查明 Java 性能问题,使用分析器(或类似分析器,如线程转储)是迄今为止最好的方法。

标签: java hashmap bufferedreader


【解决方案1】:

您将不得不使用一些分析工具检查您的程序,以了解它为何缓慢。 一般来说,文件访问比内存操作慢得多(除非你在内存中受到限制并且执行过多的 GC),所以猜测是读取文件可能在这里更慢。

【讨论】:

  • 但程序一开始很快,过一会就变慢了。我试图找出 System.out.println() 的问题。 @AKJ
  • 有时测量程序的行为会改变程序的特性。通过测量,我的意思是 System.out.println()。使用分析器;这是为此而生的。
  • 这是一个已知事实,大量使用System.out.println 会对性能产生重大影响。标准输出不利于调试性能问题。
【解决方案2】:

最好的方法是用分析器(例如,JProfile)运行你的程序,看看哪些部分是慢的。例如,调试输出也会减慢您的程序。

【讨论】:

    【解决方案3】:

    如果您在 Eclipse 内部对此进行测试,您应该意识到写入 stdout/stderr 会带来巨大的性能损失,因为 Eclipse 会在控制台视图中捕获该输出。在紧密循环中打印始终是一个性能问题,即使在 Eclipse 之外也是如此。

    但是,如果您抱怨的是处理 3000 万行后出现的速度下降,那么我敢打赌这是内存问题。首先它会因为强烈的 GC'ing 而变慢,然后它会因OutOfMemoryError 而中断。

    【讨论】:

    • 查看我的回复,了解如何使用 GNU Trove 原始集合将内存使用量减少大约 5-10 倍。粗略估计,HashMap&lt;Integer, List&lt;&gt;&gt; 每个条目至少需要 3*16 字节。双向链表再次需要每个存储条目至少 2*16 字节。 1m 个键 + 30m 个值 ~ 1 GB。还没有包括开销。使用 GNU trove TIntObjectHash&lt;TIntArrayList&gt;,每个键应该是 4+4+16 字节,每个值应该是 4 字节,所以 144 MB。两者的开销可能相似。绝对少 5-10 倍。
    • @Anony-Mousse 你真的应该在你的答案中包含所有这些很好的信息,让它更明显。此外,当您的答案在此页面上显而易见时,为什么要在我的下方宣传您的答案。
    • 好吧,从评论中看不出这里的另一个答案可能会指出如何使用更少的内存。我会将内存估计添加到我的答案中。
    【解决方案4】:

    哈希映射并不慢,但实际上它是映射中最快的。 HashTable 是地图中唯一的线程安全的,有时会很慢。

    重要提示:读取数据后关闭 BufferedReader 和文件...这可能会有所帮助。

    例如:br.close() 文件.close()

    请从任务管理器中检查您的系统进程,可能有太多进程在后台运行。

    有时 eclipse 是真正的资源繁重,所以尝试从控制台运行它来检查它。

    【讨论】:

      【解决方案5】:

      在进行分析之前,您不会知道什么是慢,什么不是。

      System.out 很可能会显示为瓶颈,然后您将不得不再次在没有它们的情况下进行分析。 System.out最糟糕的发现性能瓶颈的方法,因为这样做通常会增加更严重的瓶颈。

      您的代码的一个明显优化是移动该行

      snsMap.put(key, list);
      

      进入if 语句。您只需要在创建 new 列表时放置它。否则,put 只会用自己替换当前值。

      Integer 对象相关的Java 成本(尤其是Java 集合API 中整数的使用)主要是内存(因此是垃圾收集!)问题。有时,您可以通过使用原始集合(例如 GNU trove)获得显着收益,具体取决于您调整代码以有效使用它们的程度。 Trove 的大部分优势在于内存使用。绝对尝试重写您的代码以使用来自 GNU trove 的 TIntArrayListTIntObjectMap。我也会避免使用链表,尤其是对于原始类型。

      粗略估计,HashMap&lt;Integer, List&lt;Integer&gt;&gt; 每个条目至少需要 3*16 字节。双向链表再次需要每个存储条目至少 2*16 字节。 1m 个键 + 30m 个值 ~ 1 GB。还没有包括开销。使用 GNU trove TIntObjectHash&lt;TIntArrayList&gt;,每个键应该是 4+4+16 个字节,每个值应该是 4 个字节,所以 144 MB。两者的开销可能相似。

      Trove 使用较少内存的原因是这些类型专门用于原始值,例如int。它们将直接存储int 值,因此每个使用 4 个字节来存储。

      Java 集合HashMap 由许多对象组成。它大致看起来像这样:有Entry 对象分别指向一个键和一个值对象。这些必须是对象,因为泛型在 Java 中的处理方式。在您的情况下,密钥将是一个 Integer 对象,它使用 16 个字节(4 个字节标记,4 个字节类型,4 个字节实际 int 值,4 个字节填充)AFAIK。这些都是 32 位系统估计。因此HashMap 中的单个条目可能需要大约 16(条目)+ 16(整数键)+ 32(但为空的 LinkedList)字节的内存,所有这些都需要考虑用于垃圾回收。

      如果您有很多 Integer 对象,那么它只需要 4 倍于您可以使用 int 原语存储所有内容的内存。这是您为在 Java 中实现的干净 OOP 原则所付出的成本。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-27
        • 1970-01-01
        • 1970-01-01
        • 2011-09-23
        相关资源
        最近更新 更多