【问题标题】:Anyone know of a java.util.Map implementation optimized for low memory use?有人知道针对低内存使用优化的 java.util.Map 实现吗?
【发布时间】:2009-03-11 04:13:21
【问题描述】:

我在常见的地方(apache commons、google)都找过,但找不到...

它应该是开源的。

几乎是在寻找一个基于链表的。用例是 10'000 的地图,其中的值不一定很多。它不需要按比例放大,因为当它变得太大时我可以转换它。

一些数字,大小使用一些计算出来的jvm值(8bytes/java.lang.Object, 4bytes/ref)HashMap大约是100+32n字节,理论上最好是12+20*n。

【问题讨论】:

  • 我认为基于链表的地图不会是“最小的”。我会在没有 Entry 对象的情况下创建一个基于数组的数组(即,值直接存储在数组中)。这意味着碰撞会变得很糟糕,但有一些方法可以解决这个问题。
  • 上周我完全实现了这个地图(所以你并不孤单)。不幸的是,实现不是开源的。我设法将地图所需的大小减少到 16(对于地图对象)+ 16(对于数组;四舍五入)+ 8 * size(对于数组内容)。这是您可以获得的最低内存使用量,除非您想仅使用静态方法直接对数组进行操作,这将为每个映射节省另外 16 个字节。但在那种情况下,它就不再是Map 接口的实现了。

标签: java optimization collections


【解决方案1】:

可以看看 commons-collections Flat3Map,它被优化为在 3 个字段中存储 3 个值并在 4 处溢出到另一个地图。

我还没有查看实现,但可能值得考虑。唯一的问题是,由于 commons-collections 与 1.3 兼容,因此没有泛型。

