Guía de Java FileChannel

1. Información general

En este tutorial rápido, veremos la clase FileChannel proporcionada en la biblioteca Java NIO . Discutiremos cómo leer y escribir datos usando FileChannel y ByteBuffer .

También exploraremos las ventajas de usar FileChannel y algunas de sus otras funciones de manipulación de archivos.

2. Ventajas de FileChannel

Las ventajas de FileChannel incluyen:

  • Leer y escribir en una posición específica en un archivo
  • Cargar una sección de un archivo directamente en la memoria, lo que puede ser más eficiente
  • Podemos transferir datos de archivos de un canal a otro a un ritmo más rápido
  • Podemos bloquear una sección de un archivo para restringir el acceso de otros hilos
  • Para evitar la pérdida de datos, podemos forzar la escritura de actualizaciones en un archivo inmediatamente en el almacenamiento.

3. Lectura con FileChannel

FileChannel funciona más rápido que la E / S estándar cuando leemos un archivo grande.

Debemos tener en cuenta que, aunque son parte de Java NIO , las operaciones de FileChannel se bloquean y no tienen un modo sin bloqueo.

3.1. Leer un archivo con FileChannel

Entendamos cómo leer un archivo usando FileChannel en un archivo que contiene:

Hello world

Esta prueba lee el archivo y verifica que se haya leído correctamente:

@Test public void givenFile_whenReadWithFileChannelUsingRandomAccessFile_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { int bufferSize = 1024; if (bufferSize > channel.size()) { bufferSize = (int) channel.size(); } ByteBuffer buff = ByteBuffer.allocate(bufferSize); while (channel.read(buff) > 0) { out.write(buff.array(), 0, buff.position()); buff.clear(); } String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent); } }

Aquí leemos bytes del archivo usando FileChannel , RandomAccessFile y ByteBuffer.

También debemos tener en cuenta que varios subprocesos simultáneos pueden utilizar FileChannels de forma segura . Sin embargo, solo un hilo a la vez se permite una operación que implica actualizar la posición de un canal o cambiar el tamaño del archivo. Esto bloquea otros subprocesos que intentan una operación similar hasta que se completa la operación anterior.

Sin embargo, las operaciones que proporcionan posiciones de canal explícitas pueden ejecutarse simultáneamente sin ser bloqueadas.

3.2. Abrir un canal de archivos

Para leer un archivo usando FileChannel , debemos abrirlo.

Veamos cómo abrir un FileChannel usando RandomAccessFile :

RandomAccessFile reader = new RandomAccessFile(file, "r"); FileChannel channel = reader.getChannel();

El modo 'r' indica que el canal está 'abierto para lectura' únicamente. Debemos tener en cuenta que cerrar un RandomAccessFile también cerrará el canal asociado.

A continuación, veremos abrir un FileChannel para leer un archivo usando FileInputStream :

FileInputStream fin= new FileInputStream(file); FileChannel channel = fin.getChannel();

De manera similar, cerrar un FileInputStream también cierra el canal asociado a él.

3.3. Leer datos de un canal de archivos

Para leer los datos, podemos usar uno de los métodos de lectura .

Veamos cómo leer una secuencia de bytes. Usaremos un ByteBuffer para almacenar los datos:

ByteBuffer buff = ByteBuffer.allocate(1024); int noOfBytesRead = channel.read(buff); String fileContent = new String(buff.array(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent);

A continuación, veremos cómo leer una secuencia de bytes, comenzando en una posición de archivo:

ByteBuffer buff = ByteBuffer.allocate(1024); int noOfBytesRead = channel.read(buff, 5); String fileContent = new String(buff.array(), StandardCharsets.UTF_8); assertEquals("world", fileContent);

Debemos tener en cuenta la necesidad de un juego de caracteres para decodificar una matriz de bytes en una cadena .

Especificamos el conjunto de caracteres con el que se codificaron originalmente los bytes. Sin él , podemos terminar con texto confuso. En particular, es posible que las codificaciones de varios bytes como UTF-8 y UTF-16 no puedan decodificar una sección arbitraria del archivo, ya que algunos de los caracteres de varios bytes pueden estar incompletos.

4. Escribir con FileChannel

4.1. Escribir en un archivo usando FileChannel

Exploremos cómo escribir usando FileChannel :

@Test public void whenWriteWithFileChannelUsingRandomAccessFile_thenCorrect() throws IOException { String file = "src/test/resources/test_write_using_filechannel.txt"; try (RandomAccessFile writer = new RandomAccessFile(file, "rw"); FileChannel channel = writer.getChannel()){ ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff); // verify RandomAccessFile reader = new RandomAccessFile(file, "r"); assertEquals("Hello world", reader.readLine()); reader.close(); } }

4.2. Abrir un canal de archivos

Para escribir en un archivo usando FileChannel , debemos abrirlo.

Veamos cómo abrir un FileChannel usando RandomAccessFile :

RandomAccessFile writer = new RandomAccessFile(file, "rw"); FileChannel channel = writer.getChannel();

El modo 'rw' indica que el canal está 'abierto para lectura y escritura'.

Veamos también cómo abrir un FileChannel usando FileOutputStream :

FileOutputStream fout = new FileOutputStream(file); FileChannel channel = fout.getChannel(); 

4.3. Escribir datos con FileChannel

Para escribir datos con un FileChannel , podemos usar uno de los métodos de escritura .

Veamos cómo escribir una secuencia de bytes, usando un ByteBuffer para almacenar los datos:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff); 

A continuación, veremos cómo escribir una secuencia de bytes, comenzando en una posición de archivo:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff, 5); 

