Conceptos fundamentales de Java NIO
Java NIO proporciona un enfoque diferente para las operaciones de entrada/salida, basado en canales y búferes, diseñado para aplicaciones de alta concurrencia. A diferencia de las E/S tradicionales, NIO permite opearciones no bloqueantes mediante selectores.
Comparación entre modelos de E/S
El modelo BIO (Blocking I/O) es síncrono y bloqueante, asignando un hilo por conexión, lo que puede agotar recursos. NIO utiliza un modelo no bloqueante donde un solo hilo gestiona múltiples conexiones mediante multiplexación de E/S.
Componentes esenciales de NIO
NIO se estructura en tres pilares: Canales para la comunicación, Búferes para el almacenamiento temporal y Selectores para la multiplexación de eventos.
Canales de archivo
Para operar con archivos, se utiliza FileChannel. Ejemplo de lectura de archivo:
FileChannel canal = FileChannel.open(Paths.get("entrada.dat"), StandardOpenOption.READ);
ByteBuffer bufferLectura = ByteBuffer.allocate(4096);
int bytesLeidos;
while ((bytesLeidos = canal.read(bufferLectura)) > 0) {
bufferLectura.flip();
while (bufferLectura.hasRemaining()) {
byte dato = bufferLectura.get();
// Procesamiento del dato
}
bufferLectura.compact();
}
canal.force(true);
canal.close();
Canales de socket para clientes
SocketChannel permite la comunicación TCP no bloqueante desde el cliente:
SocketChannel cliente = SocketChannel.open();
cliente.configureBlocking(false);
cliente.connect(new InetSocketAddress("servidor.local", 8443));
while (!cliente.finishConnect()) {
// Espera activa o ejecución de otras tareas
}
ByteBuffer bufferEnvio = ByteBuffer.wrap("Mensaje de prueba".getBytes());
cliente.write(bufferEnvio);
cliente.close();
Canales de socket para servidores
ServerSocketChannel gestiona conexiones entrantes en el servider:
ServerSocketChannel servidor = ServerSocketChannel.open();
servidor.bind(new InetSocketAddress(8080));
servidor.configureBlocking(false);
Selector selector = Selector.open();
servidor.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(1000);
Set<selectionkey> clavesSeleccionadas = selector.selectedKeys();
Iterator<selectionkey> iterador = clavesSeleccionadas.iterator();
while (iterador.hasNext()) {
SelectionKey clave = iterador.next();
if (clave.isAcceptable()) {
SocketChannel clienteConectado = servidor.accept();
clienteConectado.configureBlocking(false);
clienteConectado.register(selector, SelectionKey.OP_READ);
} else if (clave.isReadable()) {
SocketChannel canalCliente = (SocketChannel) clave.channel();
ByteBuffer bufferCliente = ByteBuffer.allocate(1024);
canalCliente.read(bufferCliente);
// Procesamiento de datos recibidos
}
iterador.remove();
}
}
</selectionkey></selectionkey>
Gestión de búferes
Los búferes son regiones de memoria para almacenamiento temporal de datos. Su uso requiere manejo cuidadoso de modos lectura/escritura mediante métodos como flip(), compact() y clear().
ByteBuffer buffer = ByteBuffer.allocateDirect(2048); // Búfer fuera del heap
buffer.putInt(12345);
buffer.putDouble(6.789);
buffer.flip();
int valorEntero = buffer.getInt();
double valorDouble = buffer.getDouble();
buffer.clear();
Optimización con mapeo de memoria
MappedByteBuffer permite mapear archivos directamente en memoria, reduciendo copias de datos entre espacio de usuario y kernel:
try (FileChannel archivo = FileChannel.open(Paths.get("grandes.dat"), StandardOpenOption.READ)) {
MappedByteBuffer bufferMapeado = archivo.map(
FileChannel.MapMode.READ_ONLY, 0, archivo.size()
);
while (bufferMapeado.hasRemaining()) {
byte[] bloque = new byte[1024];
bufferMapeado.get(bloque);
// Procesamiento del bloque
}
}
Selectores para multiplexación
Los selectores permiten monitorear múltiples canales en un solo hilo. El proceso implica registrar canales, sondear eventos y procesar claves de selección.
Selector monitor = Selector.open();
ServerSocketChannel servidorCanal = ServerSocketChannel.open();
servidorCanal.configureBlocking(false);
servidorCanal.register(monitor, SelectionKey.OP_ACCEPT);
while (monitor.select() > 0) {
Set<selectionkey> eventos = monitor.selectedKeys();
for (SelectionKey evento : eventos) {
if (evento.isConnectable()) {
SocketChannel canal = (SocketChannel) evento.channel();
canal.finishConnect();
canal.register(monitor, SelectionKey.OP_READ);
} else if (evento.isReadable()) {
SocketChannel canalLectura = (SocketChannel) evento.channel();
ByteBuffer bufferTemporal = ByteBuffer.allocate(512);
canalLectura.read(bufferTemporal);
// Decodificación y procesamiento
}
}
eventos.clear();
}
</selectionkey>