Java 哈希集代码的意外运行时间
所以最初,我有以下代码:Java 哈希集代码的意外运行时间,java,performance,for-loop,hashset,Java,Performance,For Loop,Hashset,所以最初,我有以下代码: import java.util.*; public class sandbox { public static void main(String[] args) { HashSet<Integer> hashSet = new HashSet<>(); for (int i = 0; i < 100_000; i++) { hashSet.add(i); }
import java.util.*;
public class sandbox {
public static void main(String[] args) {
HashSet<Integer> hashSet = new HashSet<>();
for (int i = 0; i < 100_000; i++) {
hashSet.add(i);
}
long start = System.currentTimeMillis();
for (int i = 0; i < 100_000; i++) {
for (Integer val : hashSet) {
if (val != -1) break;
}
hashSet.remove(i);
}
System.out.println("time: " + (System.currentTimeMillis() - start));
}
}
import java.util.*;
公共类沙箱{
公共静态void main(字符串[]args){
HashSet HashSet=新的HashSet();
对于(int i=0;i<100_000;i++){
添加(i);
}
长启动=System.currentTimeMillis();
对于(int i=0;i<100_000;i++){
for(整数val:hashSet){
如果(val!=-1)中断;
}
hashSet.remove(i);
}
System.out.println(“时间:”+(System.currentTimeMillis()-start));
}
}
在我的计算机上运行嵌套for循环大约需要4秒,我不明白为什么需要这么长时间。外部循环运行100000次,内部for循环应该运行1次(因为hashSet的任何值都不会是-1),并且从hashSet中删除一个项是O(1),因此应该有大约200000个操作。如果一秒钟内通常有100000000个操作,为什么我的代码需要4s才能运行
另外,如果行hashSet.remove(i)代码>被注释掉,代码只需要16毫秒。
如果内部for循环被注释掉(但不是hashSet.remove(i);
),代码只需要8毫秒。您已经创建了hashSet
的一个边际用例,其中算法降低为二次复杂度
以下是耗时很长的简化循环:
for (int i = 0; i < 100_000; i++) {
hashSet.iterator().next();
hashSet.remove(i);
}
突出显示的行是一个线性循环,用于搜索哈希表中的第一个非空bucket
由于Integer
具有平凡的hashCode
(即hashCode等于数字本身),结果表明,连续整数大多占据哈希表中的连续存储桶:数字0进入第一个存储桶,数字1进入第二个存储桶,等等
现在删除0到99999之间的连续数字。在最简单的情况下(当bucket包含一个键时),键的删除被实现为将bucket数组中的相应元素置零。请注意,移除后,工作台未压实或重新灰化
因此,从bucket数组开始移除的键越多,HashIterator查找第一个非空bucket所需的时间就越长
尝试从另一端移除键:
hashSet.remove(100_000 - i);
算法将大大加快 我确认你的发现。我可以推测原因,但希望聪明的人能给出一个有趣的解释。看起来val的循环占用了很多时间。删除
的速度仍然很快。在修改集合后设置新迭代器会产生某种开销…?@apangin在中很好地解释了for val
循环速度慢的原因。但是,请注意,根本不需要循环。如果您想检查集合中是否有与-1不同的值,那么检查hashSet.size()>1 | |将更加有效!hashSet.contains(-1)
。啊,我遇到了这个问题,但在最初几次运行后就放弃了它,并认为这可能是某种JIT优化,于是转向通过JITWatch进行分析。应该先运行异步探查器。该死很有趣。如果在循环中执行以下操作,它会通过减小内部映射的大小来加速循环:If(i%800==0){hashSet=newhashset(hashSet);}
。
hashSet.remove(100_000 - i);