我知道当我们需要存储重复项时,我们更喜欢ArrayList
而不是HashSet
,而HashSet
使用hashCode()
函数来计算其数组中每个元素的索引。
所以,这意味着如果我们想要存储单个元素,那么ArrayList
应该比HashSet
花费更少的时间。如果我在任何地方都错了,请纠正我。
但是当我通过代码检查性能时,我会得到不同的行为。
案例1:
import java.util.*;
class HashsetVSArraylist
{
public static void main(String args[])
{
ArrayList<Integer> a1=new ArrayList<Integer>();
long nanos = System.nanoTime();
a1.add(1);
System.out.println("ArrayList Time:"+(System.nanoTime()-nanos)+"ns");
HashSet<Integer> h1=new HashSet<Integer>();
nanos = System.nanoTime();
h1.add(2);
System.out.println("HashSet Insertion Time:"+(System.nanoTime()-nanos)+"ns");
}
}
Output:
ArrayList Time:495087ns
HashSet Insertion Time:21757ns
案例2:
import java.util.*;
class HashsetVSArraylist
{
public static void main(String args[])
{
HashSet<Integer> h1=new HashSet<Integer>();
long nanos = System.nanoTime();
h1.add(2);
System.out.println("HashSet Insertion Time:"+(System.nanoTime()-nanos)+"ns");
ArrayList<Integer> a1=new ArrayList<Integer>();
nanos = System.nanoTime();
a1.add(1);
System.out.println("ArrayList Time:"+(System.nanoTime()-nanos)+"ns");
}
}
Output:
HashSet Insertion Time:582527ns
ArrayList Time:21758ns
现在,我认为HashSet
应该花更多时间插入单个元素。但是,在这两种情况下,行为都是不同的......代码中排在第二位的数据结构花费的时间更少。此外,当插入的元素数量超过1000时,行为也会发生变化。
请解释发生了什么。
答案 0 :(得分:2)
您的基准打破了。在尝试使用Java进行基准测试之前,请阅读:Dynamic compilation and performance measurement和:Anatomy of a flawed microbenchmark。
简短的解释是,您尝试衡量的总持续时间很长, 很多 太短,基准测试结果将被微小的操作系统淹没CPU详细信息,以及Java VM在开始运行时仍然忙于将字节码编译为机器代码的事实。
同时,这是一个有点疯狂的比较ArrayList和HashList性能时,他们有两个不同的目的,但所有其他条件相同,ArrayList中的实现的显著简单的,所以你的假设是肯定正确;它会更快。
答案 1 :(得分:1)
这里的真正问题是Java在将整数原语转换为Integer对象时所做的自动装箱。
当你调用a1.add(1)时,这实际上是调用a1.add(Integer.valueOf(1))
第一次引用Integer类的静态valueOf方法时,导致在Integer类中执行静态初始化程序,这会创建数百个静态对象,并且在系统上大约需要500ms。
即便如此,在幕后还会发生许多干扰此测试的其他事情,例如其他静态初始化程序,动态内存分配,系统资源分配以及无数其他内容。
如果您可以设计一个消除或最小化这些变量的测试,那么您会发现从长远来看,ArrayList的添加总是比HashSet快,但对于任何给定的插入都没有。幸运的是,我们永远不应该关心单次插入的速度。
例如,想象一下尝试向ArrayList添加值的几乎最坏的情况,但是ArrayList是它的最大分配大小。 ArrayList尝试分配更多空间,但系统已达到其当前内存分配上限,因此需要等待VM为系统分配更多内存。同时垃圾收集器开始了。在这种情况下,通常可能需要<1ms的插入可能需要多秒才能执行。