Подтвердить что ты не робот

Почему реализация HashSet в Sun Java использует HashMap в качестве поддержки?

Глядя на источник Java 6, HashSet<E> фактически реализуется с помощью HashMap<E,Object>, используя экземпляр фиктивного объекта для каждой записи набора.

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

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

4b9b3361

Ответ 1

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

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

Но почему он все еще используется? Здесь любая причина использовать его, помимо этого проще поддерживать коды?

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

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

В частности:

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

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

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

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

И из Map:

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

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

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

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

Ответ 2

Я предполагаю, что он никогда не возникал как серьезная проблема для реальных приложений или важных тестов. Зачем усложнять код без реальной выгоды?

Также обратите внимание, что размеры объектов округляются во многих реализациях JVM, поэтому фактически не может быть увеличения размера (я не знаю этого примера). Также код для HashMap скорее всего будет скомпилирован и в кеше. При прочих равных условиях, больше кода = > больше промахов cache = > более низкая производительность.

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

@возвращает предыдущее значение, связанное с ключом, или null, если не было сопоставления для ключа. (Нулевой возврат также может указывать на то, что ранее связанная карта 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, то да, я бы сказал, что они сделали по указанным вами причинам.

Ответ 6

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

Ответ 7

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

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

private static final Object PRESENT = new Object();

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