No contexto do Java NIO, um canal atua como uma via para transferência de dados, permitindo operações de lietura e escrita de forma bidirecional. Diferente dos fluxos tradicionais, que são unidirecionais, os canais suportam operações de lietura e escrita simultâneas, proporcionando uma melhor correspondência com as APIs do sistema operacional subjacente. Os canais encapsulam interações com fontes de dados, como arquivos ou sockets de rede, sem exigir conhecimento detalhado de sua estrutura física, garantindo portabilidade e eficiência.
A API de canais é definida por interfaces, já que implementações específicas variam significativamente entre sistemas operacionais. Isso permite um acesso controlado aos serviços de I/O. Os canais sempre interagem com buffers para transferência de dados: a leitura de um canal ocorre para um buffer, e a escrita a partir de um buffer para o canal.
Principais características dos canais no Java NIO:
- Operações de leitura e escrita bidirecionais.
- Suporte a I/O assíncrono.
- Integração obrigatória com buffers para manipulação de dados.
Implementações notáveis de canais incluem:
- FileChannel: para operações com arquivos.
- DatagramChannel: para comunicação via UDP.
- SocketChannel: para comunicação via TCP.
- ServerSocketChannel: para aceitar conexões TCP, gerando um SocketChannel para cada nova conexão.
Estas implementações cobrem tanto I/O de rede (UDP e TCP) quanto I/O de arquivo.
FileChannel: Detalhes e Exemplos
A classe FileChannel fornece métodos para leitura, escrita e operações de dispersão/coleta, além de funcionalidades específicas para manipulação de arquivos. Para utilizá-la, é necessário obtê-la a partir de um InputStream, OutputStream ou RandomAccessFile.
Exemplo de leitura de dados de um arquivo para um buffer:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class LeituraArquivo {
public static void main(String[] args) throws Exception {
RandomAccessFile acessoArquivo = new RandomAccessFile("dados.txt", "r");
FileChannel canal = acessoArquivo.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesLidos = canal.read(buffer);
while (bytesLidos != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
bytesLidos = canal.read(buffer);
}
acessoArquivo.close();
}
}
Fluxo típico de uso de buffers:
- Escrever dados no bufffer.
- Chamar
flip()para alternar entre modos de leitura e escrita. - Ler dados do buffer.
- Chamar
clear()oucompact()para preparar o buffer para reuso.
Operações com FileChannel
Abrindo um FileChannel: Obtido via objetos como RandomAccessFile.
RandomAccessFile acessoArquivo = new RandomAccessFile("arquivo.dat", "rw");
FileChannel canal = acessoArquivo.getChannel();
Lendo dados: O método read() transfere bytes do canal para um buffer, retornando o número de bytes lidos ou -1 no final do arquivo.
Escrevendo dados: O método write() aceita um buffer como argumento. Deve ser chamado em loop até que todos os dados do buffer sejam escritos.
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class EscritaArquivo {
public static void main(String[] args) throws Exception {
RandomAccessFile acessoArquivo = new RandomAccessFile("saida.txt", "rw");
FileChannel canal = acessoArquivo.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
String conteudo = "Dados gravados: " + new Date().toString();
buffer.clear();
buffer.put(conteudo.getBytes(StandardCharsets.UTF_8));
buffer.flip();
while (buffer.hasRemaining()) {
canal.write(buffer);
}
canal.close();
}
}
Fechando o canal: Essencial para liberar recursos, usando close().
Posicionamento: Os métodos position() permitem obter ou definir a posição atual no arquivo. Posicionar além do final do arquivo pode causar "buracos" no arquivo durante escritas.
Tamanho do arquivo: O método size() retorna o tamanho do arquivo associado.
Truncando o arquivo: truncate() reduz o arquivo para um tamanho especificado, removendo dados além desse ponto.
Forçando gravação: force() garante que os dados em buffer sejam gravados em disco, opcionalmente incluindo metadados do arquivo.
Transferência entre canais: Os métodos transferTo() e transferFrom() permitem copiar dados diretamente entre canais, desde que um deles seja um FileChannel. Eles são eficientes para grandes volumes de dados.
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class TransferenciaDados {
public static void main(String[] args) throws Exception {
RandomAccessFile origem = new RandomAccessFile("origem.bin", "r");
FileChannel canalOrigem = origem.getChannel();
RandomAccessFile destino = new RandomAccessFile("destino.bin", "rw");
FileChannel canalDestino = destino.getChannel();
canalOrigem.transferTo(0, canalOrigem.size(), canalDestino);
canalOrigem.close();
canalDestino.close();
}
}