Guide to Java FileChannel – Java FileChannel指南

最后修改: 2019年 5月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick tutorial, we’ll look at the FileChannel class provided in the Java NIO library. We’ll discuss how to read and write data using FileChannel and ByteBuffer.

在这个快速教程中,我们将看看FileChannel类在Java NIO库中提供。我们将讨论如何使用FileChannelByteBuffer读取和写入数据。

We’ll also explore the advantages of using FileChannel and some of its other file manipulation features.

我们还将探讨使用FileChannel的优势以及它的一些其他文件操作功能。

2. Advantages of FileChannel

2.FileChannel的优势

The advantages of FileChannel include:

FileChannel的优势包括。

  • Reading and writing at a specific position in a file
  • Loading a section of a file directly into memory, which can be more efficient
  • We can transfer file data from one channel to another at a faster rate
  • We can lock a section of a file to restrict access by other threads
  • To avoid data loss, we can force writing updates to a file immediately to storage

3. Reading with FileChannel

3.用FileChannel阅读

FileChannel performs faster than standard I/O when we read a large file.

FileChannel在我们读取一个大文件时,表现得比标准I/O更快。

We should note that although part of Java NIO, FileChannel operations are blocking and do not have a non-blocking mode.

我们应该注意,尽管是Java NIO的一部分,FileChannel操作是阻塞的,没有非阻塞模式。

3.1. Reading a File Using FileChannel

3.1.使用FileChannel读取一个文件

Let’s understand how to read a file using FileChannel on a file that contains:

让我们了解如何使用FileChannel在一个包含的文件上读取一个文件。

Hello world

This test reads the file and checks it was read ok:

这个测试读取文件并检查它是否被读取。

@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);
    }
}

Here we read bytes from the file using FileChannel, RandomAccessFile, and ByteBuffer.

这里我们使用FileChannelRandomAccessFileByteBuffer从文件读取字节。

We should also note that multiple concurrent threads can use FileChannels safelyHowever, only one thread at a time is allowed an operation that involves updating a channel’s position or changing its file size. This blocks other threads attempting a similar operation until the previous operation completes.

我们还应该注意,多个并发线程可以安全地使用文件通道。然而,每次只有一个线程被允许进行涉及更新通道位置或改变其文件大小的操作。这将阻止其他试图进行类似操作的线程,直到前一个操作完成。

However, operations that provide explicit channel positions can run concurrently without being blocked.

然而,提供显式通道位置的操作可以同时运行而不被阻塞。

3.2. Opening a FileChannel

3.2.打开一个文件通道

In order to read a file using FileChannel, we must open it.

为了使用FileChannel读取一个文件,我们必须打开它。

Let’s see how to open a FileChannel using RandomAccessFile:

让我们看看如何使用RandomAccessFile打开一个FileChannel

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

Mode ‘r’ indicates that the channel is ‘open for reading’ only. We should note that closing a RandomAccessFile will also close the associated channel.

模式’r’表示该通道仅’开放供阅读’。我们应该注意,关闭一个RandomAccessFile也将关闭相关的通道。

Next, we’ll see opening a FileChannel to read a file using FileInputStream:

接下来,我们将看到打开一个FileChannel,使用FileInputStream读取一个文件。

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

Similarly, closing a FileInputStream also closes the channel associated with it.

同样地,关闭一个FileInputStream也会关闭与之相关的通道。

3.3. Reading Data from a FileChannel

3.3.从文件通道读取数据

To read the data, we can use one of the read methods.

为了读取数据,我们可以使用其中一个read 方法。

Let’s see how to read a sequence of bytes. We’ll use a ByteBuffer to hold the data:

让我们看看如何读取一串字节。我们将使用一个ByteBuffer来保存数据。

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

assertEquals("Hello world", fileContent);

Next, we’ll see how to read a sequence of bytes, starting at a file position:

接下来,我们将看到如何读取一个字节序列,从一个文件位置开始。

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

We should note the need for a Charset to decode a byte array into String.

我们应该注意到需要一个字符集来将一个字节数组解码成字符串

We specify the Charset with which the bytes were originally encoded. Without it, we may end up with garbled text. In particular, multi-byte encodings like UTF-8 and UTF-16 may not be able to decode an arbitrary section of the file, as some of the multi-byte characters may be incomplete.

我们指定字节最初被编码的字符集。没有它我们可能会得到乱码的文本。特别是,像UTF-8UTF-16这样的多字节编码可能无法解码文件的任意部分,因为一些多字节字符可能不完整。

