为什么Java HashMap会变慢?

时间:2012-05-17 11:03:21

标签: java hashmap bufferedreader

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

    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?

5 个答案:

答案 0 :(得分:3)

如果从Eclipse内部进行测试,那么你应该知道写入stdout / stderr会导致巨大的性能损失,因为Eclipse会在Console视图中捕获这个ouptut。即使在Eclipse之外,在紧密循环内打印也始终是一个性能问题。

但是,如果您抱怨的是处理3000万行后经历的减速,那么我敢打赌这是一个内存问题。首先它由于强烈的GC'而减慢,然后它与OutOfMemoryError打破。

答案 1 :(得分:2)

您必须使用一些分析工具检查您的程序,以了解它为什么慢。 一般情况下,文件访问比内存操作要慢得多(除非你受限于内存和多余的GC),所以猜测读取文件可能会慢一些。

答案 2 :(得分:2)

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

最有可能的是,System.out将显示为瓶颈,然后您将不得不在没有它们的情况下进行分析。 System.out是您可以为查找性能瓶颈所做的最差事情,因为这样做通常会增加更糟糕的瓶颈。

对代码的一种显着优化是移动行

snsMap.put(key, list);

进入if语句。您只需在创建列表时将其放入。否则,put将只替换当前值。

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

粗略估计,HashMap<Integer, List<Integer>>每个条目至少需要3 * 16个字节。双向链表再次需要存储每个条目至少2 * 16个字节。 1米键+ 30米值~1 GB。尚未包含任何开销。使用GNU特洛伊TIntObjectHash<TIntArrayList>,每个密钥应为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原则所付出的代价。

答案 3 :(得分:0)

最好的方法是使用分析器运行程序(例如,JProfile)并查看哪些部分很慢。例如,调试输出也会降低程序的速度。

答案 4 :(得分:0)

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

重要提示:在您阅读数据后关闭BufferedReader和File ...这可能有所帮助。

例如:br.close()      file.close()

请从任务管理器检查系统进程,可能还有进程在后台运行。

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