Professor Muniz Educação & Programação

Trilha: Estrutura de Dados

Voltar aos conteúdos
Apostila de Apoio Didático

Estrutura de Dados: Filas (FIFO)

Análise e Implementação de Filas Lineares, Aritmética Modular para Fila Circular e Gestão de Memória em Java

MÓDULO 1

O Conceito FIFO e a Estrutura de Fila

1.1 O que é uma Fila?

A Fila (ou Queue) é uma estrutura de dados linear e restrita que opera sob a disciplina de acesso FIFO (First-In, First-Out) — o primeiro elemento a entrar na estrutura deve ser obrigatoriamente o primeiro a sair.

Diferente das pilhas (onde tudo ocorre no topo) ou de arrays comuns (onde qualquer índice pode ser acessado livremente), em uma fila as operações ocorrem em duas extremidades distintas: a inserção de novos dados é feita sempre no fim (ou traseira/rear) e a remoção ou consulta ocorre sempre no início (ou frente/front).

1.2 Analogias e Funcionamento

A analogia clássica mais intuitiva é a fila de atendimento de um banco: a primeira pessoa que chega é a primeira a ser atendida pelo caixa, enquanto as próximas pessoas aguardam ordenadamente no final da fila.

No ecossistema de software e na arquitetura de sistemas operacionais, esse padrão é essencial em:

  • Fila de Impressão (Print Spooler): Garante que os documentos impressos por múltiplos usuários sejam processados estritamente na ordem em que foram enviados.
  • Escalonamento de Processos no S.O. (Round-Robin / Ready Queue): O sistema operacional gerencia threads e processos prontos para execução em uma fila de espera da CPU.
  • Mensageria e Buffers Assíncronos (RabbitMQ, Apache Kafka, Redis Queue): Permite a comunicação desacoplada entre microsserviços com garantia de processamento ordenado de mensagens.
Entrada (Fim / Enfileirar)
➜
40
30
20
10 1º
➜
Saída (Início / Desenfileirar)
Esquema do Fluxo FIFO: Elementos entram pelo fim e saem pelo início da estrutura
MÓDULO 2

Vetores Estáticos vs. Fila Circular

2.1 O Modelo de Armazenamento Estático para Filas

Implementar uma fila utilizando vetores estáticos em Java oferece alto desempenho e controle de baixo nível sobre a memória, mas impõe um desafio geométrico peculiar:

Vantagens Técnicas
  • Previsibilidade Temporal: Inserções e remoções operam em complexidade de tempo constante O(1), pois os ponteiros acessam posições indexadas diretamente.
  • Localidade Espacial: Os elementos residem contiguamente na memória física, garantindo máxima taxa de acerto no Cache L1/L2 do processador.
  • Baixo Impacto no Garbage Collector: Não gera criação ou destruição contínua de objetos nó em tempo de execução.
O Problema do Desperdício Linear

Em um vetor linear simples, ao removermos elementos do inicio, o índice da frente avança. As primeiras posições tornam-se espaços mortos inacessíveis. Quando o índice fim chega ao final físico do array, o sistema acusa que a fila está cheia (falso Overflow), mesmo que haja posições livres no início!

2.2 Soluções Arquiteturais

Abordagem 1 Deslocamento de Itens (Shift Array)

Ao remover do início, movemos todos os itens restantes uma posição para a esquerda. Desvantagem: Degrada o desempenho de remoção de O(1) para O(N), tornando a operação inviável para grandes volumes.

Abordagem 2 Fila Circular com Aritmética Modular (Solução Ótima)

Fazemos com que os ponteiros inicio e fim "deem a volta" para o índice 0 ao atingirem a borda do array usando a operação de resto da divisão: (indice + 1) % tamanho. Mantém todas as operações em O(1) sem desperdiçar um único byte!

Abordagem 3 Fila Dinâmica Encadeada (Linked Queue)