4. Writing with FileChannel

4.用FileChannel写作

4.1. Writing into a File Using FileChannel

4.1.使用FileChannel向文件中写入内容

Let’s explore how to write using FileChannel:

让我们探讨一下如何使用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. Opening a FileChannel

4.2.打开一个文件通道

In order to write into a file using FileChannel, we must open it.

为了使用FileChannel写进一个文件,我们必须打开它。

Let’s see how to open a FileChannel using RandomAccessFile:

让我们看看如何使用RandomAccessFile打开一个FileChannel

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

Mode ‘rw’ indicates that the channel is ‘open for reading and writing’.

模式’rw’表示通道是’开放供读写’。

Let’s also see how to open a FileChannel using FileOutputStream:

我们也来看看如何使用FileOutputStream打开一个FileChannel

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

4.3. Writing Data with FileChannel

4.3.用FileChannel写数据

To write data with a FileChannel, we can use one of the write methods.

要用FileChannel写入数据,我们可以使用其中一个write方法。

Let’s see how to write a sequence of bytes, using a ByteBuffer to store the data:

让我们看看如何写一个字节序列,使用ByteBuffer来存储数据。

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

Next, we’ll see how to write a sequence of bytes, starting at a file position:

接下来,我们将看到如何写一个字节序列,从一个文件位置开始。

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

5. Current Position

5.当前位置

FileChannel allows us to get and change the position at which we are reading or writing.

FileChannel允许我们获得和改变我们正在读取或写入的位置。

Let’s see how to get the current position:

让我们看看如何获得当前的位置。

long originalPosition = channel.position();

Next, let’s see how to set the position:

接下来,让我们看看如何设置位置。

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

6. Get the Size of a File

6.获取文件的大小

Let’s see how to use the FileChannel.size method to get the size of a file in bytes:

让我们看看如何使用FileChannel.size方法来获取一个文件的字节数大小。

@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. Truncate a File

7.截断一个文件

Let’s understand how to use the FileChannel.truncate method to truncate a file to given size in bytes:

让我们了解如何使用FileChannel.truncate方法来将一个文件截断到给定的字节大小。

@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. Force File Update into Storage

8.强制将文件更新到存储器中

An operating system may cache file changes for performance reasons, and data may be lost if the system crashes. To force file content and metadata to write to disk continuously we can use the force method:

操作系统可能会因为性能原因而缓存文件变化,如果系统崩溃,数据可能会丢失。为了强迫文件内容和元数据连续写入磁盘,我们可以使用force方法。

channel.force(true);

This method is guaranteed only when the file resides on a local device.

这种方法只有在文件驻留在本地设备上时才能保证。

9. Load a Section of a File into Memory

9.将文件的一个部分加载到内存中

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:

让我们看看如何使用FileChannel.map在内存中加载一个文件的某一部分。我们使用FileChannel.MapMode.READ_ONLY来以只读模式打开该文件。

@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.

同样地,我们可以使用FileChannel.MapMode.READ_WRITE将文件打开为读和写两种模式。

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

我们也可以使用 FileChannel.MapMode.PRIVATE 模式,在这种模式下,改变并不适用于原始文件.

10. Lock a Section of a File

10.锁定一个文件的某一部分

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

让我们了解如何使用FileChannel.tryLock方法来锁定一个文件的某个部分,以防止某个部分被并发访问。

@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.

tryLock方法试图获取文件部分的锁。如果请求的文件段已经被另一个线程封锁,它会抛出一个OverlappingFileLockException异常。这个方法还需要一个boolean参数来请求共享锁或独占锁。

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

我们应该注意,有些操作系统可能不允许共享锁,而默认为独占锁。

11. Closing a FileChannel

11.关闭一个文件通道

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

最后,当我们使用完一个文件通道,我们必须关闭它。在我们的例子中,我们使用了try-with-resources

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

如果有必要,我们可以用close方法直接关闭FileChannel

channel.close();

12. Conclusion

12.结语

In this tutorial, we’ve seen how to use FileChannel to read and write files. In addition, we’ve explored how to read and change the file size and its current read/write location and looked at how to use FileChannels in concurrent or data critical applications.

在本教程中,我们已经看到如何使用FileChannel来读写文件。此外,我们还探讨了如何读取和改变文件大小及其当前的读写位置,并研究了如何在并发或数据关键性应用中使用FileChannels

As always, the source code for the examples is available over on GitHub.

像往常一样,这些例子的源代码可以在GitHub上找到