为什么Java HashMap会变慢?

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

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> snsMap = new HashMap<Integer, List>(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 list = snsMap.get(key); //if the follower is not in the map if(snsMap.get(key) == null) list = new LinkedList(); 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?

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

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

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

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

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

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

 snsMap.put(key, list); 

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

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

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

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

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

如果你有很多Integer对象,它只需要4倍的内存,就好像你可以使用int基元存储所有内容一样。 这是您为Java实现的清洁OOP原则所付出的代价。

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

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

重要提示:在读取数据后关闭BufferedReader和File …这可能会有所帮助。

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

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

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