Внутренняя реализация java.утиль.HashMap и HashSet

я пытался понять внутреннюю реализацию java.util.HashMap и java.util.HashSet.

следующие сомнения появляются в моем уме на некоторое время:

  1. в чем важность @Override public int hashcode() в HashMap / HashSet? Где этот хэш-код используется внутри?
  2. я обычно видел, что ключ HashMap является String как myMap<String,Object>. Могу ли я сопоставить значения с someObject (вместо строки) как myMap<someObject, Object>? Что все контракты я нужно подчиняться, чтобы это произошло успешно?

спасибо заранее !

EDIT:

  1. мы говорим, что хэш-код ключа (проверьте!) является ли фактическая вещь, против которой значение отображается в хэш-таблице? И когда мы это сделаем myMap.get(someKey); java внутренне вызывает someKey.hashCode() чтобы получить число в хэш-таблице, чтобы искать полученное значение?

ответ: Да.

EDIT 2:

  1. на java.util.HashSet, откуда создается ключ для хэш-таблицы? Это из объекта, который мы добавляем, например. mySet.add(myObject); затем myObject.hashCode() собирается решить, где это помещается в хэш-таблицу? (как мы не даем ключи в HashSet).

ответ: добавленный объект становится ключом. Значение является фиктивным!

9 ответов


ответ на вопрос 2 прост - да, вы можете использовать любой понравившийся вам объект. Карты с ключами строкового типа широко используются, поскольку они являются типичными структурами данных для служб именования. Но в целом, вы можете сопоставить любые два типа, такие как Map<Car,Vendor> или Map<Student,Course>.

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

практический sidenote: eclipse (и, вероятно, другие IDEs) может автоматически генерировать пару equals () и hashCode () реализации для вашего класса, только на основе членов класса.

редактировать

для вашего дополнительного вопроса: да, точно. Посмотрите на исходный код для HashMap.get (ключ объекта); он вызывает ключ.hashcode для вычисления позиции (bin) во внутренней хэш-таблице и возвращает значение в этой позиции (если оно есть).

но будьте осторожны с методами "handmade" hashcode / equals - если вы используете объект в качестве ключа, убедитесь, что хэш-код после этого не изменится, иначе вы больше не найдете сопоставленные значения. Другими словами, поля, которые вы используете для вычисления equals и hashcode должно быть окончательным (или 'unchangeable' после творения объект.)

Предположим, у нас есть контакт с String name и String phonenumber и мы используем оба поля для вычисления equals () и hashcode (). Теперь мы создаем "Джон Доу" с его номером мобильного телефона и сопоставляем его с его любимым магазином пончиков. hashcode () используется для вычисления индекса (bin) в хэш-таблице, и именно там хранится магазин пончиков.

теперь мы узнаем, что у него есть новый номер телефона, и мы меняем поле номера телефона объекта John Doe. Это приводит к новому хэш-коду. И этот хэш-код разрешает новый индекс хэш-таблицы - который обычно не является позицией, где хранился любимый магазин пончиков Джона до.

проблема ясна: в этом случае мы хотели сопоставить " Джон Доу "с магазином пончиков, а не"Джон Доу с определенным номером телефона". Поэтому мы должны быть осторожны с автогенерированными equals/hashcode, чтобы убедиться, что они то, что мы действительно хотим, потому что они могут использовать нежелательные поля, вводя проблемы с HashMaps и Hashsets том.

Изменить 2

если вы добавляете объект в хэш-набор, объект является ключом для внутренней хэш-таблицы, значение задано, но не используется (только статический экземпляр объекта). Вот реализация из openjdk 6 (b17):

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;

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

в чем важность хэш-кода @Override public int() в HashMap/HashSet?

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

где этот хэш-код используется внутри?

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

могу ли я сопоставить значения с someObject (вместо String) как myMap<someObject, Object>?

да someObject должен быть классом, а не объектом (ваше имя предполагает, что вы хотите передать объект; это должно быть SomeObject чтобы было ясно, что вы имеете в виду тип).

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

класс должен реализовывать hashCode() и equals().

[EDIT]

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

да.


да. Вы можете использовать любой объект в качестве ключа в HashMap. Для этого необходимо выполнить следующие шаги.

  1. переопределить equals.

  2. переопределить hashCode.

контракты для обоих методов очень четко упомянуты в документации java.ленг.Объект. http://java.sun.com/javase/6/docs/api/java/lang/Object.html

и да используется метод hashCode() внутренне по HashMap и, следовательно, возврат правильного значения важен для производительности.

вот метод hashCode () из HashMap

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

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


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

например список номеров: 1, 2, 3, 4, 5, 6, 7, 8 хранящиеся в List посмотрел бы (концептуально) в памяти что-то вроде: [1, 2, 3, 4, 5, 6, 7, 8].

сохранение одного и того же набора чисел в Set было бы больше похоже на это:[1, 2] [3, 4] [5, 6] [7, 8]. В этом примере список был разделен на 4 ведра.