Utiliza nós dinâmicos apontados por referências. Excelente para quando a capacidade máxima da fila é imprevisível em tempo de compilação.

MÓDULO 3

Gestão de Erros de Fila: Overflow e Underflow

3.1 O que é Overflow (Estouro)?

Em uma fila de capacidade fixa, o erro de Overflow ocorre quando solicitamos a inserção de um elemento (enfileirar) e a quantidade de dados armazenados já atingiu a capacidade limite da estrutura. Tentar escrever sem checagem causaria sobrescrita indevida ou exceção de índice fora dos limites (ArrayIndexOutOfBoundsException).

3.2 O que é Underflow (Esvaziamento)?

O erro de Underflow ocorre no sentido inverso: quando uma rotina solicita a remoção (desenfileirar) ou a consulta (espiarInicio) de um elemento em uma fila que se encontra completamente vazia (total == 0).

Dica de Projeto Defensivo

Em filas circulares, manter uma variável de controle explícita (ex: total de elementos) simplifica drasticamente as verificações de estado (vazia e cheia), eliminando a ambiguidade clássica onde inicio == fim pode significar tanto fila cheia quanto fila vazia.
MÓDULO 4

Os Métodos Fundamentais de Controle

Abaixo está a interface pública padronizada que garante o encapsulamento, robustez e alta coesão na manipulação da Fila Circular:

Método Descrição Técnica Comportamento dos Índices
enfileirar(valor) / enqueue Valida se a fila está cheia. Se houver espaço, grava no fim e incrementa total. fim = (fim + 1) % tamanho
desenfileirar() / dequeue Valida se a fila está vazia. Se contiver itens, recupera o primeiro e decrementa total. inicio = (inicio + 1) % tamanho
espiarInicio() / peek Consulta e retorna o elemento atual da frente sem removê-lo. Inalterado
verificarFilaVazia() Verifica se a fila não possui elementos ativos (total == 0). Inalterado
verificarFilaCheia() Verifica se a fila atingiu a capacidade máxima (total == tamanho). Inalterado
obterTotal() / size Retorna a contagem atual de elementos armazenados. Inalterado
MÓDULO 5

Implementação Completa em Java

Abaixo apresentamos a classe FilaCircular.java com tratamento rigoroso contra Overflow e Underflow utilizando a aritmética modular para reaproveitamento total da memória alocada:

public class FilaCircular {
    private int inicio;     // Aponta para o elemento que está na frente da fila
    private int fim;        // Aponta para a próxima posição livre no fim da fila
    private int total;      // Controla a quantidade real de elementos presentes
    private int[] itens;    // Vetor que armazena os dados fisicamente

    // Construtor que inicializa a fila com capacidade estática
    public FilaCircular(int tamanho) {
        this.inicio = 0;
        this.fim = 0;
        this.total = 0;
        this.itens = new int[tamanho];
    }

    // Verifica se a fila está vazia
    public boolean verificarFilaVazia() {
        return this.total == 0;
    }

    // Verifica se a fila atingiu sua capacidade física limite
    public boolean verificarFilaCheia() {
        return this.total == this.itens.length;
    }

    // Adiciona um valor no fim da fila (ENQUEUE) com proteção contra Overflow
    public void enfileirar(int valor) {
        if (verificarFilaCheia()) {
            System.out.println("[Erro] Overflow! A fila está cheia. Não foi possível inserir: " + valor);
            return;
        }
        this.itens[this.fim] = valor;
        // Aritmética Modular: reinicia o índice para 0 ao ultrapassar o fim físico do array
        this.fim = (this.fim + 1) % this.itens.length; 
        this.total++;
        System.out.println("Inserido: " + valor + " | Total de elementos na Fila: " + this.total);
    }

