Почему JAVA HashSet использует HashMap внутренне? Разве это не потеряет память? [Дубликат]

NullPointerException s - исключения, возникающие при попытке использовать ссылку, которая указывает на отсутствие местоположения в памяти (null), как если бы она ссылалась на объект. Вызов метода по нулевой ссылке или попытка получить доступ к полю нулевой ссылки вызовет функцию NullPointerException. Они наиболее распространены, но другие способы перечислены на странице NullPointerException javadoc.

Вероятно, самый быстрый пример кода, который я мог бы придумать для иллюстрации NullPointerException, be:

public class Example {

    public static void main(String[] args) {
        Object obj = null;
        obj.hashCode();
    }

}

В первой строке внутри main я явно устанавливаю ссылку Object obj равной null. Это означает, что у меня есть ссылка, но она не указывает на какой-либо объект. После этого я пытаюсь обработать ссылку так, как если бы она указывала на объект, вызывая метод на нем. Это приводит к NullPointerException, потому что нет кода для выполнения в местоположении, на которое указывает ссылка.

(Это техничность, но я думаю, что она упоминает: ссылка, которая указывает на null, равна 't то же, что и указатель C, указывающий на недопустимую ячейку памяти. Нулевой указатель буквально не указывает на в любом месте , который отличается от указаний на местоположение, которое оказывается недопустимым.)

39
задан Bakuriu 25 April 2014 в 12:10
поделиться

7 ответов

На самом деле это не просто HashSet. Все реализации интерфейса Set в Java 6 основаны на базовом Map. Это не требование; это просто способ реализации. Вы можете убедиться сами, проверив документацию по различным реализациям Set .

Ваши основные вопросы:

Но почему он все еще используется? Есть ли причина использовать его, кроме упрощения ведения кодов?

Я предполагаю, что обслуживание кода является большим мотивирующим фактором. Таким образом, предотвращается дублирование и раздувание.

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

В частности:

Из документации Set :

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

Интерфейс Set устанавливает дополнительные условия, помимо тех, которые унаследованы из интерфейса Collection, в контрактах всех конструкторов и контрактах add, equals и hashCode. Объявления для других унаследованных методов также включены здесь для удобства. (Спецификации, прилагаемые к этим объявлениям, были адаптированы к интерфейсу Set, но они не содержат каких-либо дополнительных условий.)

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

И из Map :

Объект, который сопоставляет ключи со значениями , Карта не может содержать дубликаты ключей; каждый ключ может отображать не более одного значения.

Если вы можете реализовать свой Set s с использованием существующего кода, любое преимущество (например, скорость), которое вы можете реализовать из существующего кода, начисляется на ваш Set.

Если вы решите реализовать Set без подставки Map, вам придется дублировать код, предназначенный для предотвращения дублирования элементов. Ах, восхитительная ирония.

Тем не менее, ничего не мешает вам реализовать ваш Set по-другому.

17
ответ дан nmeln 17 August 2018 в 09:54
поделиться
  • 1
    «Все реализации интерфейса Set в Java 6 основаны на базовом Collection. & quot; (Я предполагаю, что вы имеете в виду Map вместо Collection.) Существует по крайней мере один пример счетчика (кроме подмножеств и т.п.). EnumSet не основан на Map. – Tom Hawtin - tackline 6 January 2013 в 22:15
  • 2
    Есть еще одна возможность: она могла бы быть реализована как Map & lt; T, T & gt; вместо Map & lt; T, Object & gt; и предоставить get (T) бесплатно, по крайней мере, для HashSet (и, возможно, TreeSet), аналогично тому, что предлагает C ++. Вероятно, это приведет к некоторым хакерским обычаям (в любом случае, я не могу придумать законную чистую), но время от времени это может быть сделано. – Luke 22 July 2017 в 18:48

После поиска таких страниц, как это задается вопросом, почему мягко неэффективная стандартная реализация найдена, com.carrotsearch.hppc.IntOpenHashSet

0
ответ дан clwhisk 17 August 2018 в 09:54
поделиться

Я предполагаю, что HashSet изначально был реализован с точки зрения HashMap, чтобы сделать это быстро и легко. В терминах строк кода HashSet является частью HashMap.

Я бы предположил, что причина, по которой он еще не оптимизирован, - это страх перед изменением.

Однако, отходы намного хуже, чем вы думаете. На 32-битном и 64-битном уровне HashSet в 4 раза больше необходимого, а HashMap - 2x больше необходимого. HashMap может быть реализован с массивом с ключами и значениями в нем (плюс цепочки для коллизий). Это означает два указателя на запись или 16 байтов на 64-битной виртуальной машине. Фактически, HashMap содержит объект Entry для каждой записи, который добавляет 8 байтов для указателя на запись и 8 байтов для заголовка объекта Entry. HashSet также использует 32 байта на элемент, но отходы составляют 4 раза вместо 2x, поскольку для каждого элемента требуется только 8 байтов.

4
ответ дан Craig P. Motlin 17 August 2018 в 09:54
поделиться
  • 1
    В JVM HotSpot заголовок объекта состоит из двух слов, поэтому запись в хэш-карте с указателем на запись key, value и next для обработки столкновений имеет в пять раз больше места по сравнению с одной ссылкой в плоском массиве (при сравнении с возможной реализацией Set). Но в HashMap все еще есть массив, массив ссылок на экземпляры Entry. Таким образом, в конце HashMap на основе HashSet занимает примерно в шесть раз больше пространства плоского массива на основе HashSet. На 64-битной JVM HotSpot с включенными CompressedOOP и CompressedKlassPointers это даже в 6.5 раз ... – Holger 7 July 2018 в 14:50
  • 2
    Все конкуренты, Eclipse Collections, Fastutils, Trove и т. Д. Все это обеспечивают 4-кратное улучшение. – Craig P. Motlin 8 July 2018 в 23:01
  • 3
    Это пустой оператор без упоминания номеров версий и конкретной конфигурации JVM. Реализация OpenJDK со временем изменилась, особенно в последних версиях поддерживалась древовидная структура для обработки коллизий, что еще больше увеличивает потребление памяти, когда это происходит. Кроме того, мой предыдущий комментарий уже объяснил, что зависимость от архитектуры JVM и ее конфигурации зависит от затрат на объект. Конечно, альтернативные реализации должны прибегать к объектам, а также для коллизий. Авторы, вероятно, сделали занижение, чтобы уклониться от таких тонкостей – Holger 9 July 2018 в 07:52
  • 4
    Я один из авторов. Это не преуменьшение. 4x. Все библиотеки, все версии. Это был тот же ответ в течение 10 лет. – Craig P. Motlin 9 July 2018 в 11:14
  • 5
    В этом случае вы, очевидно, проигнорировали тот факт, что реализация JRE HashMap значительно изменилась за последнее десятилетие. И я не понимаю, почему вы так агрессивно настаиваете на том, чтобы отказаться от возможности улучшить улучшение в определенных сценариях. Это «4x.» Условно-досрочное освобождение, данное каким-то священным диктатором, который превосходит все технические обсуждения или что? – Holger 9 July 2018 в 11:18

Я посмотрел на ваш вопрос, и мне потребовалось некоторое время, чтобы подумать о том, что вы сказали. Итак, вот мое мнение относительно реализации HashSet.

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

Взгляните в методе add

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

Теперь Abd посмотрим на возвращаемое значение put

@ возвращает предыдущее значение, связанное с ключом, или null, если не было отображение для ключа. (Нулевой возврат также может указывать, что ранее связанная карта с ключом.)

Таким образом, объект PRESENT используется только для представления того, что набор содержит значение e. Я думаю, вы спросили, почему бы не использовать null вместо PRESENT. Но вы не сможете отличить, была ли запись ранее на карте, потому что map.put(key,value) всегда возвращал null, и у вас не было бы способа узнать, существовал ли ключ.


Сказав, что вы могли бы утверждать, что они могли использовать реализацию, подобную этой

   public boolean add(E e) {

        if( map.containsKey(e) ) {
            return false;
        }

        map.put(e, null);

        return true;

}

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


Если вы задали вопрос, почему они использовали HashMap, который будет тратить 8 байтов (из-за Map.Entry) вместо некоторых других данных структура с использованием аналогичного входа только 4, то да, я бы сказал, что они сделали это по причинам, о которых вы упомянули.

3
ответ дан Lombo 17 August 2018 в 09:54
поделиться

Ваш вопрос: я думаю, что для размера самой записи тратится 4 байта (на 32-битных машинах).

Создана только одна переменная объекта для всей структуры данных хэшета, и это будет

private static final Object PRESENT = new Object();

Все ключи имеют одно значение, то есть объект PRESENT.

-3
ответ дан Srujan Kumar Gulla 17 August 2018 в 09:54
поделиться
  • 1
    Пропущенное пространство находится в дополнительном поле, используемом для хранения избыточного поля value для всех записей в HashSet. – Stephen C 14 June 2018 в 13:08
  • 2
    @StephenC и в существовании объекта записи в первую очередь. – Holger 9 July 2018 в 12:02

Да, вы правы, небольшое количество потерь там определенно. Маленький, потому что для каждой записи он использует тот же объект PRESENT (который объявлен окончательным). Следовательно, единственное израсходование - это значение для каждой записи в HashMap.

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

Но если у вас огромные коллекции, а вы - уродка памяти, то вы можете отказаться от лучших альтернатив например Trove или Google Collections .

4
ответ дан Suraj Chandran 17 August 2018 в 09:54
поделиться
  • 1
    Дополнительные отходы должны хранить ссылку на ключ, которая может быть большой, если у вас есть миллионы записей в наборе. 8bytes * 1M объектов = 8MB отходов – Yoni Roit 21 July 2011 в 13:06
4
ответ дан Suraj Chandran 6 September 2018 в 07:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: