为什么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<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
....
我试着用两个系统来解释。出来println()子句来判断BufferedReader和HashMap的性能,而不是Java探查器。 有时在获取线号信息后需要一段时间才能获取地图大小信息,有时在获取地图大小后需要一段时间才能获取线号信息。我的问题是:是什么让我的程序变慢了?大文件的BufferedReader还是大映射的HashMap
# 1 楼答案
最好的方法是使用profiler(例如,JProfile)运行程序,并查看哪些部分比较慢。例如,调试输出也会降低程序的速度
# 2 楼答案
如果您是从Eclipse内部进行测试的,那么您应该知道写入stdout/stderr会对性能造成巨大的损失,因为Eclipse在控制台视图中捕获了该输出。即使在Eclipse之外,在紧密循环中打印也总是一个性能问题
但是,如果你抱怨的是在处理了3000万行之后速度变慢,那么我敢打赌这是内存问题。首先,它由于强烈的GC’ing而减慢,然后随着
OutOfMemoryError
而中断# 3 楼答案
你必须用一些分析工具来检查你的程序,以了解为什么它很慢。 一般来说,文件访问比内存中的操作慢得多(除非您在内存中受到限制,并且执行了过多的GC),所以这里读取文件的速度可能会更慢
# 4 楼答案
哈希映射并不慢,但实际上它是所有映射中速度最快的。HashTable是地图中唯一一个线程安全的,有时速度会很慢
重要提示:读取数据后关闭BufferedReader和文件。。。这可能会有帮助
例句:br。关闭() 文件关闭()
请从task manager检查您的系统进程,可能有太多进程在后台运行
有时候eclipse是真正的资源密集型的,所以试着从控制台运行它来检查它
# 5 楼答案
在你分析之前,你不会知道什么是慢的,什么不是。
最有可能的是,
System.out
将显示为瓶颈,然后您将不得不再次在没有它们的情况下进行分析System.out
是查找性能瓶颈所能做的最糟糕的事情,因为这样做通常会增加更糟糕的瓶颈对你的代码的一个明显的优化就是移动代码行
进入
if
语句。你只需要在创建一个新的列表时把它放进去。否则,put将用自身替换当前值与
Integer
对象相关的Java开销(尤其是在Java Collections API中使用整数)在很大程度上是一个内存(因此垃圾收集!)问题有时,通过使用原始集合(如GNU trove)可以获得显著的收益,这取决于您调整代码以有效使用它们的程度。Trove的大部分好处在于内存使用。一定要尝试重写代码,使用GNU-trove中的TIntArrayList
和TIntObjectMap
。我也会避免使用链表,尤其是对于基本类型粗略估计,
HashMap<Integer, List<Integer>>
每个条目至少需要3*16字节。双重链接列表同样需要每个存储的条目至少2*16字节。1m键+30m值~1GB。还不包括管理费用。对于GNU-troveTIntObjectHash<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
对象,它只会占用的内存是使用int
原语存储所有内容的4倍。这是在Java中实现的干净OOP原则的成本