Um guia para o TreeMap em Java

Um guia para o TreeMap em Java

1. Visão geral

Neste artigo, vamos explorar a implementaçãoTreeMap da interfaceMap do Java Collections Framework (JCF).

TreeMap é uma implementação de mapa que mantém suas entradas classificadas de acordo com a ordem natural de suas chaves ou, melhor ainda, usando um comparador se fornecido pelo usuário no momento da construção.

Anteriormente, cobrimos as implementações deHashMapeLinkedHashMap e perceberemos que há muitas informações semelhantes sobre como essas classes funcionam.

Os artigos mencionados são uma leitura altamente recomendada antes de prosseguir com este.

2. Classificação padrão emTreeMap

Por padrão,TreeMap classifica todas as suas entradas de acordo com sua ordem natural. Para um número inteiro, isso significa ordem crescente e, para cadeias, ordem alfabética.

Vamos ver a ordem natural em um teste:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect() {
    TreeMap map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

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

Observe que colocamos as chaves inteiras de maneira não ordenada, mas ao recuperar o conjunto de chaves, confirmamos que elas são realmente mantidas em ordem crescente. Essa é a ordem natural de números inteiros.

Da mesma forma, quando usamos strings, elas serão classificadas em sua ordem natural, ou seja, alfabeticamente:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect2() {
    TreeMap map = new TreeMap<>();
    map.put("c", "val");
    map.put("b", "val");
    map.put("a", "val");
    map.put("e", "val");
    map.put("d", "val");

    assertEquals("[a, b, c, d, e]", map.keySet().toString());
}

TreeMap, ao contrário de um mapa de hash e um mapa de hash vinculado, não emprega o princípio de hash em qualquer lugar, pois não usa uma matriz para armazenar suas entradas.

3. Classificação personalizada emTreeMap

Se não estivermos satisfeitos com a ordenação natural deTreeMap, também podemos definir nossa própria regra para ordenar por meio de um comparador durante a construção de um mapa de árvore.

No exemplo abaixo, queremos que as chaves inteiras sejam ordenadas em ordem decrescente:

@Test
public void givenTreeMap_whenOrdersEntriesByComparator_thenCorrect() {
    TreeMap map =
      new TreeMap<>(Comparator.reverseOrder());
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

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

Um mapa de hash não garante a ordem das chaves armazenadas e, especificamente, não garante que essa ordem permaneça a mesma ao longo do tempo, mas um mapa em árvore garante que as chaves sempre serão classificadas de acordo com a ordem especificada.

4. Importância da classificação deTreeMap

Agora sabemos queTreeMap armazena todas as suas entradas em ordem classificada. Devido a esse atributo de mapas em árvore, podemos realizar consultas como; encontre “maior”, encontre “menor”, ​​encontre todas as chaves menores ou maiores que um determinado valor etc.

O código abaixo cobre apenas uma pequena porcentagem desses casos:

@Test
public void givenTreeMap_whenPerformsQueries_thenCorrect() {
    TreeMap map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");

    Integer highestKey = map.lastKey();
    Integer lowestKey = map.firstKey();
    Set keysLessThan3 = map.headMap(3).keySet();
    Set keysGreaterThanEqTo3 = map.tailMap(3).keySet();

    assertEquals(new Integer(5), highestKey);
    assertEquals(new Integer(1), lowestKey);
    assertEquals("[1, 2]", keysLessThan3.toString());
    assertEquals("[3, 4, 5]", keysGreaterThanEqTo3.toString());
}

5. Implementação interna deTreeMap

TreeMap implementa a interfaceNavigableMap e baseia seu trabalho interno nos princípios de árvores vermelhas e pretas:

public class TreeMap extends AbstractMap
  implements NavigableMap, Cloneable, java.io.Serializable

O princípio das árvores vermelhas e pretas está além do escopo deste artigo, no entanto, há coisas importantes a serem lembradas para entender como elas se encaixam emTreeMap.

First of all, uma árvore vermelho-preto é uma estrutura de dados que consiste em nós; imagine uma mangueira invertida com sua raiz no céu e os galhos crescendo para baixo. A raiz conterá o primeiro elemento adicionado à árvore.

A regra é que, a partir da raiz, qualquer elemento na ramificação esquerda de qualquer nó é sempre menor que o elemento no próprio nó. Os da direita são sempre maiores. O que define maior ou menor do que é determinado pela ordem natural dos elementos ou pelo comparador definido na construção, como vimos anteriormente.

Essa regra garante que as entradas de um mapa da árvore estejam sempre em ordem classificada e previsível.

Secondly, uma árvore vermelho-preto é uma árvore de busca binária que se equilibra automaticamente. Este atributo e o acima garantem que operações básicas como pesquisar, obter, colocar e remover levem tempo logarítmicoO(log n).

Ser auto-equilibrado é a chave aqui. À medida que continuamos inserindo e excluindo entradas, imagine a árvore crescendo mais em uma borda ou mais curta na outra.

Isso significaria que uma operação levaria mais tempo no ramo mais curto e mais tempo no ramo que está mais distante da raiz, algo que não queremos que aconteça.

Portanto, isso é tratado no design de árvores vermelho-pretas. Para cada inserção e exclusão, a altura máxima da árvore em qualquer borda é mantida emO(log n), ou seja, a árvore se equilibra continuamente.

Assim como o mapa de hash e o mapa de hash vinculado, um mapa em árvore não é sincronizado e, portanto, as regras para usá-lo em um ambiente multithread são semelhantes às das outras duas implementações de mapas.

6. Escolhendo o mapa certo

Tendo visto as implementações deHashMapeLinkedHashMap anteriormente e agoraTreeMap, é importante fazer uma breve comparação entre os três para nos orientar sobre qual deles se encaixa onde.

A hash map é bom como uma implementação de mapa de propósito geral que fornece operações rápidas de armazenamento e recuperação. No entanto, fica aquém por causa de seu arranjo caótico e desordenado de entradas.

Isso faz com que ele tenha um desempenho ruim em cenários em que há muita iteração, pois toda a capacidade da matriz subjacente afeta a passagem, além do número de entradas.

A linked hash map possui os bons atributos de mapas hash e adiciona ordem às entradas. Ele tem melhor desempenho onde há muita iteração, porque apenas o número de entradas é levado em consideração, independentemente da capacidade.

A tree map leva o pedido para o próximo nível, fornecendo controle completo sobre como as chaves devem ser classificadas. Por outro lado, oferece desempenho geral pior do que as outras duas alternativas.

Poderíamos dizer umlinked hash map reduces the chaos in the ordering of a hash map without incurring the performance penalty of a tree map.

7. Conclusão

Neste artigo, exploramos a classe JavaTreeMap e sua implementação interna. Como é o último de uma série de implementações comuns da interface do Mapa, também discutimos brevemente onde ele se encaixa melhor em relação aos outros dois.

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