Руководство по LinkedHashMap в Java

Руководство по LinkedHashMap в Java

1. обзор

В этой статье мы собираемся изучить внутреннюю реализацию классаLinkedHashMap. LinkedHashMap - это обычная реализация интерфейсаMap.

Эта конкретная реализация является подклассомHashMap и, следовательно, разделяет основные строительные блокиHashMap implementation. В связи с этим настоятельно рекомендуется освежить в памяти это, прежде чем продолжить эту статью.

2. LinkedHashMap противHashMap

КлассLinkedHashMap во многих аспектах очень похож наHashMap. Однако связанная хэш-карта основана на хэш-таблице и связанном списке для улучшения функциональности хэш-карты.

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

Чтобы сохранить порядок элементов, связанная хэш-карта изменяет классMap.Entry дляHashMap, добавляя указатели на следующую и предыдущую записи:

static class Entry extends HashMap.Node {
    Entry before, after;
    Entry(int hash, K key, V value, Node next) {
        super(hash, key, value, next);
    }
}

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

Наконец, помните, что этот связанный список определяет порядок итерации, который по умолчанию является порядком вставки элементов (insert-order).

3. Заказ на размещениеLinkedHashMap

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

@Test
public void givenLinkedHashMap_whenGetsOrderedKeyset_thenCorrect() {
    LinkedHashMap map = new LinkedHashMap<>();
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);

    Set keys = map.keySet();
    Integer[] arr = keys.toArray(new Integer[0]);

    for (int i = 0; i < arr.length; i++) {
        assertEquals(new Integer(i + 1), arr[i]);
    }
}

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

Мы можем гарантировать, что этот тест всегда будет проходить, так как порядок ввода будет всегда поддерживаться. We cannot make the same guarantee for a HashMap.с

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

Порядок вставки не изменяется, если ключ повторно вставлен в карту.

4. Порядок доступаLinkedHashMap

LinkedHashMap предоставляет специальный конструктор, который позволяет нам указать среди настраиваемого коэффициента загрузки (LF) и начальной емкостиa different ordering mechanism/strategy called access-order:

LinkedHashMap map = new LinkedHashMap<>(16, .75f, true);

Первый параметр - это начальная емкость, за которой следует коэффициент нагрузки иlast param is the ordering mode. Итак, передавtrue, мы получили порядок доступа, тогда как по умолчанию был порядок вставки.

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

Итак, создание кеша с наименьшим количеством использованных ресурсов (LRU) довольно просто и практично с видом карты. Успешная операцияput илиget приводит к доступу к записи:

@Test
public void givenLinkedHashMap_whenAccessOrderWorks_thenCorrect() {
    LinkedHashMap map
      = new LinkedHashMap<>(16, .75f, true);
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);

    Set keys = map.keySet();
    assertEquals("[1, 2, 3, 4, 5]", keys.toString());

    map.get(4);
    assertEquals("[1, 2, 3, 5, 4]", keys.toString());

    map.get(1);
    assertEquals("[2, 3, 5, 4, 1]", keys.toString());

    map.get(3);
    assertEquals("[2, 5, 4, 1, 3]", keys.toString());
}

Обратите внимание, какthe order of elements in the key set is transformed as we perform access operations on the map.

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

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

Естественно, повторение вида карты не влияет на порядок итерации вспомогательной карты; only explicit access operations on the map will affect the order.

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

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

Чтобы увидеть это на практике, давайте создадим наш собственный класс связанных хэш-карт с единственной целью - принудительно удалить устаревшие сопоставления путем расширенияLinkedHashMap:

public class MyLinkedHashMap extends LinkedHashMap {

    private static final int MAX_ENTRIES = 5;

    public MyLinkedHashMap(
      int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }

}

Приведенное выше переопределение позволит увеличить размер карты до 5 записей. Когда размер превышает этот, каждая новая запись будет вставлена ​​за счет потери самой старой записи на карте, т.е. запись, время последнего доступа которой предшествует всем другим записям:

@Test
public void givenLinkedHashMap_whenRemovesEldestEntry_thenCorrect() {
    LinkedHashMap map
      = new MyLinkedHashMap<>(16, .75f, true);
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);
    Set keys = map.keySet();
    assertEquals("[1, 2, 3, 4, 5]", keys.toString());

    map.put(6, null);
    assertEquals("[2, 3, 4, 5, 6]", keys.toString());

    map.put(7, null);
    assertEquals("[3, 4, 5, 6, 7]", keys.toString());

    map.put(8, null);
    assertEquals("[4, 5, 6, 7, 8]", keys.toString());
}

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

5. Вопросы производительности

Так же, какHashMap,LinkedHashMap выполняет базовые операцииMap добавления, удаления и добавления в постоянное время, если хеш-функция имеет правильные размеры. Он также принимает нулевой ключ и нулевые значения.

Однако этоconstant-time performance of LinkedHashMap is likely to be a little worse than the constant-time of HashMap из-за дополнительных накладных расходов на поддержание двусвязного списка.

Итерация по представлениям коллекцииLinkedHashMap также занимает линейное времяO(n), аналогичное таковому дляHashMap. С другой стороны,LinkedHashMap‘s linear time performance during iteration is better than HashMap‘s linear time.

Это связано с тем, что дляLinkedHashMapn вO(n) - это только количество записей в карте независимо от емкости. Принимая во внимание, что дляHashMap,n - это емкость, а размер суммируется,O(size+capacity).

Коэффициент нагрузки и начальная мощность определяются точно так же, как дляHashMap. Обратите внимание, однако, чтоthe penalty for choosing an excessively high value for initial capacity is less severe for LinkedHashMap than for HashMap, поскольку время итерации для этого класса не зависит от емкости.

6. совпадение

Как иHashMap, реализацияLinkedHashMap не синхронизирована. Таким образом, если вы собираетесь обращаться к нему из нескольких потоков, и хотя бы один из этих потоков может изменить его структурно, то он должен быть внешне синхронизирован.

Лучше всего сделать это при создании:

Map m = Collections.synchronizedMap(new LinkedHashMap());

Разница сHashMap заключается в том, что влечет за собой структурную модификацию. In access-ordered linked hash maps, merely calling the get API results in a structural modification. Наряду с этим есть такие операции, какput иremove.

7. Заключение

В этой статье мы исследовали класс JavaLinkedHashMap как одну из наиболее эффективных реализаций интерфейсаMap с точки зрения использования. Мы также исследовали его внутреннюю работу с точки зрения отличий отHashMap, который является его суперклассом.

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

Полный исходный код всех примеров, использованных в этой статье, можно найти в папкеGitHub project.