为什么使用相同的HashMap迭代器读取密钥时,Java HashMap获取(密钥)的速度比使用Set的迭代器读取密钥要快?

对于HashMap< Integer,Integer>,在插入10000000个唯一随机值后.我使用hashmap的keySet()执行get(),如下面的代码片段所示:

HashMap<Integer,Integer> hashmap = 
                        new HashMap<Integer,Integer>(10000000,0.99f);

// ... Code to put unique 10000000 associations into the hashmap ...

int iteration = 100;
long startTime,totalTime = 0;

while(iteration > 0) {
    for(Integer key: hashmap.keySet()) {
       startTime = System.currentTimeMillis();
       hashmap.get(key);
       totalTime += (System.currentTimeMillis() - startTime);
    }
    iteration--;
}
System.out.println(totalTime/100 + " ms");

运行上面的代码,我得到:225毫秒

现在,如果我将上面的代码改为使用set,就像在下面的代码片段中一样:

Set<Integer> set = new HashSet<Integer>(hashmap.keySet());
while(iteration > 0) {
    for(Integer key: set) {
       startTime = System.currentTimeMillis();
       hashmap.get(key);
       totalTime += (System.currentTimeMillis() - startTime);
    }
    iteration--;
}
System.out.println(totalTime/100 + " ms");

运行此代码后,我得到:414毫秒

为什么这种性能差异?

P.S.:我使用了以下JVM参数:

-xms2048m -Xmx4096m -XX:MaxPermSize=256m

解决方法

当您读取大型数据结构(大于32 KB)时,您如何阅读该数据结构会影响性能.

这些是您缓存的典型大小和速度.

L1:   32 KB,4 clock cycles.
L2:  256 KB,11 clock cycles.
L3: 3-30 MB,40-75 clock cycles.
Main memory: up to 2TB,200-500 clock cycles.

这意味着缓存局部性非常重要.也就是说,如果你正在读取L1中的某些东西,那么它比从L3读取的速度快20倍.

在您的情况下,您正在使用哈希数据结构.这是为随机访问和随机排列而设计的,遗憾的是它具有非常差的可缓存性.随机访问内存,它可能在较慢的内存区域.

但是,这是一个例外.如果您多次访问相同的数据,例如从迭代器中获取一个键,或者按顺序扫描一个集合,例如这就是迭代器对HashMap(而不是TreeMap)所做的事情,你将访问的下一条数据更可能是在同一个缓存行(每个缓存行长度为64字节)或下一行.这些类型的访问执行得更好,因为cpu被设计为非常快速地执行向量操作.

BTW你的工作集就是一组键,如果你的值是不同的对象,我希望你实际看这些对象时会慢得多(因为这会增加工作集的大小以及缓存需要多少内存)它)

相关文章

HashMap是Java中最常用的集合类框架,也是Java语言中非常典型...
在EffectiveJava中的第 36条中建议 用 EnumSet 替代位字段,...
介绍 注解是JDK1.5版本开始引入的一个特性,用于对代码进行说...
介绍 LinkedList同时实现了List接口和Deque接口,也就是说它...
介绍 TreeSet和TreeMap在Java里有着相同的实现,前者仅仅是对...
HashMap为什么线程不安全 put的不安全 由于多线程对HashMap进...