Рубрики
Без рубрики

Руководство По Асинхронному Файловому Каналу NIO2

Краткое и практическое руководство по Java NIO2 Asynchronous FileChannel API

Автор оригинала: baeldung.

1. Обзор

В этой статье мы рассмотрим один из ключевых дополнительных API нового ввода-вывода (NIO2) в Java 7, API асинхронного файлового канала.

Если вы новичок в API асинхронных каналов в целом, у нас есть вступительная статья на этом сайте, которую вы можете прочитать, перейдя по этой ссылке, прежде чем продолжить.

Вы также можете прочитать больше об операциях с файлами NIO.2 и операциях с путями – понимание этого значительно облегчит выполнение этой статьи.

Чтобы использовать асинхронные файловые каналы NIO2 в наших проектах, мы должны импортировать пакет java.nio.channels , поскольку он объединяет все необходимые классы:

import java.nio.channels.*;

2. Асинхронный канал

В этом разделе мы рассмотрим, как использовать основной класс, который позволяет нам выполнять асинхронные операции с файлами, класс AsynchronousFileChannel . Чтобы создать его экземпляр, мы вызываем метод static open :

Path filePath = Paths.get("/path/to/file");

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
  filePath, READ, WRITE, CREATE, DELETE_ON_CLOSE);

Все значения перечисления поступают из опции Sta ndard Open .

Первым параметром открытого API является объект Path , представляющий расположение файла. Чтобы узнать больше об операциях path в NIO2, перейдите по этой ссылке . Другие параметры составляют набор, определяющий параметры, которые должны быть доступны для возвращаемого канала файлов.

Созданный нами асинхронный файловый канал можно использовать для выполнения всех известных операций с файлом. Чтобы выполнить только подмножество операций, мы бы указали параметры только для них. Например, только для чтения:

Path filePath = Paths.get("/path/to/file");

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
  filePath, StandardOpenOption.READ);

3. Чтение из файла

Как и во всех асинхронных операциях в NIO2, чтение содержимого файла может быть выполнено двумя способами. Использование Future и использование completionHandler . В каждом случае мы используем read API возвращаемого канала.

В папке тестовых ресурсов maven или в исходном каталоге, если вы не используете maven, давайте создадим файл с именем file.txt только с текстом baeldung.com в самом начале. Теперь мы продемонстрируем, как читать этот контент.

3.1. Будущий Подход

Во-первых, мы увидим, как асинхронно читать файл с помощью класса Future :

@Test
public void givenFilePath_whenReadsContentWithFuture_thenCorrect() {
    Path path = Paths.get(
      URI.create(
        this.getClass().getResource("/file.txt").toString()));
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      path, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    Future operation = fileChannel.read(buffer, 0);

    // run other code as operation continues in background
    operation.get();
      
    String fileContent = new String(buffer.array()).trim();
    buffer.clear();

    assertEquals(fileContent, "baeldung.com");
}

В приведенном выше коде после создания канала файлов мы используем API read , который использует ByteBuffer для хранения содержимого, считанного с канала, в качестве первого параметра.

Второй параметр-длинный, указывающий позицию в файле, с которой следует начать чтение.

Метод сразу же возвращает, был ли файл прочитан или нет.

Затем мы можем выполнить любой другой код, поскольку операция продолжается в фоновом режиме. Когда мы закончим с выполнением другого кода, мы можем вызвать get() API, который сразу же возвращает, если операция уже завершена, как мы выполняли другой код, или он блокируется до завершения операции.

Наше утверждение действительно доказывает, что содержимое файла было прочитано.

Если бы мы изменили параметр position в вызове read API с нуля на что-то другое, мы бы тоже увидели эффект. Например, седьмой символ в строке baeldung.com is g . Таким образом, изменение параметра position на 7 приведет к тому, что буфер будет содержать строку g.com .

3.2. Подход completionHandler

Далее мы увидим, как прочитать содержимое файла с помощью экземпляра completionHandler :