    // Remove o elemento da frente da fila (DEQUEUE) com proteção contra Underflow
    public int desenfileirar() {
        if (verificarFilaVazia()) {
            throw new RuntimeException("[Erro] Underflow! A fila está vazia. Impossível remover.");
        }
        int valorRemovido = this.itens[this.inicio];
        // Avanço circular do ponteiro de início
        this.inicio = (this.inicio + 1) % this.itens.length;
        this.total--;
        System.out.println("Removido: " + valorRemovido + " | Total de elementos restantes: " + this.total);
        return valorRemovido;
    }

    // Consulta o elemento que está na frente (PEEK) sem alterá-lo
    public int espiarInicio() {
        if (verificarFilaVazia()) {
            throw new RuntimeException("A fila está vazia. Não há elementos para consultar.");
        }
        return this.itens[this.inicio];
    }

    // Retorna a quantidade atual de itens
    public int obterTotal() {
        return this.total;
    }
}

Estudo de Caso Prático: Testando o Reaproveitamento Circular

No código de teste a seguir, demonstramos na prática como a fila circular aceita novas inserções após remoções parciais, reaproveitando os índices iniciais liberados sem erro de Overflow.

Aplicação da Fila (AppFila.java)
public class AppFila {
    public static void main(String[] args) {
        System.out.println("--- Execução da Fila Circular ---\n");

        // Instancia uma Fila Circular com capacidade para 3 elementos
        FilaCircular filaAtendimento = new FilaCircular(3);

        // 1. Enfileirando elementos até preencher
        filaAtendimento.enfileirar(101); // Senha 101
        filaAtendimento.enfileirar(102); // Senha 102
        filaAtendimento.enfileirar(103); // Senha 103

        // 2. Testando tentativa de estouro (Overflow)
        filaAtendimento.enfileirar(104); // Deve acusar fila cheia

        System.out.println("\nPróximo a ser atendido: " + filaAtendimento.espiarInicio());

        // 3. Atendendo clientes (Desenfileirando 2 posições)
        System.out.println("\n--- Atendimento no Caixa ---");
        filaAtendimento.desenfileirar(); // Remove 101
        filaAtendimento.desenfileirar(); // Remove 102

        // 4. Reaproveitamento Circular de Memória
        System.out.println("\n--- Testando Inserção Circular nos Espaços Liberados ---");
        filaAtendimento.enfileirar(104); // Inserido circularmente!
        filaAtendimento.enfileirar(105); // Inserido normalmente!

        // 5. Esvaziando totalmente a estrutura
        System.out.println("\n--- Descarregando o Restante da Fila ---");
        while (!filaAtendimento.verificarFilaVazia()) {
            filaAtendimento.desenfileirar();
        }
    }
}
MÓDULO 6

Prática de Laboratório (Mãos à Obra!)

Desafio Prático: Simulador de Atendimento com Prioridade 2:1

Utilizando a estrutura de FilaCircular, implemente um sistema de controle de atendimento bancário com duas filas distintas: Preferencial (idosos, gestantes) e Comum.

Especificações do Desafio:
  1. Regra 1: Mantenha duas instâncias de fila: filaPreferencial e filaComum.
  2. Regra 2 (Proporção 2:1): Para cada 2 clientes atendidos da fila preferencial, atenda obrigatoriamente 1 cliente da fila comum.
  3. Regra 3 (Fluxo Suave): Se a fila preferencial estiver vazia, atenda os clientes da fila comum sequencialmente sem interrupção. Se a fila comum estiver vazia, continue atendendo a preferencial.
  4. Regra 4: Crie um método atenderProximo() na classe controladora que decida a fila correta de acordo com as regras acima e exiba a senha no console.
Gabarito Recomendado de Resolução:
public class SimuladorAtendimento {
    private FilaCircular filaPreferencial;
    private FilaCircular filaComum;
    private int contadorPreferencial;

    public SimuladorAtendimento(int capacidade) {
        this.filaPreferencial = new FilaCircular(capacidade);
        this.filaComum = new FilaCircular(capacidade);
        this.contadorPreferencial = 0;
    }

