Acesso Aleatório a Arquivos em Java com RandomAccessFile

A classe java.io.RandomAccessFile é uma ferramenta poderosa e versátil no ecossistema de E/S (Entrada/Saída) de arquivos em Java. Diferente dos fluxos sequenicais tradicionais, que leem ou escrevem dados do início ao fim, RandomAccessFile permite que um programa navegue e modifique o conteúdo de um arquivo em qualquer posição desejada. Isso a torna ideal para cenários onde apenas partes específicas de um arquivo precisam ser lidas, atualizadas ou para adicionar conteúdo ao final de arquivos existentes.

Uma instância de RandomAccessFile mantém um "ponteior de arquivo" interno, que indica a posição atual para operações de leitura ou escrita. Este ponteiro pode ser manipulado explicitamente através de dois métodos essenciais:

  • long getFilePointer(): Retorna a posição atual do ponteiro de arquivo em bytes.
  • void seek(long pos): Move o ponteiro de arquivo para a posição especificada (pos), medida a partir do início do arquivo.

Modos de Abertura

Ao criar uma instância de RandomAccessFile, é necessário especificar um modo de acesso, que define as operações permitidas no arquivo:

  • "r": Abre o arquivo para leitura. Se o arquivo não existir, uma FileNotFoundException é lançada.
  • "rw": Abre o arquivo para leitura e escrita. Se o arquivo não existir, ele será criado.
  • "rws": Semelhante a "rw", mas exige que todas as atualizações no conteúdo ou metadados do arquivo sejam escritas sincronicamente no dispositivo de armazenamento subjacente.
  • "rwd": Semelhante a "rw", mas exige que as atualizações apenas no conteúdo do arquivo (não nos metadados) sejam escritas sincronicamente no dispositivo de armazenamento.

Exemplo 1: Lendo uma Seção Específica de um Arquivo

Este exemplo demonstra como usar RandomAccessFile para ler dados a partir de uma posição arbitrária em um arquivo, sem precisar processá-lo desde o início.


import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;

public class LeituraSegmentadaArquivo {

    public static void main(String[] args) {
        String caminhoArquivo = "C:/temp/exemploLeitura.txt"; // Altere para um caminho válido no seu sistema
        long posicaoInicial = 50; // Iniciar leitura a partir do byte 50

        // Criar um arquivo de exemplo para teste e preenchê-lo
        try (RandomAccessFile escritorInicial = new RandomAccessFile(caminhoArquivo, "rw")) {
            escritorInicial.writeBytes("Este é o início do arquivo. "
                                     + "Precisamos de um texto longo o suficiente para testar a leitura segmentada. "
                                     + "Vamos preencher o arquivo com algumas frases adicionais para garantir que tenhamos dados além da posição inicial. "
                                     + "Conteúdo importante a ser lido está aqui no meio! "
                                     + "Fim do arquivo de exemplo para RandomAccessFile.");
        } catch (IOException e) {
            System.err.println("Erro ao preparar o arquivo de teste: " + e.getMessage());
            return; // Encerrar se não for possível preparar o arquivo
        }

        try (RandomAccessFile arquivoAcesso = new RandomAccessFile(caminhoArquivo, "r")) {
            System.out.println("Ponteiro inicial: " + arquivoAcesso.getFilePointer());
            
            // Mover o ponteiro para a posição desejada
            arquivoAcesso.seek(posicaoInicial);
            System.out.println("Ponteiro após seek(" + posicaoInicial + "): " + arquivoAcesso.getFilePointer());

            byte[] bufferLeitura = new byte[128]; // Buffer para ler 128 bytes por vez
            int bytesLidos;

            System.out.println("\n--- Conteúdo lido a partir da posição " + posicaoInicial + " ---");
            // Ler o arquivo a partir da nova posição até o fim
            while ((bytesLidos = arquivoAcesso.read(bufferLeitura)) > 0) {
                System.out.print(new String(bufferLeitura, 0, bytesLidos, StandardCharsets.UTF_8));
            }
            System.out.println("\n--- Fim da Leitura ---");

        } catch (FileNotFoundException e) {
            System.err.println("Erro: O arquivo não foi encontrado em " + caminhoArquivo);
        } catch (IOException e) {
            System.err.println("Erro de E/S durante a leitura: " + e.getMessage());
        }
    }
}

Exemplo 2: Anexando Conteúdo ao Final de um Arquivo

Adicionar conteúdo ao final de um arquivo existente é um caso de uso comum para RandomAccessFile. Isso é feito movendo o ponteiro de arquivo para o tamanho total do arquivo antes de iniciar a escrita.


import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

public class AnexarConteudoArquivo {

