Mi información de contacto
Correomesophia@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Describe los conocimientos básicos de NIO a nivel ava, para una revisión básica.
En Java, NIO (E/S sin bloqueo o Nueva E/S) es un nuevo conjunto de API de operación de entrada/salida introducido en Java SE 1.4 y versiones posteriores.
En comparación con el modelo IO tradicional, proporciona mayor eficiencia y mejores capacidades de procesamiento concurrente. La característica clave de NIO es su función sin bloqueo, que permite que un solo subproceso administre múltiples canales de E/S, mejorando así en gran medida el rendimiento de la aplicación en escenarios de alta concurrencia.
El escenario de uso de Java NIO es particularmente adecuado para situaciones en las que es necesario procesar una gran cantidad de conexiones simultáneas. Por ejemplo, en un servidor de red, un subproceso puede administrar miles de conexiones de clientes sin la necesidad de asignar un subproceso independiente para cada conexión. Puede reducir significativamente el consumo de recursos del sistema y mejorar las capacidades de procesamiento.
El canal es el medio de flujo de datos en Java NIO. Es bidireccional y puede usarse para leer o escribir datos. La principal ventaja de los canales es que no bloquean, lo que significa que un hilo puede administrar múltiples canales. Cuando no ocurre ningún evento en un canal, el hilo no se bloqueará y, en cambio, podrá manejar otras tareas. Los principales tipos de canales incluyen:
FileChannel
: Se utiliza para operaciones de lectura y escritura de archivos. Se puede utilizar para escribir datos desde el búfer al archivo o para leer datos del archivo al búfer.SocketChannel
: Se utiliza para conexiones TCP en comunicaciones de red y se puede utilizar para leer y escribir datos.ServerSocketChannel
: Se utiliza para aceptar nuevas conexiones SocketChannel, similar al ServerSocket tradicional.DatagramChannel
: Se utiliza para comunicación UDP, que puede enviar y recibir datagramas.Buffer es un objeto utilizado para almacenar datos en NIO. Es una matriz de bytes que puede escribir y leer datos. Un buffer tiene una capacidad específica y tiene dos propiedades importantes: posición y límite.
Los principales tipos de amortiguadores sonByteBuffer
、CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
yDoubleBuffer
, cada tipo corresponde a un tipo de datos primitivo.
Un selector es un multiplexor en NIO que permite que un solo subproceso monitoree eventos de múltiples canales, como lectura, escritura, conexión y recepción de eventos. El selector notifica a la aplicación cuando uno de los canales está listo para operaciones de E/S. El uso de selectores mejora en gran medida las capacidades de procesamiento concurrente de las aplicaciones de red porque no es necesario crear un hilo para cada conexión.
Los principales métodos de selectores incluyen:
select()
: Bloquea hasta que al menos un canal esté listo para operaciones de E/S.selectedKeys()
: Devuelve un conjunto que contiene los objetos SelectionKey de todos los canales preparados.wakeup()
: Interrupción bloqueadaselect()
transferir.SelectionKey
Es la asociación entre selectores y canales. Representa el estado de registro de un canal en el selector, incluidos canales, selectores, colecciones de eventos interesados y colecciones de eventos listos.
Este es el modelo de E/S más tradicional. En Java, las API tradicionales de Socket y ServerSocket se basan en el bloqueo de E/S. En este modo, cuando un subproceso inicia una operación de lectura o escritura, el subproceso se bloquea hasta que se completa la operación de E/S. Si no hay datos para leer para la operación de lectura, o la operación de escritura no se puede completar de inmediato, el hilo esperará hasta que se complete la operación.
Características:
La E/S sin bloqueo es parte del marco Java NIO (Nueva E/S), que permite que los subprocesos inicien operaciones de lectura y escritura sin ser bloqueados. Si no hay datos para leer para una operación de lectura, o la operación de escritura no se puede completar de inmediato, el hilo no se suspenderá y podrá continuar realizando otras tareas.
Características:
La multiplexación consiste en monitorear varios descriptores de archivos al mismo tiempo a través de un hilo y solo operar en un descriptor cuando esté listo (generalmente significa que los datos son legibles o que se puede escribir en el búfer de escritura). Los selectores se utilizan en Java para implementar la multiplexación.
Características:
Aviso:
En aplicaciones prácticas, las E/S sin bloqueo se utilizan a menudo junto con la multiplexación. Por ejemplo, un servidor puede usar un Selector para monitorear múltiples SocketChannels. Cuando un SocketChannel tiene datos que se pueden leer o escribir, el Selector notificará al servidor y el servidor procesará este SocketChannel específico sin bloqueo.
En Java, Stream y Channel son dos formas diferentes de procesar flujos de datos. Pertenecen a la biblioteca IO estándar y a la biblioteca NIO de Java, respectivamente. Aquí hay una comparación detallada de los dos conceptos:
Stream es parte del modelo IO (IO de bloqueo) estándar de Java, que proporciona una forma de leer y escribir datos secuencialmente. Stream se divide en dos tipos: InputStream y OutputStream, que se utilizan para leer y escribir datos respectivamente.Estos flujos pueden ser flujos de bytes (comoInputStream
, OutputStream
) o una secuencia de caracteres (comoReader
, Writer
)。
Características:
close()
método para liberar recursos.A partir de Java 7, la declaración try-with-resources puede cerrar automáticamente la implementaciónAutoCloseable
Recursos de interfaz, incluido Stream. Channel es parte del modelo Java NIO (New IO), que proporciona un mayor nivel de abstracción que Stream, lo que permite un procesamiento de datos más eficiente. Los canales pueden ser bidireccionales, lo que significa que pueden usarse para leer y escribir datos.Los principales tipos de canales incluyenFileChannel
、SocketChannel
、ServerSocketChannel
yDatagramChannel
。
Características:
Resumir
En Java, los modelos de operación de E/S se pueden clasificar según las dos dimensiones de síncrono/asincrónico y de bloqueo/no bloqueo.
definición : El bloqueo de E/S sincrónico es el modelo de E/S más tradicional. Cuando un subproceso llama a una operación de E/S (como lectura o escritura), el subproceso se bloqueará hasta que se complete la operación. Esto significa que el hilo no puede realizar ninguna otra tarea hasta que se complete la operación.
Características:
InputStream
、OutputStream
、Socket
yServerSocket
escena.Ejemplo:
Utilice tradicionalInputStream
yOutputStream
Para leer y escribir archivos:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class SyncBlockingIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
definición : En E/S sincrónicas sin bloqueo, el subproceso no se bloqueará después de llamar a la operación de E/S. Si la operación no se puede completar de inmediato, el método regresará inmediatamente, generalmente devolviendo un valor especial (como -1 o 0) o lanzando una excepción para indicar que la operación no se completó.
Características:
Channels
yBuffers
, llamandoconfigureBlocking(false)
Configure el canal en modo sin bloqueo.Ejemplo:
Usando NIOFileChannel
Para lectura y escritura sin bloqueo:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
public class SyncNonBlockingIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
inChannel.configureBlocking(false);
while (inChannel.read(buffer) > 0) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
definición: Uso del modelo de E/S multiplexadas síncronasSelector
(selector) para monitorear múltiplesChannel
evento.Cuando el hilo llamaSelector.select()
, se bloqueará hasta que haya al menos unoChannel
Se producen eventos (como legibles, escribibles, solicitudes de conexión, etc.).
Características:
Channel
, mejora la concurrencia.Selector
ySelectionKey
Mecanismo que puede manejar eficientemente una gran cantidad de conexiones simultáneas.Ejemplo:
Usando NIOFileChannel
Para lectura y escritura sin bloqueo:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;
public class SyncMultiplexingIOExample {
public static void main(String[] args) throws IOException {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = ssc.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
}
}
keys.clear();
}
}
}
}
}
definición : En el modelo de E/S asíncrono sin bloqueo, el subproceso regresa inmediatamente después de iniciar la operación de E/S sin esperar a que se complete la operación. Cuando se complete la operación, el hilo será notificado de forma asincrónica a través de una función de devolución de llamada o notificación de evento.
Características:
AsynchronousChannel
Interfaz y su implementación de subclase, comoAsynchronousFileChannel
yAsynchronousSocketChannel
。Ejemplo:
Usando NIOAsynchronousFileChannel
Realice lectura y escritura de archivos asincrónica:
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
public class AsyncNonBlockingIOExample {
public static void main(String[] args) throws IOException, InterruptedException {
AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(Paths.get("input.txt"), StandardOpenOption.READ);
AsynchronousFileChannel outChannel = AsynchronousFileChannel.open(Paths.get("output.txt"), StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocate(1024);
CountDownLatch latch = new CountDownLatch(1);
inChannel.read(buffer, 0, buffer, 0, (channel, result) -> {
buffer.flip();
outChannel.write(buffer, 0, buffer, 0, null);
latch.countDown();
});
latch.await();
inChannel.close();
outChannel.close();
}
}
Nota: El modelo asíncrono requiere soporte del sistema operativo subyacente (Kernel)
definición : En teoría, este modelo no existe, porque "asincrónico" significa que la operación se realiza en segundo plano y el hilo no está bloqueado. Por lo tanto, el bloqueo de E/S asíncrono es un concepto contradictorio y no aparecerá en la práctica.
Elegir el modelo de E/S adecuado es fundamental para el rendimiento y la gestión de recursos. En escenarios de alta concurrencia, los modelos de E/S síncronos sin bloqueo o multiplexados síncronos son opciones comunes. En escenarios que requieren rendimiento y velocidad de respuesta extremos, el modelo de E/S asíncrono sin bloqueo es la primera opción.
Concepto de copia cero
La tecnología de copia cero significa que durante el proceso de transmisión de datos de un lugar a otro, no es necesario copiar los datos entre el espacio del usuario y el espacio del kernel, o al menos reduce el número de dichas copias, mejorando así la eficiencia del sistema. En las operaciones de E/S tradicionales, cuando se leen datos de la red o del disco, primero se copian al búfer en el espacio del kernel y luego se copian del espacio del kernel al búfer en el espacio del usuario. Por el contrario, en copia cero, los datos pueden procesarse directamente en el espacio del kernel o transferirse directamente desde el espacio del kernel al dispositivo de red, lo que reduce la operación de copia de la CPU, reduce la sobrecarga del sistema y mejora la eficiencia de la transmisión de datos.
Fuente de copia cero
El concepto de copia cero apareció por primera vez en el diseño de sistemas operativos, con el objetivo de resolver el cuello de botella en el rendimiento causado por la copia de datos en las operaciones de E/S tradicionales. En los primeros sistemas informáticos, todas las operaciones de E/S requerían múltiples copias de datos entre el espacio del usuario y el espacio del kernel. Esto gradualmente se convirtió en un cuello de botella en el rendimiento después de que las redes de alta velocidad y los discos de gran capacidad se volvieron comunes.
Puntos técnicos clave
Implementación en Java:
Java admite la tecnología de copia cero a través del marco NIO (New I/O). NIO presentadoFileChannel
ySocketChannel
y otras clases, que proporcionan operaciones de E/S más eficientes. Específicamente,FileChannel.transferTo()
yFileChannel.transferFrom()
Los métodos pueden transferir datos directamente desde un canal de archivo a un canal de socket o viceversa sin cargar los datos en un búfer, logrando así una copia cero.
Por ejemplo, suponga que necesita enviar el contenido de un archivo grande a la red. El enfoque tradicional es leer primero el contenido del archivo en un búfer y luego escribir el contenido del búfer en la red. Esto implica dos operaciones de copia: una del disco al búfer y otra del búfer a la red.durante el usotransferTo()
Cuando se utiliza este método, los datos se pueden transferir directamente desde el disco a la red sin la necesidad de un búfer intermedio, lo que reduce la cantidad de copias y logra una copia cero.
Un ejemplo usando ByteBuffer:
ByteBuffer
y otraBuffer
clase (comoCharBuffer
,ShortBuffer
etc.) proporcionan buffers que se pueden llenar o vaciar sin involucrar directamente una copia entre el espacio del usuario y el espacio del kernel.ByteBuffer
Se puede utilizar directa o indirectamente en tecnología de copia cero:
ByteBuffer.allocateDirect(size)
CreadoByteBuffer
Las instancias se asignan directamente a la memoria física, sin pasar por el montón de Java.Cuando se compara dicho buffer conFileChannel
oSocketChannel
Cuando se usan juntos, los datos se pueden transferir directamente entre la memoria física y los dispositivos de hardware sin necesidad de copias adicionales a través del montón de Java. Esto permite una verdadera copia cero en algunas plataformas.FileChannel
detransferTo()
ytransferFrom()
: Estos métodos permiten que los datos se almacenen directamente enFileChannel
ySocketChannel
transmitido entreByteBuffer
como intermediario. Esto significa que los datos se pueden transferir directamente del disco a la red o viceversa sin tener que copiarlos entre el espacio del usuario y el espacio del kernel.ByteBuffer
dewrap()
método:wrap()
El método le permite envolver una matriz de bytes existente u otro búfer en unByteBuffer
, por lo que no es necesario copiar los datos a un nuevo búfer. Esto es útil para evitar copias innecesarias de datos.ByteBuffer
deslice()
método:slice()
El método crea una vista del búfer sin copiar los datos subyacentes.Esto significa que una granByteBuffer
Dividir en varios buffers más pequeños sin copiar datos.Combine ByteBuffer con FileChannel:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class ZeroCopyExample {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
long transferred = fileChannel.transferTo(0, fileChannel.size(), System.out);
System.out.println("Transferred bytes: " + transferred);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Aviso:
Debemos tener en cuenta que, de hecho, no existe una copia cero completa. La copia cero mencionada aquí es solo para nuestra aplicación y no tiene una copia a nivel de usuario. Pero incluso a nivel de usuario, es imposible no realizar ninguna operación de copia, sino reducir las copias tanto como sea posible. Por lo tanto, podemos entender que el término copia cero en realidad se refiere a la tecnología de reducir el número de copias de datos. y eso no significa que no exista una operación de copia real.