优化java.util.Map和java.util.Set的实现?

时间:2009-05-14 20:04:43

标签: java performance collections map set

我正在编写一个应用程序,其中内存以及在较小程度上的速度至关重要。我从剖析中发现,我花了很多时间在Map和Set操作中。虽然我在考虑减少调用这些方法的方法,但我想知道是否有人在编写或遇到过显着改进访问时间或内存开销的实现?或者至少,在某些假设的情况下,这可以改善这些事情吗?

从JDK源代码来看,我无法相信它不能更快​​或更精简。

我知道Commons Collections,但我不相信它有任何实现,其目标是更快或更精简。 Google Collections也是如此。

更新:应该注意到我不需要线程安全。

17 个答案:

答案 0 :(得分:11)

通常这些方法非常快。 您应该检查几件事情:您的哈希码是否已实施?它们是否足够均匀?否则你会得到垃圾表现。

http://trove4j.sourceforge.net/< - 这有点快,节省了一些内存。我在50,000次更新中节省了几毫秒

您确定正确使用地图/套装吗?即不试图迭代所有的值或类似的东西。另外,例如,不要做一个包含然后删除。只需检查删除。

同时检查你是否使用Double vs double。我注意到,在成千上万次检查中,性能有了几分之一的提升。

您是否也正确/适当地设置了初始容量?

答案 1 :(得分:7)

你看过Trove4J了吗?来自网站:

  

Trove旨在提供java.util.Collections API的快速轻量级实现。

基准提供here

答案 2 :(得分:6)

除Google和Commons Collections外,以下是我所知道的:

当然,您始终可以实现自己的数据结构,这些结构针对您的用例进行了优化。为了能够更好地提供帮助,我们需要了解您访问模式以及您在集合中存储的数据类型。

答案 3 :(得分:4)

尝试提高equals和hashCode方法的性能,这有助于加快标准容器对象的使用。

答案 4 :(得分:2)

您可以扩展AbstractMap和/或AbstractSet作为起点。我不久前做了这个来实现基于二元trie的映射(键是一个整数,树上的每个“级别”都是一个位置。左子是0,右子是1)。这对我们来说效果很好,因为密钥是EUI-64标识符,对我们来说大多数时候前5个字节都是相同的。

要实现AbstractMap,您至少需要实现entrySet()方法,以返回一组Map.Entry,每个Map.Entry都是一个键/值对。

要实现集合,可以扩展AbstractSet并提供size()和iterator()的实现。

但是,这至少就是这样。您还需要实现get和put,因为默认映射是不可修改的,并且get的默认实现遍历entrySet寻找匹配。

答案 5 :(得分:2)

您可以通过以下方式节省一点内存:

(a)使用更强,更宽的哈希码,从而避免存储密钥;

(b)通过从数组中分配自己,避免为每个哈希表条目创建单独的对象

如果它有用,这里是 Numerical Recipies 哈希表的简洁Java实现,我有时会发现它很有用。你可以直接键入CharSequence(包括字符串),否则你必须自己为你的对象提出一个强大的64位哈希函数。

请记住,此实现不会存储密钥,因此如果两个项目具有相同的哈希码(您希望在哈希值为2 ^ 32或几十亿之后)如果你有一个很好的哈希函数项,那么一个项将覆盖另一项:

public class CompactMap<E> implements Serializable {
  static final long serialVersionUID = 1L;

  private static final int MAX_HASH_TABLE_SIZE = 1 << 24;
  private static final int MAX_HASH_TABLE_SIZE_WITH_FILL_FACTOR = 1 << 20;

  private static final long[] byteTable;
  private static final long HSTART = 0xBB40E64DA205B064L;
  private static final long HMULT = 7664345821815920749L;

  static {
    byteTable = new long[256];
    long h = 0x544B2FBACAAF1684L;
    for (int i = 0; i < 256; i++) {
      for (int j = 0; j < 31; j++) {
        h = (h >>> 7) ^ h;
        h = (h << 11) ^ h;
        h = (h >>> 10) ^ h;
      }
      byteTable[i] = h;
    }
  }

  private int maxValues;
  private int[] table;
  private int[] nextPtrs;
  private long[] hashValues;
  private E[] elements;
  private int nextHashValuePos;
  private int hashMask;
  private int size;

  @SuppressWarnings("unchecked")
  public CompactMap(int maxElements) {
    int sz = 128;
    int desiredTableSize = maxElements;
    if (desiredTableSize < MAX_HASH_TABLE_SIZE_WITH_FILL_FACTOR) {
      desiredTableSize = desiredTableSize * 4 / 3;
    }
    desiredTableSize = Math.min(desiredTableSize, MAX_HASH_TABLE_SIZE);
    while (sz < desiredTableSize) {
      sz <<= 1;
    }
    this.maxValues = maxElements;
    this.table = new int[sz];
    this.nextPtrs = new int[maxValues];
    this.hashValues = new long[maxValues];
    this.elements = (E[]) new Object[sz];
    Arrays.fill(table, -1);
    this.hashMask = sz-1;
  }

  public int size() {
    return size;
  }

  public E put(CharSequence key, E val) {
    return put(hash(key), val);
  }

