Использование памяти хэш-таблицы в Java

Я использую 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();
    }
}

person user1785771    schedule 01.11.2012    source источник
comment
Некоторый код будет оценен по достоинству.   -  person Gilberto Torrezan    schedule 01.11.2012


Ответы (3)


Карта представляет собой «расширяемую» структуру: когда она достигает своего предела, ее размер изменяется. Так что вполне возможно, что, скажем, 40% пространства, используемого вашей картой, на самом деле пусты. Если вы знаете, сколько записей будет на вашей карте, вы можете использовать специальные конструкторы для оптимального размера вашей карты:

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

Даже если вы это сделаете, карта все равно будет занимать больше места, чем фактический размер содержащихся в ней элементов.

Более подробно: размер HashMap удваивается, когда он достигает (capacity * loadFactor). Коэффициент загрузки по умолчанию для HashMap равен 0,75.

Пример:

  • Представьте, что ваша карта имеет емкость (размер) 10 000 записей.
  • Затем вы помещаете на карту 7501 запись. Вместимость * коэффициент нагрузки = 10 000 * 0,75 = 7 500
  • Таким образом, ваша хэш-карта достигла порога изменения размера и изменяется до (емкость * 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()));
}
person assylias    schedule 01.11.2012
comment
Хеш-таблицы имеют значительные накладные расходы — в обмен на поддержку поиска в постоянном времени. - person Louis Wasserman; 01.11.2012
comment
@LouisWasserman Я бы не подумал, что накладные расходы объяснят такую ​​разницу. - person assylias; 01.11.2012
comment
да, я читал об этом @assylias, но когда он удваивает свою емкость, это не его реальный размер. Например, если значения являются ссылкой на объект, то как он узнает, сколько памяти ему потребуется? - person user1785771; 01.11.2012
comment
Я не думаю, что проблема заключается в накладных расходах HashMap. В конце концов, неиспользуемые записи занимают только кусок памяти размером с указатель, то есть 8 или 16 байтов. Массивы, содержащие данные, вероятно, намного больше. - person rolve; 01.11.2012
comment
Я согласен с @rolve. Но что я могу сделать? - person user1785771; 01.11.2012
comment
@assylias, да, я понимаю твою точку зрения. Но каков обходной путь? Если я загружаю файл размером 20 МБ, я узнаю, что он потребляет 80 МБ !!! Любое обходное решение? - person user1785771; 01.11.2012
comment
@ user1785771 Опубликуйте соответствующие части вашего кода, тогда мы сможем вам помочь. - person rolve; 01.11.2012
comment
@user1785771 user1785771 Вы можете загрузить весь файл - определить оптимальный размер карты, создать ее и заполнить - это включает в себя дополнительную операцию копирования, которая не должна быть слишком дорогой по сравнению со временем, необходимым для чтения файла. Теперь, в зависимости от того, что вы помещаете в карту и как вы ее помещаете, конечное потребление памяти может сильно различаться. Показ того, как вы заполняете карту, может помочь. - person assylias; 01.11.2012
comment
Также было бы интересно узнать, как вы измеряете использование памяти — измеряете ли вы его в профилировщике или просто проверяете объем памяти, используемый процессом JVM (который будет включать некоторое неиспользуемое пространство кучи в зависимости от параметров, которые вы передали в JVM). - person assylias; 01.11.2012
comment
Я только что использовал Runtime.getRuntime(). Но дело в том, что у меня ограниченная память, поэтому я получаю сообщение об ошибке OutofMemory Errr Я здесь новичок, я не знаю, как писать код в четком разделе кода, как вы, ребята.... есть ли ‹ код› тег? - person user1785771; 01.11.2012
comment
@ user1785771, вы можете вставить код в свое сообщение, выбрать его и нажать кнопку с фигурными скобками. Или просто сделайте отступ в коде на 4 пробела, чтобы получить тот же результат. - person assylias; 01.11.2012
comment
public static void main(String[] args) { BufferedReader reader = null; Writer PrintWriter = новый PrintWriter (новый FileWriter (test.txt, true)); for (int i = 0; i ‹ 1000000; i++) Writer.println(Это просто фиктивный текст!); писатель.close(); Читатель BufferedReader = новый BufferedReader (новый FileReader (файл)); HashMap‹Integer, String› testMap = new HashMap‹Integer, String›(); Строка line = reader.readLine(); инт к = 0; в то время как (строка! = null) { testMap.put (k, строка); к++; строка = читатель.readLine();} - person user1785771; 02.11.2012
comment
Извините, я не мог сделать код правильно. Но этот код генерирует текст размером около 28 МБ, а затем считывает его в хэш-карту. Я использую 100 МБ для heab (-Xmx100m), и я получаю ошибку памяти только с этим кодом.! @ассилиас - person user1785771; 02.11.2012

Это не проблема HashMap, это проблема объектов Java в целом. Каждый объект имеет определенные накладные расходы памяти, включая массивы и записи в файле HashMap.

Но что еще более важно: символьные данные занимают вдвое больше места в памяти. Причина этого в том, что Java использует 16 бит для каждого символа, тогда как файл, вероятно, закодирован в ASCII или UTF-8. , который использует только 7 или 8 бит на символ.

Обновление: вы мало что можете с этим поделать. Код, который вы разместили, в принципе хорош. Это просто не работает с большими файлами. Возможно, вы сможете добиться большего, если тщательно настроите свой HashMap, или вы можете использовать байтовый массив вместо строки для хранения своих символов (при условии, что все в ASCII или однобайтовом UTF-8).

Но, в конце концов, чтобы решить ваши проблемы с нехваткой памяти, правильный путь — переосмыслить вашу программу, чтобы вам не приходилось сразу считывать весь файл в память.

Что бы вы ни делали с содержимым этого файла, подумайте, можете ли вы сделать это во время чтения файла с диска (это называется потоковая передача) или, возможно, извлечь соответствующие детали и хранить только их. Вы также можете попытаться получить произвольный доступ к файлу.

Я предлагаю вам немного прочитать об этих вещах, попробовать что-нибудь и вернуться и задать новый вопрос, специфичный для вашего приложения. Потому что эта ветка становится слишком длинной.

person rolve    schedule 01.11.2012
comment
+1 за продолжение ;-) Действительно, если этому коду не хватает памяти, нечего делать, кроме как не хранить весь файл в памяти. - person assylias; 02.11.2012

Есть много вещей, внутренних для реализации HashMap (и массивов), которые необходимо сохранить. Длины массивов могут быть одним из таких примеров. Не уверен, что это объясняет double, но это определенно может объяснить некоторые.

person Jon Newmuis    schedule 01.11.2012