【讨论】:

    【解决方案2】:

    使用 Map 接口包装一个 ArrayList。 ArrayList 本身只使用几个字节。每个节点需要两个指针,一个指向键,一个指向值。使用顺序搜索来查找值。只要条目很少,性能就OK[*]。这将让您有余地为您拥有大量价值的少数花瓶使用真实地图。

    *:假设您的平均地图大小为 10。今天的计算机每秒可以比较大约 1 亿个键,因此每次查找平均花费不到 5 微秒。

    如果性能对您的用例来说仍然太差,您可以尝试按键对数组进行排序并使用二分查找。

    【讨论】:

      【解决方案3】:

      好的,最后自己实现了。我做了一个速度比较,发现与 HashMap 相比,它在 4 个条目时仍然稍快,但在 5 个或更多条目时较慢。我用一个长长的键列表进行了测试,我试图将这些键的组成与随机的英语单词列表相似。

      import java.util.*;
      
      // PUBLIC DOMAIN
      public class SmallMap extends AbstractMap {
      
          private Entry entry = null;
      
          public void clear() { entry = null; }
          public boolean isEmpty() { return entry==null; }    
          public int size() {
              int r = 0;
              for(Entry e = entry; e!=null; e = e.next) r++;
              return r;
          }
      
          public boolean containsKey(Object key) {
              for(Entry e = entry; e!=null; e = e.next){
                  if(e.key.equals(key)){
                      return true;
                  }
              }
              return false;
          }
      
          public boolean containsValue(Object value) {
              for(Entry e = entry; e!=null; e = e.next){
                  if(e.value==null){
                      if(value==null) return true;
                  }else if(e.value.equals(value)){
                      return true;
                  }
              }
              return false;
          }
      
          public Object get(Object key) {
              for(Entry e = entry; e!=null; e = e.next){
                  if(e.key.equals(key)){
                      return e.value;
                  }
              }
              return null;
          }
      
          public Object put(Object key, Object value) {
              for(Entry e = entry; e!=null; e = e.next){
                  if(e.key.equals(key)){
                      Object r = e.value;
                      e.value = value;
                      return r;
                  }
              }
              entry = new Entry(key, value, entry);
              return null;
          }
      
          public Object remove(Object key) {
              if(entry!=null){
                  if(entry.key.equals(key)){
                      Object r = entry.value;
                      entry = entry.next;
                      return r;
                  }
                  for(Entry e = entry; e.next!=null; e = e.next){
                      if(key.equals(e.next.key)){
                          Object r = e.next.value;
                          e.next = e.next.next;
                          return r;
                      }
                  }
              }
              return null;
          }
      
          public Set entrySet() { return new EntrySet(); }
      
          class EntrySet extends AbstractSet{
              public Iterator iterator() {
                  return new Iterator(){
      
                      Entry last = null;
                      Entry e = entry;
                      public boolean hasNext() { return e!=null; }
      
                      public Object next() { 
                          last = e;
                          e = e.next;
                          return last;
                      }
      
                      public void remove() { 
                          if(last == null) throw new IllegalStateException();
                          SmallMap.this.remove(last.key);
                      }
                  };
              }
      
              public int size() { return SmallMap.this.size();}
          }
      
          static private class Entry implements java.util.Map.Entry {
              final Object key;
              Object value;
              Entry next; 
              Entry(Object key, Object value, Entry next){
                  if(key==null) throw new NullPointerException();
                  this.key = key;
                  this.value = value;
                  this.next = next;
              }
              public Object getKey() { return key; }
              public Object getValue() { return value; }
              public Object setValue(Object value) { 
                  Object r = this.value;
                  this.value = value;
                  return r;
              }
              public int hashCode() {
                  return (key   == null ? 0 :   key.hashCode()) ^
                     (value == null ? 0 : value.hashCode());
              }
          }
      }
      

      【讨论】:

      • HashMap“m”在哪里使用?是否有理由不泛化类?
      • 哦,不是,不小心把它留在了里面。没有理由不让它通用,除非我正在考虑使用它。
      【解决方案4】:

      简单来说,我推荐使用 JDK 的 HashMap、Hashtable 和 ConcurrentHashMap 中的一种,具体取决于同步或并发要求。 如果您决定使用它们,在构造函数中适当地设置 initialCapacity 和 loadFactor 可能会有所帮助。

      Google collections 和 apache commons collections 提供了更多的特性:LRUMap、ReferenceMap、MultikeyMap 等等。但我认为不仅仅是小尺寸。

      【讨论】:

      • 我的问题不清楚。我的意思是低内存使用。实际上,在 apache commons 中有一个针对小尺寸进行了优化,称为 Flat3Map。
      • 当原始请求是“告诉我一个比HashMap 更节省内存的Map 实现”时,您绝对不应该建议ConcurrentHashMap,因为这基本上是(并且非常简化) 具有额外间接级别的HashMap。所以它总是比HashMap 需要更多的内存。那是错误的方向。
      【解决方案5】:

      LinkedHashMap 使用链表,我认为,但我怀疑它是否针对低内存使用进行了优化。通常地图的全部目的是加快从键到值的查找,这解释了为什么您在常见的地方找不到您需要的东西。编写自己的 Map 实现可能是最简单的,也许你甚至可以发布代码以防其他人需要同样的东西。

      【讨论】:

        【解决方案6】:

        以隐藏地图使用的方式编写代码(无论如何你都应该这样做,听起来你也是如此)。在重要的时候,因为您已经分析了代码并且可以看到内存确实是一个问题,所以找到一个:-)

        如果您此时知道存在问题,那么抱歉,我不知道。然而,人们经常会处理这样的“想法”,即代码会变慢/占用大量内存等……并开始尝试预先优化它,而不是使代码正确。

        也就是说,如果您正在编写一些您知道这很重要的内容,那么您应该在进行时进行衡量。例如,我正在编写代码来解析类文件,我做了一个小改动,然后看看它是如何影响性能的。例如,我知道我所做的更改(3 行)使我的程序运行速度慢了 4 倍......我当时花了很多时间去寻找更快的方法。

        另外,如果“n”的值很小,您确定需要映射吗?也许列表足够快?您是否也尝试过调整现有 Map 以使其使用更少的内存?

        【讨论】:

          【解决方案7】:

          也许这个答案有点晚了,但是看看Javolution 项目。它包含许多数据结构的实现,用于嵌入式和实时环境。具体来说,有一个 FastMap 类可能只是做你想做的事。

          【讨论】:

          • 看了看……它的大小比小 n 的哈希图差,因为它是预先分配的。它实际上只有在 n 非常大时才会表现出色。
          【解决方案8】:

          如果您只存储Strings,请查看http://code.google.com/p/flatmap

          编辑哦,抱歉,我看到你在寻找小而不是大的地图,那么忘记我的建议吧。

          【讨论】:

            【解决方案9】:

            这在很大程度上取决于您将如何使用这些地图,您能否一次性填充它们然后进行查找(您是否需要这些查找快速)?

            使用最小内存量的实现是将所有元素放入一个数组中并进行扫描以查找元素(但我想这对于您的需要还不够).. .

            如果你一开始就知道所有元素,你可以尝试选择一个没有太多冲突的好的哈希方法。

            或者,如果您允许较慢的插入时间,也许您可​​以使用 TreeMap...

            【讨论】:

              【解决方案10】:

              我知道这是一个老问题,但也许有人可以添加更多想法。

              注意:以下内容仅适用于特定的用例子集:

              如果要求包括高度重叠组键(在极端情况下,所有地图的键组相同),那么一个非常有效的解决方案可能是“外部化" 与地图有关的键,并且地图只包含数组中的值。

              实现不应该“在结构上”依赖于重叠因子,但我的表现更好,键重叠越多。如您所料。

              我不能给出我的实现的确切细节,但重要的是要有一个合适的机制来将键(存储在你的地图对象之外)转换为值数组的索引,同时还允许值数组保持 紧凑,即如果您的地图包含五个映射,则长度为 5。

              假设所有此类地图的键位于单独的地图中,映射到数字。然后就是想办法将数字和数组索引关联起来。

              对不起,如果这不够具体,但我认为这个想法既有趣又简单,可以用作开发内存高效地图的替代方向。

              同样,它本质上适合高“键重叠”用例,但它本身是通用的。如果重叠太低,可能会出现性能问题,具体取决于实现细节。

              【讨论】:

                猜你喜欢
                • 2010-10-26
                • 2011-10-03
                • 1970-01-01
                • 1970-01-01
                • 2010-09-06
                • 1970-01-01
                • 2016-03-19
                • 2011-08-28
                • 1970-01-01
                相关资源
                最近更新 更多