Um guia para o LinkedHashMap em Java

Um guia para o LinkedHashMap em Java

1. Visão geral

Neste artigo, vamos explorar a implementação interna da classeLinkedHashMap. LinkedHashMap é uma implementação comum da interfaceMap.

Essa implementação particular é uma subclasse deHashMape, portanto, compartilha os blocos de construção principais deHashMap implementation. Como resultado, é altamente recomendável revisar isso antes de prosseguir com este artigo.

2. LinkedHashMap vsHashMap

A classeLinkedHashMap é muito semelhante aHashMap na maioria dos aspectos. No entanto, o mapa de hash vinculado é baseado na tabela de hash e na lista vinculada para aprimorar a funcionalidade do mapa de hash.

Ele mantém uma lista duplamente vinculada executando todas as suas entradas, além de uma matriz subjacente de tamanho padrão 16.

Para manter a ordem dos elementos, o hashmap vinculado modifica a classeMap.Entry deHashMap adicionando ponteiros para as entradas anteriores e seguintes:

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

Observe que a classeEntry simplesmente adiciona dois ponteiros; beforeeafter que permitem que ele se conecte à lista vinculada. Além disso, ele usa a implementação de classeEntry de um HashMap.

Por fim, lembre-se de que esta lista vinculada define a ordem da iteração, que por padrão é a ordem de inserção dos elementos (ordem de inserção).

3. Pedido de inserçãoLinkedHashMap

Vamos dar uma olhada em uma instância de mapa hash vinculado que ordena suas entradas de acordo com como são inseridas no mapa. Também garante que essa ordem será mantida durante todo o ciclo de vida do mapa:

@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]);
    }
}

Aqui, estamos simplesmente fazendo um teste rudimentar e inconclusivo sobre a ordem das entradas no mapa de hash vinculado.

Podemos garantir que esse teste sempre será aprovado, pois a ordem de inserção será sempre mantida. We cannot make the same guarantee for a HashMap.

Esse atributo pode ser de grande vantagem em uma API que recebe qualquer mapa, faz uma cópia para manipular e o retorna ao código de chamada. Se o cliente precisar que o mapa retornado seja ordenado da mesma maneira antes de chamar a API, um hashmap vinculado é o caminho a seguir.

A ordem de inserção não será afetada se uma chave for reinserida no mapa.

4. Pedido de acessoLinkedHashMap

LinkedHashMap fornece um construtor especial que nos permite especificar, entre o fator de carga personalizado (LF) e a capacidade inicial,a different ordering mechanism/strategy called access-order:

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

O primeiro parâmetro é a capacidade inicial, seguido pelo fator de carga e olast param is the ordering mode. Então, ao passartrue, descobrimos a ordem de acesso, enquanto o padrão era a ordem de inserção.

Esse mecanismo garante que a ordem de iteração dos elementos seja a ordem em que os elementos foram acessados ​​pela última vez, do menos acessado recentemente ao mais recente.

E assim, a criação de um cache LRU (Least Used Used) é bastante fácil e prático com um tipo de mapa. Uma operaçãoput ouget bem-sucedida resulta em um acesso para a entrada:

@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());
}

Observe comothe order of elements in the key set is transformed as we perform access operations on the map.

Simplificando, qualquer operação de acesso no mapa resulta em uma ordem tal que o elemento que foi acessado apareça por último se uma iteração for executada imediatamente.

Após os exemplos acima, deve ficar óbvio que uma operaçãoputAll gera um acesso de entrada para cada um dos mapeamentos no mapa especificado.

Naturalmente, a iteração sobre uma visualização do mapa não afeta a ordem de iteração do mapa de apoio; only explicit access operations on the map will affect the order.

LinkedHashMap também fornece um mecanismo para manter um número fixo de mapeamentos e para continuar descartando as entradas mais antigas no caso de uma nova precisar ser adicionada.

O métodoremoveEldestEntry pode ser substituído para impor esta política para remover mapeamentos obsoletos automaticamente.

Para ver isso na prática, vamos criar nossa própria classe de mapa hash vinculado, com o único propósito de impor a remoção de mapeamentos obsoletos estendendoLinkedHashMap:

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;
    }

}

Nossa substituição acima permitirá que o mapa cresça para um tamanho máximo de 5 entradas. Quando o tamanho exceder esse valor, cada nova entrada será inserida com o custo de perder a entrada mais antiga no mapa, ou seja, a entrada cuja hora do último acesso precede todas as outras entradas:

@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());
}

Observe como as entradas mais antigas no início do conjunto de chaves continuam caindo quando adicionamos novas ao mapa.

5. Considerações de desempenho

Assim comoHashMap,LinkedHashMap executa as operações básicasMap de adicionar, remover e conter em tempo constante, contanto que a função hash seja bem dimensionada. Ele também aceita uma chave nula, além de valores nulos.

No entanto, issoconstant-time performance of LinkedHashMap is likely to be a little worse than the constant-time of HashMap devido à sobrecarga adicional de manter uma lista duplamente vinculada.

A iteração nas visualizações de coleção deLinkedHashMap também leva um tempo linearO(n) semelhante ao deHashMap. Por outro lado,LinkedHashMap‘s linear time performance during iteration is better than HashMap‘s linear time.

Isso ocorre porque, paraLinkedHashMap,n emO(n) é apenas o número de entradas no mapa, independentemente da capacidade. Considerando que, paraHashMap,n é a capacidade e o tamanho somado,O(size+capacity).

O fator de carga e a capacidade inicial são definidos precisamente como paraHashMap. Observe, entretanto, quethe penalty for choosing an excessively high value for initial capacity is less severe for LinkedHashMap than for HashMap, já que os tempos de iteração para esta classe não são afetados pela capacidade.

6. Concorrência

Assim comoHashMap, a implementação deLinkedHashMap não está sincronizada. Portanto, se você deseja acessá-lo a partir de vários encadeamentos e pelo menos um desses encadeamentos o altera estruturalmente, deve ser sincronizado externamente.

É melhor fazer isso na criação:

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

A diferença comHashMap reside no que acarreta uma modificação estrutural. In access-ordered linked hash maps, merely calling the get API results in a structural modification. Ao lado disso, estão operações comoputeremove.

7. Conclusão

Neste artigo, exploramos a classe JavaLinkedHashMap como uma das implementações mais importantes da interfaceMap em termos de uso. Também exploramos seu funcionamento interno em termos da diferença deHashMap, que é sua superclasse.

Felizmente, depois de ler esta postagem, você pode tomar decisões mais informadas e eficazes sobre qual implementação de mapa empregar no seu caso de uso.

O código-fonte completo para todos os exemplos usados ​​neste artigo pode ser encontrado emGitHub project.