Java 哈希集。在大场景中表现缓慢
我遇到了一个无法解决的问题。 我使用哈希集来存储值。我存储的值是自定义类型的Cycles,其中我覆盖了HashCode和equals,如下所示,以确保hascode或equal方法不会导致性能低下 我还将哈希集的初始容量设置为10.000.000Java 哈希集。在大场景中表现缓慢,java,performance,hashset,Java,Performance,Hashset,我遇到了一个无法解决的问题。 我使用哈希集来存储值。我存储的值是自定义类型的Cycles,其中我覆盖了HashCode和equals,如下所示,以确保hascode或equal方法不会导致性能低下 我还将哈希集的初始容量设置为10.000.000 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (cycleId ^ (cycleId
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (cycleId ^ (cycleId >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Cycle other = (Cycle) obj;
if (cycleId != other.cycleId)
return false;
return true;
}
在第一个1.500.000个第一个值之后,当我尝试添加新值(使用HashSet类的add方法)时,程序非常慢。最终,在存储值达到1.600.000之前,我将出现java内存不足异常(线程“thread-0”java.lang.OutOfMemoryError:java堆空间中的异常)
我使用的IDE是Eclipse。因此,下一步是将JVM堆大小从默认值增加到1GA(使用commnads Xmx1000M和Xms1000M)
现在,elipse的可用内存增加了10倍(我可以看到右下角显示了堆大小的内存和已用内存),但同样,在相同的值中(在1.500.000之后和1.600.000之前),我有相同的“慢”性能和相同的内存不足错误,这非常奇怪
有人知道可能是什么问题吗
提前感谢您可能您的计算机没有足够的内存,因此必须将其交换到磁盘。从Eclipse启动的应用程序的可用内存大小应通过运行菜单进行配置。尝试: 运行->运行配置->参数 ->虚拟机参数->-Xmx1000M
程序运行缓慢的原因是垃圾收集器——每次内存超出限制时,垃圾收集器都会启动。如果您想增加程序可以使用的内存,那么增加Eclipse的堆大小将无济于事。您必须将参数放入程序的启动配置的vm参数中。您是否测试了
hashCode
方法实现?对于圆圈ID
的任何值,它总是返回31
。毫不奇怪,HashMap运行缓慢,它具有线性性能。JVM抛出的“内存不足”不是基于可用内存。当花费在垃圾收集上的时间太长时,会抛出此消息。具体的实现细节因JVM和垃圾收集器实现而异
在这种情况下,增加内存没有帮助。您可能必须选择另一种方法。堆内存不足(通过-Xmx增加它,例如-Xmx512m
)。当可用内存非常低时,垃圾收集器会花费大量时间,疯狂地扫描堆以查找无法访问的对象
您的hashCode()很好,使用cycleId
long的所有位需要额外加分
编辑。现在我看到你确实增加了记忆力,但没有帮助。首先,你确定你确实增加了内存吗?您可以通过jconsole检查这一点,连接到您的应用程序并查看其堆大小
要验证另一种解释,您的cycleId
中是否有任何特定的模式会使这个hashCode()实现变得不好?它的32个高阶位与32个低阶位基本相似。(是的,对)
但不会。即使是这样,您也会看到性能逐渐下降,而不是在某个特定点急剧下降(而且您确实会遇到OutOfMemoryError和frenzy gc操作)。所以我最好的猜测还是记忆问题您可能没有像您所想的那样增加堆大小,或者在某个时候有其他一些代码占用内存。(您可以使用VisualVM之类的工具对此进行分析,并在OOME上获得堆转储,然后查看它包含哪些对象)
Edit2我将上面正确的部分加粗。您如何初始化
哈希集?你需要了解它的增长模式。每次add
操作时,它都会检查是否接近容量。如果它达到某一点(由其“负载系数”决定),它将执行一个可能代价高昂的调整大小操作。从JavaDoc(属于HashMap
——支持HashSet
)的集合中:
作为一般规则,默认负载系数(.75)在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置初始容量时,应考虑map中的预期条目数及其负载系数,以尽量减少再灰化操作次数。如果初始容量大于最大入口数除以负载系数,则不会发生再灰化操作
您不希望增加Eclipse的JVM堆,而是希望为您的程序设置它
转到Run>Run Configurations(或Debug Configurations)并在那里设置VM选项。我对告诉OP在应用程序中增加堆大小的答案数量感到非常失望。这不是一个解决方案——这是一个快速而肮脏的补丁,不会解决任何潜在问题
我发现这篇演讲内容非常丰富:
主要是列出每个空时的最小字节大小的页面--
事实证明,HashSet实际上是一个HashMap,并且(与直觉相反)占用了更多的内存,尽管它只保存值而不是键值对。cycleId到底是什么?如果它是标识中的ID,因此对于循环是唯一的,那么只需将cycleId作为hashcode返回即可。如果它不是一个整数,那么使用它是什么类型的哈希代码。如果它是一个64位,并且ID从0开始(具有偶数分布或大部分位于较低的32位),那么将其强制转换为int.@lassespeholt,为什么?那么hashcode将只依赖于long!使用所有
ArrayList: 40 or 48
LinkedList: 48
HashMap: 56 or 120
HashSet: 72 or 136