@Test
public void 
  givenPath_whenReadsContentWithCompletionHandler_thenCorrect() {
    
    Path path = Paths.get(
      URI.create( this.getClass().getResource("/file.txt").toString()));
    AsynchronousFileChannel fileChannel 
      = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    fileChannel.read(
      buffer, 0, buffer, new CompletionHandler() {

        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // result is number of bytes read
            // attachment is the buffer containing content
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

В приведенном выше коде мы используем второй вариант API read . Он по-прежнему принимает ByteBuffer и начальную позицию операции read в качестве первого и второго параметров соответственно. Третьим параметром является экземпляр completionHandler .

Первый универсальный тип обработчика завершения-это возвращаемый тип операции, в данном случае целое число, представляющее количество прочитанных байтов.

Второй-это тип вложения. Мы решили прикрепить буфер таким образом, чтобы, когда read завершится, мы могли использовать содержимое файла внутри completed callback API.

С семантической точки зрения, это не совсем корректный модульный тест, поскольку мы не можем выполнить утверждение внутри метода completed callback. Тем не менее, мы делаем это ради согласованности и потому, что хотим, чтобы наш код был максимально copy-paste-run – .

4. Запись в файл

Java NIO2 также позволяет нам выполнять операции записи в файл. Как и в случае с другими операциями, мы можем записывать в файл двумя способами. Использование Future и использование completionHandler . В каждом случае мы используем write API возвращаемого канала.

Создание AsynchronousFileChannel для записи в файл можно сделать следующим образом:

AsynchronousFileChannel fileChannel
  = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

4.1. Особые соображения

Обратите внимание на опцию, переданную в open API. Мы также можем добавить еще один параметр StandardOpenOption.СОЗДАЙТЕ , если мы хотим, чтобы файл, представленный путем , был создан, если он еще не существует. Другим распространенным вариантом является StandardOpenOption.ДОБАВИТЬ , который не перезаписывает существующее содержимое в файле.

Мы будем использовать следующую строку для создания нашего канала файлов в тестовых целях:

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
  path, WRITE, CREATE, DELETE_ON_CLOSE);

Таким образом, мы предоставим любой произвольный путь и будем уверены, что файл будет создан. После завершения теста созданный файл будет удален. Чтобы гарантировать, что созданные файлы не будут удалены после завершения теста, вы можете удалить последнюю опцию.

Чтобы запустить утверждения, нам нужно будет прочитать содержимое файла, где это возможно, после записи в них. Давайте скроем логику чтения в отдельном методе, чтобы избежать избыточности:

public static String readContent(Path file) {
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      file, StandardOpenOption.READ);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    Future operation = fileChannel.read(buffer, 0);

    // run other code as operation continues in background
    operation.get();     

    String fileContent = new String(buffer.array()).trim();
    buffer.clear();
    return fileContent;
}

4.2. Будущий Подход

Для асинхронной записи в файл с помощью класса Future :

@Test
public void 
  givenPathAndContent_whenWritesToFileWithFuture_thenCorrect() {
    
    String fileName = UUID.randomUUID().toString();
    Path path = Paths.get(fileName);
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      path, WRITE, CREATE, DELETE_ON_CLOSE);

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    buffer.put("hello world".getBytes());
    buffer.flip();

    Future operation = fileChannel.write(buffer, 0);
    buffer.clear();

    //run other code as operation continues in background
    operation.get();

    String content = readContent(path);
    assertEquals("hello world", content);
}

Давайте проверим, что происходит в приведенном выше коде. Мы создаем случайное имя файла и используем его для получения объекта Path . Мы используем этот путь, чтобы открыть асинхронный файловый канал с ранее упомянутыми параметрами.

Затем мы помещаем содержимое, которое хотим записать в файл, в буфер и выполняем запись . Мы используем наш вспомогательный метод, чтобы прочитать содержимое файла и действительно подтвердить, что это то, что мы ожидаем.

4.3. Подход completionHandler

Мы также можем использовать обработчик завершения, чтобы нам не приходилось ждать завершения операции в цикле while:

@Test
public void 
  givenPathAndContent_whenWritesToFileWithHandler_thenCorrect() {
    
    String fileName = UUID.randomUUID().toString();
    Path path = Paths.get(fileName);
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
      path, WRITE, CREATE, DELETE_ON_CLOSE);

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("hello world".getBytes());
    buffer.flip();

    fileChannel.write(
      buffer, 0, buffer, new CompletionHandler() {

        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // result is number of bytes written
            // attachment is the buffer
        }
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {

        }
    });
}

Когда мы вызываем API записи на этот раз, единственной новой вещью является третий параметр, в котором мы передаем анонимный внутренний класс типа completionHandler .

Когда операция завершается, класс вызывает завершенный метод, в котором мы можем определить, что должно произойти.

5. Заключение

В этой статье мы рассмотрели некоторые из наиболее важных функций API-интерфейсов AsynchronousFileChannel Java NIO2.

Чтобы получить все фрагменты кода и полный исходный код для этой статьи, вы можете посетить проект Github .