Anteriormente conocimos la API IO (Interfaz de programación de aplicaciones de entrada/salida) y el paquete java.io , cuyas clases son principalmente para trabajar con flujos en Java. La clave aquí es el concepto de flujo .

Hoy comenzaremos a considerar la API NIO (Nueva entrada/salida).

La principal diferencia entre los dos enfoques de E/S es que la API de IO está orientada a la transmisión, mientras que la API de NIO está orientada al búfer. Entonces, los conceptos principales que hay que entender son los búferes y los canales .

¿Qué es un búfer y qué es un canal?

Un canal es un portal lógico a través del cual entran y salen datos, mientras que un búfer es el origen o el destino de estos datos transmitidos. Durante la salida, los datos que desea enviar se colocan en un búfer y el búfer pasa los datos al canal. Durante la entrada, los datos del canal se colocan en el búfer.

En otras palabras:

  • un búfer es simplemente un bloque de memoria en el que podemos escribir información y desde el cual podemos leer información,
  • un canal es una puerta de enlace que proporciona acceso a dispositivos de E/S, como archivos o sockets.

Los canales son muy similares a las secuencias del paquete java.io. Todos los datos que van a cualquier lugar (o provienen de cualquier lugar) deben pasar a través de un objeto de canal. En general, para usar el sistema NIO, obtiene un canal a una entidad de E/S y un búfer para almacenar datos. Luego, trabaja con el búfer, ingresando o enviando datos según sea necesario.

Puede avanzar y retroceder en un búfer, es decir, "caminar" por el búfer, algo que no podría hacer en flujos. Esto da más flexibilidad al procesar datos. En la biblioteca estándar, los búferes están representados por la clase Buffer abstracta y varios de sus descendientes:

  • Buffer de bytes
  • CharBuffer
  • Búfer corto
  • IntBuffer
  • Buffer flotante
  • búfer doble
  • búfer largo

La principal diferencia entre las subclases es el tipo de datos que almacenan: bytes , ints , longs y otros tipos de datos primitivos.

Propiedades tampón

Un búfer tiene cuatro propiedades principales. Estos son capacidad, límite, posición y marca.

La capacidad es la cantidad máxima de datos/bytes que se pueden almacenar en el búfer. La capacidad de un búfer no se puede cambiar . Una vez que un búfer está lleno, debe borrarse antes de escribir más en él.

En el modo de escritura, el límite de un búfer es el mismo que su capacidad, lo que indica la cantidad máxima de datos que se pueden escribir en el búfer. En el modo de lectura, el límite de un búfer se refiere a la cantidad máxima de datos que se pueden leer del búfer.

La posición indica la posición actual del cursor en el búfer. Inicialmente, se establece en 0 cuando se crea el búfer. En otras palabras, es el índice del siguiente elemento a leer o escribir.

La marca se utiliza para guardar una posición del cursor. A medida que manipulamos un búfer, la posición del cursor cambia constantemente, pero siempre podemos devolverlo a la posición previamente marcada.

Métodos para trabajar con un búfer

Ahora veamos el conjunto principal de métodos que nos permiten trabajar con nuestro búfer (bloque de memoria) para leer y escribir datos hacia y desde los canales.

  1. allocate(int capacity) — este método se utiliza para asignar un nuevo búfer con la capacidad especificada. El método allocate() lanza una IllegalArgumentException si la capacidad pasada es un entero negativo.

  2. capacity() devuelve la capacidad del búfer actual .

  3. position() devuelve la posición actual del cursor. Las operaciones de lectura y escritura mueven el cursor al final del búfer. El valor devuelto siempre es menor o igual que el límite.

  4. limit() devuelve el límite del búfer actual.

  5. mark() se usa para marcar (guardar) la posición actual del cursor.

  6. reset() devuelve el cursor a la posición previamente marcada (guardada).

  7. clear() establece la posición en cero y establece el límite de la capacidad. Este método no borra los datos del búfer. Sólo reinicializa la posición, el límite y la marca.

  8. flip() cambia el búfer del modo de escritura al modo de lectura. También establece el límite de la posición actual y luego vuelve a poner la posición en cero.

  9. read() : el método de lectura del canal se usa para escribir datos del canal en el búfer, mientras que el método put() del búfer se usa para escribir datos en el búfer.

  10. write() — El método de escritura del canal se usa para escribir datos del búfer al canal, mientras que el método get() del búfer se usa para leer datos del búfer.

  11. rewind() rebobina el búfer. Este método se usa cuando necesita volver a leer el búfer: establece la posición en cero y no cambia el límite.

Y ahora unas palabras sobre los canales.

Las implementaciones de canales más importantes en Java NIO son las siguientes clases:

  1. FileChannel : un canal para leer y escribir datos desde/hacia un archivo.

  2. DatagramChannel : esta clase lee y escribe datos en la red a través de UDP (Protocolo de datagramas de usuario).

  3. SocketChannel : un canal para leer y escribir datos en la red a través de TCP (Protocolo de control de transmisión).

  4. ServerSocketChannel : un canal para leer y escribir datos a través de conexiones TCP, tal como lo hace un servidor web. Se crea un SocketChannel para cada conexión entrante.

Práctica

Es hora de escribir un par de líneas de código. Primero, leamos el archivo y mostremos su contenido en la consola, y luego escribamos una cadena en el archivo.

El código contiene muchos comentarios. Espero que te ayuden a comprender cómo funciona todo:

// Create a RandomAccessFile object, passing in the file path
// and a string that says the file will be opened for reading and writing
try (RandomAccessFile randomAccessFile = new RandomAccessFile("text.txt", "rw");
    // Get an instance of the FileChannel class
    FileChannel channel = randomAccessFile.getChannel();
) {
// Our file is small, so we'll read it in one go
// Create a buffer of the required size based on the size of our channel
   ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
   // Read data will be put into a StringBuilder
   StringBuilder builder = new StringBuilder();
   // Write data from the channel to the buffer
   channel.read(byteBuffer);
   // Switch the buffer from write mode to read mode
   byteBuffer.flip();
   // In a loop, write data from the buffer to the StringBuilder
   while (byteBuffer.hasRemaining()) {
       builder.append((char) byteBuffer.get());
   }
   // Display the contents of the StringBuilder on the console
   System.out.println(builder);

   // Now let's continue our program and write data from a string to the file
   // Create a string with arbitrary text
   String someText = "Hello, Amigo!!!!!";
   // Create a new buffer for writing,
   // but let the channel remain the same, because we're going to the same file
   // In other words, we can use one channel for both reading and writing to a file
   // Create a buffer specifically for our string — convert the string into an array and get its length
   ByteBuffer byteBuffer2 = ByteBuffer.allocate(someText.getBytes().length);
   // Write our string to the buffer
   byteBuffer2.put(someText.getBytes());
   // Switch the buffer from write mode to read mode
   // so that the channel can read from the buffer and write our string to the file
   byteBuffer2.flip();
   // The channel reads the information from the buffer and writes it to our file
   channel.write(byteBuffer2);
} catch (FileNotFoundException e) {
   e.printStackTrace();
} catch (IOException e) {
   e.printStackTrace();
}

Pruebe la API de NIO : ¡le encantará!