Java LinkedHashSet
Use LinkedHashSet em Java para manter a ordem de inserção com as mesmas operações de tempo constante do HashSet.
LinkedHashSet<E> é HashSet<E> com uma promessa extra: ao iterar, você obtém os elementos na ordem em que foram inseridos pela primeira vez. O mecanismo de tabela hash é idêntico — mesmos buckets, mesmo fator de carga, mesmo add, remove e contains em tempo quase constante — mas cada entrada carrega dois ponteiros extras (before, after) que encadeiam as entradas em uma lista duplamente ligada conforme são adicionadas. A iteração percorre essa lista, não o array de buckets.
Se você quer desempenho de conjunto hash e uma ordem de iteração determinística e previsível, LinkedHashSet é a resposta. É quase um upgrade gratuito para os casos em que a ordem não especificada do HashSet já causou problemas.
A regra "primeira inserção vence"
A ordem é fixada pela primeira vez que um elemento é inserido. Re-adicionar um elemento existente não o move:
Set<String> s = new LinkedHashSet<>();
s.add("a");
s.add("b");
s.add("c");
s.add("a"); // already present — returns false, order unchanged
System.out.println(s); // [a, b, c]Isso o torna a ferramenta certa para "lembrar a ordem em que as tags chegaram" ou "registrar eventos únicos em ordem cronológica." Se você remover um elemento e re-adicioná-lo, ele vai para o final da lista — a posição estava vinculada à inserção atual, e a nova é a única que resta.
O custo: ponteiros e mais ponteiros
O mecanismo de ordenação extra tem um custo. Cada entrada armazena não apenas (hash, key, next-in-bucket) como o HashSet, mas (hash, key, next-in-bucket, before, after). São duas referências extras por elemento — aproximadamente 16 bytes extras em uma JVM de 64 bits. Para um conjunto de 10 milhões de Longs, isso representa cerca de 160 MB extras. Para a maioria do código de aplicação isso não é nada; para estruturas de dados do tamanho de cache, faz diferença.
Em troca, você obtém O(1) em cada operação (igual ao HashSet) mais uma ordem de iteração estável que não depende do fator de carga, do rehash, da distribuição do hash ou da versão da JVM.
O custo de iteração é proporcional ao tamanho, não à capacidade
Há um bônus sutil sobre o HashSet: percorrer um LinkedHashSet segue a lista ligada, visitando exatamente size entradas. Iterar um HashSet percorre todos os buckets, visitando aproximadamente capacity slots — incluindo os vazios. Para um conjunto escassamente populado, isso pode ser uma diferença significativa. Se você construir um conjunto, expandi-lo bem além dos elementos que vai manter e depois iterar com frequência, o LinkedHashSet pode na verdade iterar mais rápido.
Quando escolhê-lo
O fluxo de decisão:
- A ordem não importa, você só precisa de verificação rápida de pertencimento →
HashSet. Menor e mais simples. - Você quer que a ordem de inserção seja lembrada →
LinkedHashSet. Mesma velocidade paraadd/contains, iteração previsível. - Você quer ordem classificada →
TreeSet. Algoritmo diferente, operações em tempo logarítmico.
O motivo mais comum para escolher LinkedHashSet é defensivo: você está construindo uma API pública que retorna um Set, e não quer que os chamadores dependam da ordem arbitrária do HashSet. Um LinkedHashSet é a coisa mais gentil que você pode retornar — tem o mesmo contrato que um Set, mas a iteração é reproduzível entre execuções e JVMs, o que torna a saída visível ao usuário estável e os testes mais fáceis de escrever.
Um exemplo prático: tags únicas em ordem de chegada
O programa abaixo constrói dois conjuntos a partir do mesmo fluxo de entradas de tags: um com HashSet, outro com LinkedHashSet. A ordem de iteração do HashSet depende da JVM (é estável mas arbitrária para uma determinada JVM); a ordem do LinkedHashSet é exatamente a ordem em que os elementos únicos apareceram pela primeira vez. Em seguida, mostra a regra de "remover e re-adicionar" e, por fim, constrói um deduplicador que preserva a ordem em apenas duas linhas.
O que observar na execução:
- O
LinkedHashSetimprimiu os eventos únicos na ordem em que apareceram pela primeira vez. OHashSetos imprimiu em uma outra ordem qualquer — o que quer que o layout de buckets determinasse. - Re-adicionar
"a"não alterou a ordem em nada. Removê-lo e re-adicioná-lo o moveu para o final. A primeira inserção é a que ancora a posição. - O deduplicador que preserva a ordem é uma linha quando você conhece o truque: colete em um
LinkedHashSet, depois volte para uma lista. - A varredura de 10 elementos em um
LinkedHashSetde 2 000 000 buckets percorreu exatamente 10 entradas; umHashSetcom a mesma estrutura teria varrido todos os buckets vazios entre eles.
O que vem a seguir
A terceira implementação padrão de Set oferece algo que nem o HashSet nem o LinkedHashSet podem: iteração classificada e a capacidade de fazer consultas de intervalo como "todas as tags entre a e m." O TreeSet é o próximo.