如果我的地图需要比快速小,那么我应该使用Map <k,v>的哪种实现?</k,v>

时间:2012-01-12 13:33:45

标签: java dictionary collections

我习惯在我的程序中使用HashMap,因为我知道它通常效率最高(如果使用得当)并且可以轻松应对大型地图。我知道EnumMap对于枚举键非常有用,但是我经常会生成一个永远不会很大的小地图,很快就会被丢弃,并且没有并发问题。

这些小型,本地和临时用途HashMap<K,V>是否过于复杂?在这些情况下,我可以使用另一个简单的实现吗?

我想我正在寻找Map实施,类似于ArrayList List。它存在吗?


在回复后添加:

这是一个缓慢但非常简单的实现可能更好的情况 - 当我有很多这些Map时。例如,假设我有一百万左右的这些微小的小地图,每个地图都有少量(通常少于三个)条目。我的参考率很低 - 也许我实际上并没有在大部分时间丢弃之前引用它们。仍然是HashMap是他们最好的选择吗?

资源利用率不仅仅是速度 - 我想要的东西不会破坏堆很多,并且使GC需要很长时间,例如。

可能HashMap是正确答案,但这不是过早优化的情况(或者至少可能不是)。


经过一番思考后添加了很多:

我决定手工编写自己的SmallMap代码。用AbstractMap制作一个很容易。我还添加了几个构造函数,以便可以从现有的SmallMap构建Map

在此过程中,我必须决定如何代表Entry并为SmallSet方法实施entrySet

我通过编码(并对其进行单元测试)学到了很多东西,并想分享这个,以防其他人想要一个。它在github上here

9 个答案:

答案 0 :(得分:18)

Java中没有Map的标准小实现。 HashMap是最好,最灵活的Map实施方案之一,很难被击败。但是,在非常小的需求区域 - 堆使用和构造速度至关重要 - 可以做得更好。

我在GitHub上实现了SmallCollections来演示如何做到这一点。我会喜欢关于我是否成功的一些评论。我无法确定。

虽然这里提供的答案有时是有帮助的,但总的来说,他们倾向于误解这一点。无论如何,回答我自己的问题,最终对我来说比给予它更有用。

这里的问题已经达到了目的,这就是为什么我“自己回答”。

答案 1 :(得分:12)

我认为这是不成熟的优化。你有记忆问题吗?创建太多地图会导致性能问题?如果不是,我认为HashMap没问题。

此外,看看API,我没有看到比HashMap更简单的东西。

如果您遇到问题,可以推出自己的Map实现,它具有非常简单的内部功能。但是我怀疑你会比默认的Map实现做得更好,而且你要确保你的新类有效。在这种情况下,您的设计可能存在问题。

答案 2 :(得分:4)

HashMap可能是最轻量级的简单集合。

有时,更有效的解决方案是使用POJO。例如如果您的键是字段名称和/或您的值是基元。

答案 3 :(得分:2)

HashMap是一个不错的选择,因为它提供了普通案例O(1)置列和获取。它不保证排序虽然像SortedMap实现(即TreeMap O(log n) puts和gets)但如果你没有要求排序那么HashMap更好。

答案 4 :(得分:1)

我同意@hvgotcodes认为这是过早的优化,但知道工具箱中的所有工具仍然很好。

如果你对地图中的内容做了很多迭代,那么LinkedHashMap通常比HashMap快得多,如果你有很多线程同时使用Map,那么ConcurrentHashMap通常是更好的选择。我不担心任何Map实现对于小型数据集都是低效的。通常情况下,如果您的哈希值不正确,或者某些事情导致其负载过少,则错误构造的地图很容易因大量数据而变得低效。

然后当然有些情况下HashMap完全没有意义,比如你有三个值,你总是用键0,1和2索引,但我假设你理解: - )

答案 5 :(得分:1)

HashMap使用更多或更少的内存(创建时),具体取决于您的初始化方式:更多存储桶意味着更多内存使用,但更快访问适用于大量项目;如果您只需要少量项目,则可以使用较小的值对其进行初始化,这样可以减少仍然很快的桶数(因为它们每个都会收到一些项目)。如果正确设置,则不会浪费内存(权衡主要取决于内存使用情况与速度的关系)。

对于堆碎片和GC循环浪费等等,Map实现对它们的作用并不多;这一切都归结为你如何设置它。理解这不是关于Java的实现,但是泛型(例如,不能假设像EnumMap这样的关键值的任何事情)哈希表(不是HashTable s)是最好的实现地图结构。

答案 6 :(得分:1)

Android有一个ArrayMap,旨在最大限度地减少内存。除了处于核心之外,它还在v4支持库中,理论上,它也应该能够为Oracle或OpenJDK JRE编译。这是指向the source of ArrayMap in a fork of the v4 support library on github的链接。

答案 7 :(得分:0)

有一个名为AirConcurrentMap的替代方案,在1K条目之上的内存效率高于我找到的任何其他Map,并且比基于键的操作的ConcurrentSkipListMap更快,并且比任何迭代Map更快,并且具有内部线程池并行扫描。它是一个有序的,即NavigableMap和ConcurrentMap。它可以免费用于非商业性的无源使用,并且可以使用或不使用来源进行商业许可。有关图表,请参阅boilerbay.com。完全披露:我是作者。

AirConcurrentMap符合标准,因此它在任何地方都是插件兼容的,即使对于常规地图也是如此。

迭代器已经非常快,尤其是1K以上的条目。高速扫描使用具有单次访问(k,v)回调的“访问者”模型,该回调达到Java 8并行流的速度。 AirConcurrentMap并行扫描超过Java 8并行流约4倍。线程访问者将split()和merge()方法添加到提醒map / reduce之一的单线程访问者:

static class ThreadedSummingVisitor<K> extends ThreadedMapVisitor<K, Long> {
    private long sum;
    // This is idiomatic
    long getSum(VisitableMap<K, Long> map) {
        sum = 0;
        map.getVisitable().visit(this);
        return sum;
    }

    @Override
    public void visit(Object k, Long v) {
        sum += ((Long)v).longValue();
    }

    @Override
    public ThreadedMapVisitor<K, Long> split() {
        return new ThreadedSummingVisitor<K>();
    }

    @Override
    public void merge(ThreadedMapVisitor<K, Long> visitor) {
        sum += ((ThreadedSummingVisitor<K>)visitor).sum;
    }
}
...
// The threaded summer can be re-used in one line now.
long sum = new ThreadedSummingVisitor().getSum((VisitableMap)map);

答案 8 :(得分:0)

我也很感兴趣,只是为了一个实验,我创建了一个地图,该地图仅在字段中存储键和值,最多允许5个条目。它消耗的内存少了4个,并且比HashMap快16倍 https://github.com/stokito/jsmallmap