    public void adicionarPreferencial(int senha) {
        filaPreferencial.enfileirar(senha);
    }

    public void adicionarComum(int senha) {
        filaComum.enfileirar(senha);
    }

    public void atenderProximo() {
        if (filaPreferencial.verificarFilaVazia() && filaComum.verificarFilaVazia()) {
            System.out.println("[Atendimento] Ambas as filas estão vazias.");
            return;
        }

        // Atende Preferencial se houver pessoas E (não atingiu a cota de 2 OU a Comum está vazia)
        if (!filaPreferencial.verificarFilaVazia() && (contadorPreferencial < 2 || filaComum.verificarFilaVazia())) {
            int senha = filaPreferencial.desenfileirar();
            contadorPreferencial++;
            System.out.println(">> Chamando PREFERENCIAL: Senha " + senha + " (Atendimentos seguidos: " + contadorPreferencial + "/2)");
        } 
        // Caso contrário, atende a Comum se houver pessoas
        else if (!filaComum.verificarFilaVazia()) {
            int senha = filaComum.desenfileirar();
            contadorPreferencial = 0; // Reinicia a contagem de preferenciais
            System.out.println(">> Chamando COMUM: Senha " + senha);
        }
    }

    public static void main(String[] args) {
        SimuladorAtendimento banco = new SimuladorAtendimento(10);

        // Simulando chegada de clientes
        banco.adicionarPreferencial(201);
        banco.adicionarPreferencial(202);
        banco.adicionarPreferencial(203);
        banco.adicionarComum(101);
        banco.adicionarComum(102);

        System.out.println("--- Início do Atendimento Bancário (Regra 2:1) ---");
        banco.atenderProximo(); // Preferencial 201 (1/2)
        banco.atenderProximo(); // Preferencial 202 (2/2)
        banco.atenderProximo(); // Comum 101 (reseta contador)
        banco.atenderProximo(); // Preferencial 203 (1/2)
        banco.atenderProximo(); // Comum 102 (reseta contador)
    }
}
COMPARATIVO

Comparativo Definitivo: FILA vs. PILHA

Sintetize e consolide as distinções estruturais entre as duas principais estruturas de dados sequenciais lineares:

Característica FILA (Queue) PILHA (Stack)
Protocolo de Acesso FIFO (First-In, First-Out) LIFO (Last-In, First-Out)
Pontos de Operação Duas extremidades: insere no fim, remove no início Uma única extremidade: todas as operações ocorrem no topo
Ponteiros de Controle inicio e fim topo
Terminologia em Métodos enfileirar() / desenfileirar() empilhar() / desempilhar()
Principal Desafio (Vetor) Desperdício de memória no início (resolvido por Fila Circular) Estouro de capacidade física (Overflow ao passar do limite)
Casos de Uso Típicos Spooler de impressão, filas de mensagens, agendamento de SO Desfazer/Refazer (Ctrl+Z), histórico do navegador, Call Stack

Fim deste Conteúdo

Aqui encerra-se a nossa análise sobre estruturas lineares sequenciais baseadas em vetores estáticos (Pilhas e Filas). Na próxima aula, avançaremos para o estudo de Listas Encadeadas (Alocação Dinâmica e Referências de Memória) em Java.

Materiais Complementares de Apoio

Slides da Aula: Filas (FIFO) & Fila Circular

Acesse o slide de apoio didático estruturado em PDF/PowerPoint, contendo toda a parte visual, esquemas de fila circular e diagramas de classe UML abordados.

Baixar Slides
Videoaula Relacionada: FILAS - Java

Assista à explicação detalhada com resoluções de problemas e exemplos práticos de implementação de filas circulares.

Assistir Vídeo
Repositório de Código Fonte

Explore, acesse e faça o download de todos os códigos-fonte de exemplo e exercícios estruturados nesta trilha.

Abrir Repositório
Voltar aos conteúdos