Java hashmap与hashset性能

时间:2017-03-01 11:05:13

标签: java performance hashmap hashcode

我有一个由7.6M行组成的文件。每行的形式为:A,B,C,D其中B,C,D是用于计算A的重要性级别的值,A是每个行唯一的字符串标识符。我的方法:

private void read(String filename) throws Throwable {
        BufferedReader br  = new BufferedReader(new FileReader(filename));

        Map<String, Double> mmap = new HashMap<>(10000000,0.8f);
        String line;
        long t0 = System.currentTimeMillis();
        while ((line = br.readLine()) != null) {
            split(line);
            mmap.put(splitted[0], 0.0);
        }
        long t1 = System.currentTimeMillis();
        br.close();
        System.out.println("Completed in " + (t1 - t0)/1000.0 + " seconds");
}

private void split(String line) {
    int idxComma, idxToken = 0, fromIndex = 0;
    while ((idxComma = line.indexOf(delimiter, fromIndex)) != -1) {
        splitted[idxToken++] = line.substring(fromIndex, idxComma);
        fromIndex = idxComma + 1;
    }
    splitted[idxToken] = line.substring(fromIndex);
}

其中插入虚拟值0.0用于“分析”目的,splitted是为该类定义的简单String数组。我最初使用String的split()方法,但发现上面的更快。

当我运行上面的代码时,解析文件需要12秒,这比我认为应该花费更多。如果我,例如,用一个字符串向量替换HashMap并且只从每一行获取第一个条目(即我没有给它一个关联的值,因为它应该是分摊的常量),整个文件可以读取少于3秒。

这告诉我(i)HashMap中存在很多冲突(我试图通过预先分配大小并相应地设置加载因子来最小化调整大小的数量)或(ii)hashCode()函数某种程度上很慢。我怀疑它(ii)因为如果我使用HashSet,文件可以在4秒内读取。

我的问题是:HashMap执行速度如此之慢的原因是什么? hashCode()对于这个大小的地图是不够的,还是从根本上忽略了一些东西?

3 个答案:

答案 0 :(得分:3)

HashMap与Vector:在HashMap中插入比在Vector中插入更昂贵。虽然两者都是摊销的常量时间操作,但HashMap在内部执行许多其他操作(如生成hashCode,检查碰撞,解决碰撞等),而Vector只是在末尾插入元素(增加结构的大小,如果需要)。

HashMap vs HashSet: HashSet内部使用HashMap。因此,如果您将它们用于同一目的,则不应存在任何性能差异。理想情况下,这两者都有不同的目的,因此关于哪个更好的讨论是无用的。

因为,你需要B,C,D作为A的值作为键,你一定要坚持使用HashMap。如果你真的只想比较性能,可以将“null”而不是0.0作为所有键的值(因为这是HashSet在将键放入其支持的HashMap时使用的)。

更新:HashSet使用虚拟常量值(静态final)插入HashMap,而不是null。对于那个很抱歉。你可以用任何常量替换你的0.0,性能应该类似于HashSet。

答案 1 :(得分:2)

您可以使用更具内存效率的Collections库。

我建议Eclipse集合(https://www.eclipse.org/collections/),它有一个ObjectDoubleMap(https://www.eclipse.org/collections/javadoc/8.0.0/org/eclipse/collections/api/map/primitive/ObjectDoubleMap.html),它是一个对象的映射(在你的例子中是String),它有一个double(是的,原始的double)作为相关价值。它在处理内存和性能方面要好得多。

您可以通过执行以下操作获取此实例:

ObjectDoubleMaps.mutable.empty();

答案 2 :(得分:0)

是的,用0.0作为虚拟值VS静态最终常量作为虚拟值VS HashSet检查您的示例。这是粗略的比较,为了更好的精确度,我建议使用JHM工具,但我的HashSet性能与静态常量几乎相同,就像虚拟性能一样。

所以,最有可能的是,低性能是由于在编译期间为每一行包装0.0虚拟值(它被Double.valueOf()替换而导致的,这会显式创建一个新的Double对象时间)。

这可以解释低性能,因为HashSet具有预定义的静态最终虚拟对象(不是null,顺便说一句)。