【问题标题】:Java HashMap that takes to much of the memory占用大量内存的 Java HashMap
【发布时间】:2022-10-30 19:36:18
【问题描述】:

问题是我的哈希图占用了太多空间。我想知道代码是否可以以更有效的方式完成而不占用那么多内存。我有一个巨大的数组,我使用 HashMap 的原因是因为我想要一种快速的方法来打印出 where key = 3 的第一次出现,如代码所示。但现在的问题是内存。我仍然希望它相对较快 O(n log n)


ArrayList<String> str = new ArrayList<>();
Map<String, Long> counts2 = new LinkedHashMap<String, Long>();
for(String val : str){
    long count = counts2.getOrDefault(val, 0L);
    counts2.put(val, ++count);
}
for(String key: counts2.keySet()){
    if(counts2.get(key)==3){
        System.out.println(list.indexOf(key));
        break;
    }
}

【问题讨论】:

  • 亚历山大你的意思是什么?
  • 是的,O(n log n) @AlexanderIvanchenko
  • 我也有类似的需求,最终构建了一个由快速进程、键值、磁盘存储系统支持的哈希映射。例如code.google.com/archive/p/jdbm2
  • @user16320675 or memory, I would try removing entries with count &gt; 3 - 我怀疑这是否有效,因为这些条目可能会修复。而且很容易出错。考虑有一个字符串出现7 次。当计数为4 时,该条目被删除,然后它以3 的计数重新出现 - 宾果游戏,现在它可以影响结果。
  • @AlexanderIvanchenko 好吗?

标签: java list memory hashmap space


【解决方案1】:

由于您主要关心的是空间,您可能会考虑以下性能权衡,这不需要分配额外的内存。

for (int i = 1; i < strings.size(); i++) {
    String next = strings.get(i);
    if (Collections.frequency(strings,next) == 3) {
        System.out.println(i);
        break;
    }
}

【讨论】:

  • 我试过它太慢了。我担心两者
  • @ZedORYasuo 你需要指定在问题中。请注意,在大多数情况下,这是一种权衡。最高效的解决方案并不总是最节省空间的,反之亦然。
【解决方案2】:

更新:你应该不是使用以下

我只是将它留在这里一点,以了解不该做什么。 今天我了解到使用hashcode 进行单独比较是不够的。 我认为短路的想法很好,但似乎并不令人担忧。 HashMap 已经在解决冲突方面做得很好,并且复制的实现可能最终使用与初始版本一样多的内存。

相关问题:

Java: Use hashCode() inside of equals() for convenience?
Two strings: same hashcode
What is a good 64bit hash function in Java for textual strings?

原答案如下:

一种方法是存储哈希而不是整个字符串:

...
var count = new HashMap<Integer, Long>();
for(String val: list) {
   count.put(val.hashCode(), count.getOrDefault(val.hashCode(), 0L)+1);
}

扩展@Alexander 的想法,我认为您可以通过保存哈希和索引而不是普通字符串和重新计数(+ 短路)来节省空间和计算

所以:

  1. 迭代列表
  2. 在地图中搜索,如果第一次看到保存索引和计数 = 1
  3. 如果在增量计数之前看到
  4. 如果计数为 3,则完成。
    import java.util.*;
    
    class SpaceTime {
    
      public static void main(String ... args) {
    
        var input = Arrays.asList("one", "two", "three", "two", "three", "two");
        var map = new HashMap<Integer, CountAndIndex>();
    
        for (int i = 0 ; i < input.size(); i++ ) {
          var s = input.get(i);
          var hc = s.hashCode();
          var cai = map.getOrDefault(hc, startAt(i));
          cai.count++;
          if (cai.count == 3) {
            System.out.printf("We've got it!!. Item: '%s' appears for the first time at index: %d%n", s, cai.index);
            break;
          }
          map.put(hc, cai);
        }
      }
      static CountAndIndex startAt(int index) {
        var cai = new CountAndIndex();
        cai.count = 0;
        cai.index = index;
        return cai;
      }
    }
    
    class CountAndIndex {
      long count;
      long index;
    }
    // output: 
    
    We've got it!!. Item: 'two' appears for the first time at index: 1
    
    

