以最快的速度实现少量条目的 Java Map

2022-09-03 02:25:17

对于极少数条目(少于15个元素左右)的java.util.Map的最快实现是什么?线程安全和非线程安全。


答案 1

如果所有条目都可以表示为枚举,请使用枚举映射

此实现将 Map 接口的丰富性和安全性与接近数组的速度相结合。如果要将枚举映射到值,应始终优先使用枚举映射而不是数组。

如果不是,HashMap是很好的解决方案。它为基本操作提供恒定时间,例如和:get()put()

此实现为基本操作(get 和 put)提供常量时间性能,前提是哈希函数将元素正确分散在存储桶中。

只需重新注册即可在哈希映射中设置低值:capacity

因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载因子太低)非常重要。

当然,上述实现不是线程安全的。在这种情况下,最好的线程安全实现将是 ConcurrentHashMap。它将HashMap的高性能与线程安全相结合。

以下是不同 Map 实现的良好比较。

编辑:这是不同实现的有趣比较。看起来,至少对于基元类型,有比内置更快的替代方案。HashMapHashMap

编辑2:由于彼得·劳雷的回答,我决定进行一些测试,比较和:ConcurrentHashMapCollections.synchronizedMap()

public static void main(String[] args) {
      System.out.println("\n===== ConcurrentHashMap =====");
      testMap(new ConcurrentHashMap<>());
      System.out.println("\n===== Collections.synchronizedMap()  =====");
      testMap(Collections.synchronizedMap(new HashMap<>()));
}

    static final int N = 5;
    static final int R = 1000000;

    public static void testMap(Map map) {
        long startTime = System.nanoTime();

        System.out.println("\n-- " + N*R + " puts(key, value) --");
        startTime = System.nanoTime();
        for (int j = 0; j < R; j++) {
            for (int i = 0; i < N; i++) 
                map.put(i, new float[] { 0f, 1f, 2f, 3f, 4f });
            map.clear();
        }
        System.out.println((System.nanoTime() - startTime) / 1000000000.0);

        System.out.println("\n-- " + N*R + " get(key) --");
        startTime = System.nanoTime();
        for (int j = 0; j < R; j++) {
            for (int i = 0; i < N; i++)  
                map.get(i); 
            map.clear();
        }
        System.out.println((System.nanoTime() - startTime) / 1000000000.0);
    }

}

我的结果是:

===== ConcurrentHashMap =====

  • 5000000 看跌期权(键、值) - 0.99714195 s

  • 5000000 获取(密钥) - 0.452227427 s

===== Collections.synchronizedMap() =====

  • 5000000 看跌期权(键,值) - 0.586431367 s

  • 5000000 获取(密钥) - 0.376051088 s

所以,彼得可能是对的 - 对于小地图,速度更快。Collections.synchronizedMap()


答案 2

如果你想要一个紧凑的地图,你可以使用

Map map = Collections.synchronizedMap(new HashMap<>());

如果你不需要线程安全,请删除 And 如果你希望集合使用尽可能少的内存,你可以做类似的事情。Collections.synchronizedMap

Map map = new HashMap<>(4, 1.0f);

这将使用最少的内存存储4个键/值(或更多)。它的大小大约是空的标准HashMap的一半。

使用 ConcurrentHashMap 的问题在于,对于一个小集合来说,它的权重很重,也就是说,它使用许多对象,如果为空,则大约有 1 KB 的内存。

要获得准确的内存使用情况,您需要在命令行上使用-XX-UseTLAB

public static void main(String sdf[]) throws Exception {
    testCreateSize("ConcurrentHashMap", ConcurrentHashMap::new);
    testCreateSize("HashMap", HashMap::new);
    testCreateSize("synchronized HashMap", () -> {
        return Collections.synchronizedMap(new HashMap<>());
    });
    testCreateSize("small synchronized HashMap", () -> {
        return Collections.synchronizedMap(new HashMap<>(4, 2.f));
    });

}

public static void testCreateSize(String description, Supplier<Map<Integer, Integer>> supplier) {
    // warmup.
    supplier.get();
    long start = memoryUsed();
    Map<Integer, Integer> map = supplier.get();
    for (int i = 0; i < 4; i++) {
        map.put(i, i);
    }
    long used = memoryUsed() - start;
    System.out.printf("Memory used for %s, was %,d bytes%n", description, used);

}

public static long memoryUsed() {
    return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}

指纹

Memory used for ConcurrentHashMap, was 272 bytes
Memory used for HashMap, was 256 bytes
Memory used for synchronized HashMap, was 288 bytes
Memory used for small synchronized HashMap, was 240 bytes

通过在使用映射的类中使用同步方法,可以再节省 32 个字节。


推荐