5. Posición actual

FileChannel nos permite obtener y cambiar la posición en la que estamos leyendo o escribiendo.

Veamos cómo obtener la posición actual:

long originalPosition = channel.position();

A continuación, veamos cómo establecer la posición:

channel.position(5); assertEquals(originalPosition + 5, channel.position());

6. Obtenga el tamaño de un archivo

Veamos cómo usar el método FileChannel.size para obtener el tamaño de un archivo en bytes:

@Test public void whenGetFileSize_thenCorrect() throws IOException { RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); // the original file size is 11 bytes. assertEquals(11, channel.size()); channel.close(); reader.close(); }

7. Truncar un archivo

Entendamos cómo usar el método FileChannel.truncate para truncar un archivo al tamaño dado en bytes:

@Test public void whenTruncateFile_thenCorrect() throws IOException { String input = "this is a test input"; FileOutputStream fout = new FileOutputStream("src/test/resources/test_truncate.txt"); FileChannel channel = fout.getChannel(); ByteBuffer buff = ByteBuffer.wrap(input.getBytes()); channel.write(buff); buff.flip(); channel = channel.truncate(5); assertEquals(5, channel.size()); fout.close(); channel.close(); } 

8. Forzar la actualización del archivo al almacenamiento

Un sistema operativo puede almacenar en caché los cambios en los archivos por razones de rendimiento, y los datos se pueden perder si el sistema falla. Para forzar que el contenido del archivo y los metadatos se escriban en el disco de forma continua, podemos usar el método force :

channel.force(true);

Este método está garantizado solo cuando el archivo reside en un dispositivo local.

9. Cargue una sección de un archivo en la memoria

Let's see how to load a section of a file in memory using FileChannel.map. We use FileChannel.MapMode.READ_ONLY to open the file in read-only mode:

@Test public void givenFile_whenReadAFileSectionIntoMemoryWithFileChannel_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 6, 5); if(buff.hasRemaining()) { byte[] data = new byte[buff.remaining()]; buff.get(data); assertEquals("world", new String(data, StandardCharsets.UTF_8)); } } }

Similarly, we can use FileChannel.MapMode.READ_WRITE to open the file into both read and write mode.

We can also useFileChannel.MapMode.PRIVATE mode, where changes do not apply to the original file.

10. Lock a Section of a File

Let's understand how to lock a section of a file to prevent concurrent access of a section using the FileChannel.tryLock method:

@Test public void givenFile_whenWriteAFileUsingLockAFileSectionWithFileChannel_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "rw"); FileChannel channel = reader.getChannel(); FileLock fileLock = channel.tryLock(6, 5, Boolean.FALSE )){ //do other operations... assertNotNull(fileLock); } }

The tryLock method attempts to acquire a lock on the file section. If the requested file section is already blocked by another thread, it throws an OverlappingFileLockException exception. This method also takes a boolean parameter to request either a shared lock or an exclusive lock.

We should note that some operating systems may not allow a shared lock, defaulting instead to an exclusive lock.

11. Closing a FileChannel

Finally, when we are done using a FileChannel, we must close it. In our examples we have used try-with-resources.

If necessary, we can close the FileChannel directly with the close method:

channel.close();

12. Conclusion

En este tutorial, hemos visto cómo usar FileChannel para leer y escribir archivos . Además, hemos explorado cómo leer y cambiar el tamaño del archivo y su ubicación actual de lectura / escritura y analizamos cómo usar FileChannels en aplicaciones concurrentes o críticas de datos.

Como siempre, el código fuente de los ejemplos está disponible en GitHub.