теперь представьте, что вы хотите найти значение 6 так List и Set. Со списком вам нужно будет начать в начале списка и проверить каждое значение, пока не дойдете до 6, это займет 6 шагов. С помощью набора вы найдете правильное ведро, проверьте каждый из элементов в этом ведре (только 2 в нашем примере), что делает этот процесс 3 шага. Ценность такого подхода резко возрастает, чем больше у вас данных.

но подождите, как мы узнали, в каком ведре искать? Вот где hashCode метод. Чтобы определить ведро, в котором искать элемент Java-контейнеры хэширования, вызовите hashCode затем применить некоторую функцию к результату. Эта функция пытается сбалансировать количество сегментов и количество элементов для максимально быстрого поиска.

во время поиска, как только правильное ведро было найдено, каждый элемент в этом ведре сравнивается по одному, как в списке. Вот почему, когда вы переопределяете hashCode вы также должны переопределить equals. Итак, если объект любой тип имеет оба equals и hashCode метод его можно использовать как ключ в Map или в Set. Существует контракт, который необходимо соблюдать, чтобы правильно реализовать эти методы канонический текст об этом из Большой книги Джоша Блоха Effective Java:пункт 8: всегда переопределять хэш-код при переопределении equals


  1. любой Object в Java должен быть hashCode() способ; HashMap и HashSet нет execeptions. Этот хэш-код используется, если вы вставляете хэш-карту/набор в другую хэш-карту / набор.
  2. любой тип класса может использоваться в качестве ключа в HashMap/HashSet. Это требует, чтобы hashCode() метод возвращает равные значения для равных объектов, и что equals() метод реализован в соответствии с контрактом (рефлексивный, транзитивный, симметричный). Реализации по умолчанию из Object уже соблюдайте эти контракты, но вы можете переопределить их, если хотите равенство значений вместо равенства ссылок.

существует сложная связь между equals (),hashcode() и хэш-таблицы в целом в Java (и .NET тоже, если на то пошло). Цитата из документации:

public int hashCode()

возвращает значение хэш-кода для объекта. Этот метод поддерживается для использования хэш-таблиц, таких как java.util.Hashtable.

генеральный контракт хэш-кода:

  • всякий раз, когда он вызывается на одном и том же объекте более одного раза во время выполнения приложения Java, метод hashCode должен последовательно возвращать одно и то же целое число, при условии, что никакая информация, используемая в равных сравнениях на объекте не изменяется. Это целое число не должно оставаться последовательным от одного выполнения приложения к другому выполнению того же приложения.
  • если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объекты должны давать один и тот же целочисленный результат.
  • не требуется, чтобы два объекта были неравны в соответствии с равными (java.lang.Object) метод, затем вызывая hashCode метод на каждом из двух объектов должен привести к отличным целочисленные результаты. Однако программист должен знать, что получение различных целочисленных результатов для неравных объектов может повысить производительность хэш-таблиц.

насколько это разумно практично,hashCode способ определяется классом Object возвращает различные целые числа для различных объектов. (Это обычно реализуется путем преобразования внутренний адрес объекта в целое число, но этот метод реализации не требуется язык программирования Java™.)

строку

@Overrides public int hashCode()

говорит, что hashCode() метод переопределяется. Об этом ИА обычно знак того, что безопасно использовать тип в качестве ключа в HashMap.

и да, вы можете aesily использовать любой объект, который подчиняется договору для equals() и hashCode() на HashMap как ключ.


в ответ на вопрос 2, хотя вы можете иметь любой класс, который может использоваться в качестве ключа в Hashmap, рекомендуется использовать неизменяемые классы в качестве ключей для HashMap. Или, по крайней мере, если ваша реализация "hashCode" и "equals" зависит от некоторых атрибутов вашего класса, вы должны позаботиться о том, чтобы не предоставлять методы для изменения этих атрибутов.


Аарон Digulla абсолютно правильно. Интересное дополнительное замечание, которое люди, похоже, не понимают, заключается в том, что метод hashCode() ключевого объекта не используется дословно. Он, по сути, перефразирован HashMap, т. е. он вызывает hash(someKey.hashCode)), где hash() является внутренним методом хэширования.

чтобы увидеть это, посмотрите на источник:http://kickjava.com/src/java/util/HashMap.java.htm

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


метод HashCode для классов коллекции, таких как HashSet, HashTable, HashMap и т. д. – хэш-код возвращает целое число для объекта, который поддерживается с целью хэширования. Он реализуется путем преобразования внутренний адрес объекта в целое число. Метод хэш-кода должен быть переопределен в каждом классе, который переопределяет метод equals. Три общих контакта для метода HashCode

  • для двух равных объектов acc. чтобы метод equal, затем вызов HashCode для оба объекта должны иметь одинаковое целочисленное значение.

  • Если она вызывается несколько раз для одного объекта, то он должен возвратить целочисленные значения.

  • для двух неравных объектов acc. чтобы метод equal, а затем вызов метода HashCode для обоих объектов, не обязательно, чтобы он производил различное значение.