  public E put(long hash, E val) {
    int hc = (int) hash & hashMask;
    int[] table = this.table;
    int k = table[hc];
    if (k != -1) {
      int lastk;
      do {
        if (hashValues[k] == hash) {
          E old = elements[k];
          elements[k] = val;
          return old;
        }
        lastk = k;
        k = nextPtrs[k];
      } while (k != -1);
      k = nextHashValuePos++;
      nextPtrs[lastk] = k;
    } else {
      k = nextHashValuePos++;
      table[hc] = k;
    }
    if (k >= maxValues) {
      throw new IllegalStateException("Hash table full (size " + size + ", k " + k);
    }
    hashValues[k] = hash;
    nextPtrs[k] = -1;
    elements[k] = val;
    size++;
    return null;
  }

  public E get(long hash) {
    int hc = (int) hash & hashMask;
    int[] table = this.table;
    int k = table[hc];
    if (k != -1) {
      do {
        if (hashValues[k] == hash) {
          return elements[k];
        }
        k = nextPtrs[k];
      } while (k != -1);
    }
    return null;
  }

  public E get(CharSequence hash) {
    return get(hash(hash));
  }

  public static long hash(CharSequence cs) {
    if (cs == null) return 1L;
    long h = HSTART;
    final long hmult = HMULT;
    final long[] ht = byteTable;
    for (int i = cs.length()-1; i >= 0; i--) {
      char ch = cs.charAt(i);
      h = (h * hmult) ^ ht[ch & 0xff];
      h = (h * hmult) ^ ht[(ch >>> 8) & 0xff];
    }
    return h;
  }

}

答案 6 :(得分:1)

答案 7 :(得分:1)

在commons-collections中至少有一个专门针对速度而构建的实现:Flat3Map它非常具体,因为只要不超过3个元素,它就会非常快。

我怀疑你可以通过跟随@ thaggie的建议来增加对mals ...添加查看equals / hashcode方法的时间。

答案 8 :(得分:1)

有时我看到Map和Set操作使用了很高比例的CPU,它表明我已经过度使用了Map和Set,并且我的数据重组几乎已经消除了来自前10%CPU消费者的集合。

查看是否可以避免集合的副本,迭代集合以及导致访问集合的大多数元素并创建对象的任何其他操作。

答案 9 :(得分:1)

你说你描述了一些课程,但你有没有时间检查他们的速度?我不确定你是如何检查他们的内存使用情况的。当您比较不同的实现时,似乎有一些特定的数字会很好。

答案 10 :(得分:1)

这里有一些注释和几个替代数据结构库的链接:http://www.leepoint.net/notes-java/data/collections/ds-alternatives.html

我也会对fastutil进行强烈投票。 (在另一个响应中提到,并在该页面上)它具有比您可以动摇的更多不同的数据结构,以及针对基本类型优化的版本作为键或值。 (缺点是jar文件很大,但你可以把它修剪成你需要的东西)

答案 11 :(得分:1)

几年前我经历过类似的事情 - 非常大的地图和集合以及其中很多。默认的Java实现占用了太多空间。最后我推出了自己的,但只是在我检查了我的代码所需的实际使用模式之后。例如,我有一组已知的大型对象,这些对象是早期创建的,有些地图很稀疏而有些地图很密集。其他结构单调增长(没有删除),而在其他地方,使用“集合”更快,处理重复项目的偶尔但无害的额外工作比花费时间和空间避免重复。我使用的许多实现都是数组支持的,并且利用了我的哈希码被顺序分配的事实,因此对于密集映射,查找只是一个数组访问。

带走讯息:

  1. 看看你的算法,
  2. 考虑多个实现,
  3. 请记住,那里的大多数库都适合一般用途(例如插入删除,一系列大小,既不稀疏也不密集等),因此它们会产生开销你可以避免。
  4. 哦,写单元测试......

答案 12 :(得分:0)

我使用以下包(koloboke)来执行int-int hashmap,因为它支持promitive类型,并且它在一个long变量中存储了两个int,这对我来说很酷。 koloboke

答案 13 :(得分:0)

导致问题的MapSet可能不是那么多,而是它们背后的对象。根据您的问题,您可能需要更多数据库类型的方案,其中“对象”存储为一堆字节而不是Java对象。你可以嵌入一个数据库(比如Apache Derby)或做你自己的专家。这非常依赖于你实际在做什么。 HashMap并非刻意大而缓慢......

答案 14 :(得分:0)

您使用的是哪个版本的JVM?

如果你不在6岁(虽然我怀疑你是),那么切换到6可能有帮助。

如果这是一个服务器应用程序并且正在Windows上运行,请尝试使用-server来使用正确的热点实现。

答案 15 :(得分:0)

  • Commons Collections有一个id映射,通过==进行比较,它应该更快。 - [Joda Primities][1]和原始集合一样,Trove也是如此。我试验了Trove并发现它的内存使用效果更好。
  • 我正在使用一些整数来映射许多小对象的集合。将这些更改为整数可以节省近一半的内存(尽管需要一些更复杂的应用程序代码来补偿)。
  • 对我来说,排序的树应该比hashmaps消耗更少的内存似乎是合理的,因为它们不需要加载因子(尽管如果有人可以确认或有理由为什么这实际上是愚蠢的,请在评论中发帖)。

答案 16 :(得分:0)

Commons Collections有FastArrayListFastHashMapFastTreeMap,但我不知道它们的价值......