Estrutura De Dados Java - Estrutura De Dados E Algoritmos Em Java - Várias Estruturas
Estrutura De Dados E Algoritmos Em Java - Várias Estruturas

Escolhendo a estrutura certa no dia a dia

A maioria dos desenvolvedores Java começa com Arrays e ArrayLists porque é o que apareceu nos livros. O problema é que Arrays têm tamanho fixo e ArrayLists encostam na parede toda vez que precisam redimensionar. Se você já passou horas debugando um código lento e não sabia o porquê, provavelmente é isso. No meu caso, tive um bug em produção onde uma lista de transações crescia sem controle e o ArrayList ficava realocando memória a cada 1000 elementos. O GC entrava em pico constante. A solução foi calcular o tamanho máximo esperado e inicializar o ArrayList com essa capacity desde o início, ou migrar para um array nativo com ponteiros manuais. Nada mágico, só entender o que acontece por baixo.

estrutura de dados java no código

Vamos ao que realmente importa. Aqui está um exemplo prático de como eu organizo as estruturas hoje em dia:

import java.util.*;

public class EstruturasPraticas {
    public static void main(String[] args) {
        // HashSet: O(1) para busca, remove duplicatas automaticamente
        Set<String> usuariosUnicos = new HashSet<>(1000);
        usuariosUnicos.add("carlos");
        usuariosUnicos.add("ana");
        usuariosUnicos.add("carlos"); // ignorado

        // LinkedHashMap: preserva ordem de inserção, busca O(1)
        Map<String, Integer> cache = new LinkedHashMap<>(16, 0.75f, true);
        cache.put("produtoA", 150);
        cache.put("produtoB", 89);
        // com accessOrder=true, get() move o entry pro final (LRU natural)
        cache.get("produtoA");

        // PriorityQueue: fila de prioridade, útil para job scheduling
        PriorityQueue<Task> fila = new PriorityQueue<>(
            Comparator.comparingInt(Task::prioridade)
        );
        fila.add(new Task("email", 3));
        fila.add(new Task("backup", 1));
        fila.add(new Task("notificacao", 2));

        System.out.println(fila.poll().nome); // backup
    }

    static class Task {
        String nome;
        int prioridade;
        Task(String n, int p) { nome = n; prioridade = p; }
    }
}

Isso parece simples, mas tem coisas que ninguém ensina nos tutoriais. O LinkedHashMap com accessOrder=true, por exemplo, é basicamente uma implementação de cache LRU pronta. Muita gente implementa isso do zero com ConcurrentHashMap e um LinkedList, quando já existe nativo. A diferença de performance é mínima na maioria dos casos, mas a legibilidade melhora muito. Já o HashSet tem uma pegadinha. Se você usar objetos customizados como chave sem sobrescrever hashCode() e equals(), o comportamento será imprevisível. Eu já perdi meio diando um bug assim. O objeto era adicionado ao set, mas depois não era encontrado. A classe tinha equals() sobrescrito mas hashCode() esquecido. O set colocou em um bucket e quando fui buscar, o hashCode retornava algo diferente e o set procurava em outro bucket totalmente.

Quando usar cada coisa

Array nativo: quando você sabe o tamanho exatamente e performance é crítica. Acesso por índice é o mais rápido possível em Java. Mas se precisar crescer, vira dor de cabeça. ArrayList: o coringa. Boa para a maioria dos casos. O único custo real é o redimensionamento periódico. Se souber o tamanho aproximado, passe a capacity no construtor e elimina esse overhead. Custo de memóriapois o array interno pode ficar subutilizado, mas raramente é problema.

👉 Clique no botão abaixo para saber mais sobre o assunto!

LinkedList: quase nunca. A exceção é quando você faz remoções frequentes no meio da lista e já tem o nó referenciado. Na prática, isso raramente acontece. A localização do nó já exige percorrer a lista, então o ganho da remoção O(1) é anul pelo custo de chegada. Além disso, a LinkedList usa muito mais memória por nó porque guarda dois pointers por elemento. Em benchmarks reais, ArrayList costuma vencer LinkedList em quase tudo exceto remoção por iterador já posicionado. HashSet/HashMap: busca e inserção em O(1) médio. O detalhe é o pior caso: colisões de hash podem degradar para O(n). No Java 8+, o HashMap usa árvoresadas quando há muitas colisões em um mesmo bucket, melhorando para O(log n). Mas isso só se aplica a Strings e alguns tipos nativos que têm hashCode() bom. Para objetos customizados com hashCode() ruim, você pode ter degradação séria.

TreeMap/TreeSet: ordenação automática via BST. Útil quando você precisa de range queries ou iterator em ordem. O custo é O(log n) em vez de O(1), e a constante é maior porque a árvore precisa manter balanceamento. Se você só precisa ordenar na hora de imprimir, melhor sortear uma lista depois do que manter um TreeMap só por isso. PriorityQueue: heap binário. Ideal para cenários de scheduling, Dijkstra, ou qualquer coisa que precise extrair o mínimo/regra frequentamente. Não é ordenado completamente, então não serve para percorrer em ordem. O heap internamente é um array, então a localidade de cache é boa.

Armazenamento de dados com estrutura de dados java para persistência leve

Se o objetivo é salvar dados de forma simples sem banco, um Properties ou até um arquivo CSV com leitura própria resolve. Para algo mais estruturado, existe a biblioteca Jackson para JSON, mas isso já é outro assunto. O importante é não reinventar a roda: use o que a stdlib oferece antes de depender de bibliotecas externas. Um problema comum que vejo em projetos novos é a tentação de usar ArrayList quando deveria ser Set, ou HashMap quando deveria ser LinkedHashMap. A diferença é pura semântica. Se a ordem importa, use LinkedHashMap. Se unicidade importa, use Set. Quando você mistura essas escolhas no meio do código, gera bugs sutis que só aparecem em produção sob carga real.

O que realmente faz diferença é entender o trade-off de cada estrutura e escolher com intenção, não por hábito. Isso é o que separa código que funciona de código que funciona bem.