【讨论】:

  • 对于任何类,hashCode 返回的值都明确且有意地不要求是唯一的。两个不同的字符串可能具有相同的哈希码。 (虽然我还没有真正研究过如何做到这一点。)
  • @VGR 它们不是唯一的,因为 StringPoint 对象可以返回相同的哈希,但是:“ hashCode 方法必须始终返回相同的整数”source。毕竟这是HashMap 使用的值。
  • 是的,但是如果将哈希码用作键,则您正在计算每个哈希码的出现次数。如果两个字符串碰巧生成了相同的哈希码,则您将它们一起计算。
  • 是的,它 ”必须始终返回相同的整数" 但是对于不同的字符串,它必须并且不能是唯一的!有比整数更多的不同字符串 - 例如"0O".hasCode() == "10".hashCode()
  • (显然我的意思是"0O".hashCode() == "10".hashCode())@Zed - 这取决于您的要求,可能您不想冒险将不同的字符串一起计算(这可以用作近似值或第一个猜测)
【解决方案3】:

您可以在当前的实现上尝试一些优化。这些是低成本,快速的胜利:

使用明确的初始容量和负载因子

通过使用appropriate constructor,您可以为 LinkedHashMap 指定初始容量和加载因子。

负载系数

根据docs,较高的值会减少空间开销,但会增加查找成本。您必须尝试使用​​ 0.75(默认值)和 0.99 之间的值才能找到最佳位置。

初始容量

通过使用较高的值,您可以最大限度地减少由于存储桶已满而导致的重新散列。由于您使用的是 LinkedHashMap,因此大初始容量的影响不太重要,因为迭代时间不受影响。如果您的用例允许,您甚至可以通过选择一个足够大的值来覆盖所有不同的条目来消除重新散列(即,如果您有关于您计算多少不同键的历史数据,或者您的数据集无论如何都具有有限元素)。如果您可以最小化/消除重新散列,您还可以最大程度地减少由较大负载因子值引起的任何缺点。

只保留有趣的条目

看来你只需要找到每个频率的键。如果这是真的,您可以在完成后减少内存中保留的数据,并且每个频率(计数)只保留一个键。

示例代码


        Map<String, Long> counts2 = new LinkedHashMap<String, Long>(10_000, 0.95f); //Using the appropriate constructor
        for (String val : str) {
            long count = counts2.getOrDefault(val, 0L);
            counts2.put(val, ++count);
        }

        // Clean up unneeded (?) entries
        final HashMap<Long, Integer> data = new HashMap<>();
        for (Iterator<Map.Entry<String, Long>> it = counts2.entrySet().iterator(); it.hasNext();) {
            Map.Entry<String, Long> entry = it.next();
            if (data.containsKey(entry.getValue())) {
                it.remove();//Already exists; this will save space
            } else {
                data.put(entry.getValue(), str.indexOf(entry.getKey()));
            }
        }

        //You can now remove original counts2 now
        Integer indexOf3 = data.get(Long.valueOf(3));
        System.out.println(str.get(indexOf3) + " @ " + data.get(Long.valueOf(3)));

        //Original code
        for (String key : counts2.keySet()) {
            if (counts2.get(key) == 3) {
                System.out.println(key + " @ " + str.indexOf(key));
                break;
            }
        }

奖金说明:

你的用例让我想起了 Redis optimizes memory usage for hashes。如果您考虑将 Redis 添加到您的堆栈中,这是一种有趣的方法。

【讨论】:

    【解决方案4】:

    我可以想到两件事来减少您的内存使用量:

    1. 使用Byte 而不是Long
    2. 您知道发生 3 次以上的丢弃元素。这个可能不会节省那么多内存,因为我必须创建一个 HashSet 来实现该机制。
          public static void main(String[] args) {
              var str = List.of("blue", "green", "red", "red", "orange", "green",
                      "turquoise", "red", "green", "green");
              var exceeds3Count = new HashSet<String>();
              var counts = new HashMap<String, Byte>();
              for (String val : str) {
                  if (exceeds3Count.contains(val)) continue;
                  Byte count = counts.compute(val, (k, v) -> v == null ? 1 : (byte) (v + 1));
                  if (count > 3) {
                      counts.remove(val);
                      exceeds3Count.add(val);
                  }
              }
              System.out.println(counts);
              var answer = str.stream().filter(s -> counts.getOrDefault(s, (byte) 0) == 3).findFirst().get();
              System.out.println(answer + " is the first occurrence of a string that occurs 3 times");
          }
      

    【讨论】:

      猜你喜欢
      • 2016-03-10
      • 2014-05-11
      • 2011-02-27
      • 2013-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多