    public static void main(String[] args) {
        String nomeArquivoLog = "C:/temp/eventos.log"; // Altere para um caminho válido

        // Criar ou garantir que o arquivo exista e tenha um conteúdo inicial se estiver vazio
        try (RandomAccessFile verificador = new RandomAccessFile(nomeArquivoLog, "rw")) {
            if (verificador.length() == 0) {
                verificador.writeBytes("Log de sistema iniciado em " + LocalDateTime.now() + "\n");
            }
        } catch (IOException e) {
            System.err.println("Erro ao preparar o arquivo de log: " + e.getMessage());
            return;
        }

        try (RandomAccessFile escritorDeLog = new RandomAccessFile(nomeArquivoLog, "rw")) {
            // Mover o ponteiro para o final do arquivo (tamanho atual)
            escritorDeLog.seek(escritorDeLog.length());
            
            String novoRegistro = LocalDateTime.now() + " - Novo evento registrado: Operação XYZ concluída.\n";
            escritorDeLog.write(novoRegistro.getBytes(StandardCharsets.UTF_8));
            System.out.println("Conteúdo anexado com sucesso ao arquivo: " + nomeArquivoLog);
            System.out.println("Tamanho atual do arquivo: " + escritorDeLog.length() + " bytes");
        } catch (IOException e) {
            System.err.println("Erro ao anexar conteúdo: " + e.getMessage());
        }
    }
}

Exemplo 3: Inserindo Conteúdo em uma Posição Específica

A inserção de conteúdo no meio de um arquivo é uma operação mais complexa, pois requer deslocar os dados existentes para abrir espaço. A abordagem comum envolve o uso de um arquivo temporário para armazenar a porção do arquivo que vem após o ponto de inserção.


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;

public class InserirNoMeioDoArquivo {

    /**
     * Insere uma string de texto em uma posição específica de um arquivo.
     *
     * @param caminhoArquivo O caminho completo para o arquivo onde o conteúdo será inserido.
     * @param offset A posição (em bytes) onde o novo conteúdo deve ser inserido.
     * @param textoParaInserir O texto a ser inserido no arquivo.
     * @throws IOException Se ocorrer um erro de E/S durante a operação.
     */
    public static void inserirTexto(String caminhoArquivo, long offset, String textoParaInserir) throws IOException {
        // Criar um arquivo temporário para guardar a parte final do arquivo original
        File arquivoTemporario = File.createTempFile("tempFilePart", ".tmp");
        arquivoTemporario.deleteOnExit(); // Garantir que o temporário seja deletado ao sair do programa

        try (RandomAccessFile arquivoOriginal = new RandomAccessFile(caminhoArquivo, "rw");
             FileOutputStream saidaTemp = new FileOutputStream(arquivoTemporario);
             FileInputStream entradaTemp = new FileInputStream(arquivoTemporario)) {

            // Mover o ponteiro do arquivo original para o ponto de inserção
            arquivoOriginal.seek(offset);

            // --- Etapa 1: Copiar o restante do arquivo original para o arquivo temporário ---
            byte[] buffer = new byte[1024];
            int bytesLidos;
            while ((bytesLidos = arquivoOriginal.read(buffer)) > 0) {
                saidaTemp.write(buffer, 0, bytesLidos);
            }

            // --- Etapa 2: Voltar ao ponto de inserção no arquivo original e escrever o novo conteúdo ---
            arquivoOriginal.seek(offset);
            arquivoOriginal.write(textoParaInserir.getBytes(StandardCharsets.UTF_8));

            // --- Etapa 3: Adicionar o conteúdo do arquivo temporário de volta ao arquivo original ---
            while ((bytesLidos = entradaTemp.read(buffer)) > 0) {
                arquivoOriginal.write(buffer, 0, bytesLidos);
            }
            System.out.println("Conteúdo inserido com sucesso em " + caminhoArquivo + " na posição " + offset);

        } catch (IOException e) {
            System.err.println("Erro ao inserir conteúdo no arquivo: " + e.getMessage());
            throw e; // Re-lança a exceção após logar para que o chamador possa lidar
        }
    }

    public static void main(String[] args) {
        String arquivoAlvo = "C:/temp/meuDocumento.txt"; // Altere para um caminho válido
        String conteudoBase = "Linha inicial.\nLinha do meio, aqui faremos a inserção.\nLinha final.";
        
        // Preparar o arquivo com conteúdo inicial para o teste
        try (RandomAccessFile preparador = new RandomAccessFile(arquivoAlvo, "rw")) {
            preparador.write(conteudoBase.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            System.err.println("Erro ao preparar arquivo base: " + e.getMessage());
            return;
        }

        try {
            // Calcular a posição para inserir (ex: após "meio, ")
            long posicaoParaInserir = conteudoBase.indexOf("meio, ") + "meio, ".length();
            
            String textoNovo = "[*** CONTEÚDO NOVO INSERIDO ***] ";
            inserirTexto(arquivoAlvo, posicaoParaInserir, textoNovo);

            // Opcional: Ler e exibir o arquivo modificado para verificar a inserção
            System.out.println("\n--- Conteúdo final do arquivo " + arquivoAlvo + " ---");
            try (RandomAccessFile leitorFinal = new RandomAccessFile(arquivoAlvo, "r")) {
                byte[] bufferFinal = new byte[128];
                int bytesLidosFinal;
                while((bytesLidosFinal = leitorFinal.read(bufferFinal)) > 0){
                    System.out.print(new String(bufferFinal, 0, bytesLidosFinal, StandardCharsets.UTF_8));
                }
            }
            System.out.println("\n--- Fim do Conteúdo ---");

        } catch (IOException e) {
            System.err.println("Falha na operação de inserção: " + e.getMessage());
        }
    }
}

Tags: JavaIO RandomAccessFile ManipulacaoDeArquivos AcessoDireto FluxoDeBytes

Publicado em 7-17 14:07