我如何解释Java8和Java7中HashMap的插入性能

我如何解释Java8和Java7中HashMap的插入性能,java,hashmap,java-8,Java,Hashmap,Java 8,我正在经历Java8HashMap的改进,它显示了使用二叉树将搜索性能提高了约20% 当时有一个想法是,插入性能会受到怎样的影响。所以我开始插入数百万条记录。下面是代码片段和不同的结果集 import java.util.HashMap; import java.util.Map; public class MapWriter { public static final int MAX_KEY = 1_000_000; private Map<Double, Double> m

我正在经历Java8HashMap的改进,它显示了使用二叉树将搜索性能提高了约20%

当时有一个想法是,插入性能会受到怎样的影响。所以我开始插入数百万条记录。下面是代码片段和不同的结果集

import java.util.HashMap;
import java.util.Map;

public class MapWriter {

public static final int MAX_KEY = 1_000_000;

private Map<Double, Double> map = new HashMap<>(MAX_KEY);


public static void main(String[] args) {
    long startTime = System.currentTimeMillis();

    MapWriter writer = new MapWriter();

    for (int i = 0; i < MAX_KEY; i++) {
        double random = Math.random();
        writer.map.put(random, random);
    }

    long timeTaken = System.currentTimeMillis() - startTime;

    System.out.println("Total Time Taken = " + timeTaken);
    System.out.println("Map Size = " + writer.map.size());
}

}
以下是不同的结果:

1000万次插入

爪哇7

所用总时间=23145 爪哇8

所用总时间=64964 200万次插入

爪哇7 所用总时间=6628 爪哇8 所用总时间=8312 100万次插入

爪哇7 所用总时间=3577 爪哇8 所用总时间=1212 结果表明,多达100万次的插入Java8的性能更好。但当你向上移动时,结果显示出相反的行为

我如何解释这种行为

更新:谢谢你们宝贵的反馈。我需要学习更多关于基准测试的知识。我预先初始化了Math.random部分,结果对于Java7和Java8都是一样的。这是修改后的代码。请让我知道,从基准测试的角度来看,代码是否仍然很臭

public class MapWriter {

public static int MAX_KEY = 1_000_000;


private Map<Double, Double> map = new HashMap<>(MAX_KEY);


public static void main(String[] args) {
    MAX_KEY = Integer.parseInt(args[0]);

    Double[] keys = new Double[MAX_KEY];

    for(int i = 0; i < MAX_KEY; i++) {
        keys[i] = Math.random();
    }

    MapWriter writer = new MapWriter();
    for (int i = 0; i < 100000; i++) {

        writer.map.put(keys[i], keys[i]);
    }

    writer.map = new HashMap<>(MAX_KEY);
    long startTime = System.nanoTime();


    for (int i = 0; i < MAX_KEY; i++) {
        double random = Math.random();
        writer.map.put(random, random);
    }

    long timeTaken = System.nanoTime() - startTime;

    System.out.println("Total Time Taken = " + timeTaken / 1000000);
    System.out.println("Map Size = " + writer.map.size());
}

}

我认为在运行基准测试时,您也在测试数学、随机和自动装箱。您必须将它们删除到基准范围之外。此外,我认为你必须重新运行测试几次,取平均值而不是单次运行。

< P>如果你编写自己的微基准而不是使用JMH之类的专用工具,你应该考虑一些要点:

使用用于测量经过的时间,以避免JVM外部触发的时间更改。 不要使用Math.random,除非您希望对Math.random进行基准测试。使用全局随机生成器之类的全局资源会添加线程同步,这可能会影响代码。 使用实例作为随机输入的源,但使用常量对其进行初始化,以确保要比较的运行确实执行相同的操作 在JVM中多次运行要进行基准测试的代码,以确保不会测量初始化开销,如类加载、首次内存分配、解释代码等

通常,在测试运行期间运行探查器是一个好主意,以验证时间是否真的花在了您想要基准测试的代码中

使用上面的最后一个项目,您的基准测试的主要问题很快就暴露出来了。您没有给JVM足够的初始内存,甚至最大内存也可能太有限,只运行了一次代码。所以,这里主要是测量内存管理的影响

请注意,Java8HashMap需要稍多的内存,当仅测量初始化成本时,甚至稍多的内存需求可能会产生很大的影响

为这两个JVM提供至少1GB的初始内存以进行1000万次插入,这导致在我的机器上,Java 8和Java 7分别需要5秒和6秒的时间,即使没有预热。远离你的20秒不超过一分钟


底线是,在对不同结果的原因做出假设之前,您需要使用不同的环境参数进行更多的跑步。当您在32位JVM、-server和-client、64位JVM上运行测试时,所有这些JVM都具有不同的内存设置,并且都得到一致的结果,显示某个特定版本更快或更慢,那么您可能会建议它就是该版本。但可能还有其他原因…

从这里开始:我认为这种基准测试方法太幼稚了。你可能没有考虑的因素太多了。有证据表明java 8的哈希图使用了二叉树吗?听起来不对。@EJP是的,请特别参阅从第143行开始的实现说明。简单地说,如果散列冲突导致任何一个bin中的人口过多,该bin将从链表转换为二叉树。我甚至会从平均值中排除第一次运行的结果,如果要允许运行时有一个宽限期,在这个宽限期内它可以正常唤醒。Java 8 Math.random和auto boxing不太可能变得那么慢……我还想说,HashMap不太可能受到性能的影响,但我们现在就到了。主要的一点是,当您进行性能测试时,您需要正确地进行测试,或者根本不进行测试。这也意味着尽可能多地消除影响因素。Java 8中引入的更改是仅用于查询性能还是用于所有方面?@Sotirios Delimanolis:您是指哈希冲突的二元搜索树?它首先是为了提高查找性能。然而,过去的某些其他尝试已经过时,这可能会提高整体性能。早期版本在每一个哈希代码上都应用了改进算法,现在已经简化了。是的,这就是我的意思。非常感谢。