【问题标题】:Hash Table Memory Usage in JavaJava中的哈希表内存使用
【发布时间】:2012-10-22 07:02:54
【问题描述】:

我正在使用 java 从文件中读取数据,将数据复制到较小的数组中并将这些数组放入 Hashtables 中。我注意到 Hashmap 比原始文件消耗更多的内存(大约两倍)!知道为什么吗?

这是我的代码:

public static void main(final String[] args) throws IOException {
    final PrintWriter writer = new PrintWriter(new FileWriter("test.txt",
            true));
    for(int i = 0; i < 1000000; i++)
        writer.println("This is just a dummy text!");
    writer.close();

    final BufferedReader reader = new BufferedReader(new FileReader(
            "test.txt"));
    final HashMap<Integer, String> testMap = new HashMap<Integer, String>();
    String line = reader.readLine();
    int k = 0;
    while(line != null) {
        testMap.put(k, line);
        k++;
        line = reader.readLine();
    }
}

【问题讨论】:

  • 一些代码将不胜感激。

标签: java memory-leaks hashtable


【解决方案1】:

这不是HashMap 的问题,一般来说是Java 对象的问题。每个对象都有一定的内存开销,包括HashMap 中的数组和条目。

但更重要的是:字符数据占用内存空间的两倍。原因是Java uses 16 bits for each character,而文件可能以 ASCII 或 UTF-8 编码,每个字符仅使用 7 或 8 位。

更新:您对此无能为力。您发布的代码原则上很好。它只是不适用于大文件。如果您仔细调整HashMap,您可能会做得更好,或者您可以使用字节数组而不是字符串来存储您的字符(假设所有内容都是 ASCII 或单字节 UTF-8)。

但最终,要解决内存不足的问题,正确的方法是重新考虑程序,这样您就不必一次将整个文件读入内存。

无论您对该文件的内容做什么,请考虑一下您是否可以在从磁盘读取文件时执行此操作(这称为流式传输)或者可能提取相关部分并仅存储这些部分。您也可以尝试随机访问该文件。

我建议您稍微阅读一下这些内容,尝试一下,然后再回来提出一个针对您的应用程序的新问题。因为这个帖子太长了。

【讨论】:

  • +1 用于后续 ;-) 事实上,如果这段代码内存不足,除了不将整个文件存储在内存中之外,没有什么可做的。
【解决方案2】:

地图是一种“可扩展”结构——当它达到其容量时,它会调整大小。因此,您的地图使用的 40% 的空间可能实际上是空的。如果您知道地图中有多少条目,则可以使用临时构造函数以最佳方式调整地图大小:

Map<xx,yy> map = new HashMap<> (length, 1);

即使您这样做,地图仍将使用比所包含项目的实际大小更多的空间。

更详细地说:当 HashMap 达到 (容量 * loadFactor) 时,它的大小会翻倍。 HashMap 的默认加载因子是 0.75。

例子:

  • 假设您的地图的容量(大小)为 10,000 个条目
  • 然后您将 7,501 个条目放入地图中。容量 * loadFactor = 10,000 * 0.75 = 7,500
  • 因此,您的 hashmap 已达到其调整大小阈值并调整为 (容量 * 2) = 20,000,尽管您仅持有 7,501 个条目。这会浪费很多空间。

编辑

这个简单的代码让您了解实际发生的情况 - 输出是:

threshold of empty map = 8192
size of empty map = 35792
threshold of filled map = 8192
size of filled map = 1181712
threshold with one more entry = 16384
size with one more entry = 66640

这表明如果您添加的最后一项恰好强制地图调整大小,它可以人为地增加地图的大小。诚然,这并不能说明您观察到的全部效果。

public static void main(String[] args) throws java.lang.Exception {
    Field f = HashMap.class.getDeclaredField("threshold");
    f.setAccessible(true);

    long mem = Runtime.getRuntime().freeMemory();
    Map<String, String> map = new HashMap<>(2 << 12, 1); // 8,192
    System.out.println("threshold of empty map = " + f.get(map));
    System.out.println("size of empty map = " + (mem - Runtime.getRuntime().freeMemory()));

    mem = Runtime.getRuntime().freeMemory();
    for (int i = 0; i < 8192; i++) {
        map.put(String.valueOf(i), String.valueOf(i));
    }
    System.out.println("threshold of filled map = " + f.get(map));
    System.out.println("size of filled map = " + (mem - Runtime.getRuntime().freeMemory()));

    mem = Runtime.getRuntime().freeMemory();
    map.put("a", "a");
    System.out.println("threshold with one more entry = " + f.get(map));
    System.out.println("size with one more entry = " + (mem - Runtime.getRuntime().freeMemory()));
}

【讨论】:

  • 哈希表有相当多的开销——以换取支持恒定时间查找。
  • @LouisWasserman 我认为开销不会造成这种差异。
  • 是的,我读到过 @assylias ,但是当它的容量增加一倍时,它的实际大小却不是。比如,如果这些值是对象引用,那么它怎么知道它需要多少内存?
  • 我不认为 HashMap 的开销是问题所在。毕竟,未使用的条目只消耗一个指针大小的内存块,即 8 或 16 个字节。包含数据的数组可能要大得多。
  • @user1785771 发布您的代码的相关部分,我们或许可以为您提供帮助。
【解决方案3】:

HashMap(和数组)的实现内部有很多东西需要存储。数组长度就是这样一个例子。不确定这是否会导致 double,但肯定会导致一些。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-19
    • 2018-05-20
    • 2011-10-08
    • 2013-03-19
    • 1970-01-01
    相关资源
    最近更新 更多