FileChannel

Модуль 1. Java Syntax
26 уровень , 2 лекция
Открыта

Раннее мы с вами познакомились c IO API (Input & Output Application Programming Interface) и пакетом java.io, в классах которого сосредоточен основной функционал работы с потоками в Java. Ключевым здесь является понятие потока (stream).

Сегодня же мы начнем рассматривать NIO API (New Input & Output).

Основное отличие между двумя подходами к организации ввода/вывода заключается в том, что IO API — потоко-ориентированное, а NIO API — буферо-ориентированное. Главные понятия в этом случае — понятия буфера (buffer) и канала (channel).

Что такое буфер и канал?

Канал — это логический портал, через которые осуществляется ввод/вывод данных, а буфер является источником или приёмником этих переданных данных. При организации вывода данные, которые вы хотите отправить, помещаются в буфер, а он передает их в канал. При вводе, данные из канала помещаются в буфер.

Иными словами:

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

Каналы очень похожи на потоки в пакете java.io. Все данные, которые идут куда угодно (или приходят откуда угодно), должны проходить через объект канала. В общем, чтобы использовать систему NIO, вы получаете канал к устройству ввода/вывода и буфер для хранения данных. Затем вы работаете с буфером, вводя или выводя данные по мере необходимости.

Вы можете двигаться по буферу вперед и назад, то есть, “гулять” по нему, чего не могли делать в потоках. Это дает больше гибкости при обработке данных. В стандартной библиотеке буфер представлен абстрактным классом Buffer и множеством его наследников:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • FloatBuffer
  • DoubleBuffer
  • LongBuffer

Основное отличие наследников — тип данных, который они будут хранить — byte, int, long и другие примитивные типы данных.

Свойства буфера

У буфера есть четыре основных свойства. Это емкость, лимит, позиция и маркер.

Емкость (Capacity) — максимальный объем данных/байт, который может быть сохранен в буфер. Емкость буфера не может быть изменена. Как только буфер заполнен, его следует очистить перед записью в него.

Лимит (Limit) — в режиме записи буфера Лимит равен емкости, что показывает максимальное количество данных, которые могут быть записаны в буфер. В режиме чтения буфера Лимит означает максимальное количество данных, которые можно прочитать из буфера.

Позиция (Position) — указывает на текущую позицию курсора в буфере. Первоначально устанавливается на 0 в момент создания буфера. Иными словами, это индекс элемента, который должен быть прочитан или записан.

Маркер (Mark) — используется для маркировки текущей позиции курсора. В процессе манипуляций с буфером позиция курсора постоянно изменяется, но мы всегда можем вернуть его в маркированную раннее позицию.

Методы для работы с буфером

Теперь давайте рассмотрим основной набор методов, которые позволяют работать с нашим буфером (блоком памяти) для чтения и записи данных в каналы и из каналов.

  1. allocate (int capacity) — метод используется для выделения нового буфера с емкостью в качестве параметра. Метод allocate() выдает исключение IllegalArgumentException в случае, если переданная емкость является отрицательным целым числом.

  2. capacity() — возвращает емкость (capacity) текущего буфера.

  3. position() — возвращает текущую позицию курсора. Как операции чтения, так и записи перемещают курсор в конец буфера. Возвращаемое значение всегда меньше или равно limit.

  4. limit() — возвращает лимит текущего буфера.

  5. mark() — используется для обозначения (маркировки) текущей позиции курсора.

  6. reset() — вернет курсор в ранее отмеченную (маркированную) позицию.

  7. clear() — устанавливает позицию в ноль и ограничивает ее до емкости. В этом методе данные в буфере не очищаются, только маркеры инициализируются повторно.

  8. flip() — переключает режим буфера с режима записи на режим чтения. Он также устанавливает позицию обратно в ноль и устанавливает лимит, в котором позиция была во время записи.

  9. read() — метод чтения канала используется для записи данных из канала в буфер, а put() — метод буфера, который используется для записи данных в буфер.

  10. write() — метод записи канала используется для записи данных из буфера в канал, в то время как get() является методом буфера, который используется для чтения данных из буфера.

  11. rewind() — метод перемотки. Используется, когда требуется перечитывание, так как он устанавливает позицию в ноль и не изменяет значение лимита.

А теперь — немного о канале.

Наиболее важными реализациями канала в Java NIO выступают следующие классы:

  1. FileChannel — канал для чтения и записи данных в файл.

  2. DatagramChannel — считывает и записывает данные по сети через UDP (User Datagram Protocol).

  3. SocketChannel — канал для считывания и записи данныx по сети через TCP (Transmission Control Protocol).

  4. ServerSocketChannel — канал для чтения и записи данных через TCP-соединения, так же, как это делает веб-сервер. Для каждого входящего соединения создается SocketChannel.

Практика

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

В коде содержится много комментариев, надеюсь, они помогут вам понять как же все работает:


// инициализируем класс RandomAccessFile, в параметры передаем путь к файлу
// и модификатор, который говорит, что файл откроется для чтения и записи
try (RandomAccessFile randomAccessFile = new RandomAccessFile("text.txt", "rw");
    // получаем экземпляр класса FileChannel
    FileChannel channel = randomAccessFile.getChannel();
) {
// наш файл имеет небольшой размер, поэтому считывать мы его будем за один раз   
// создаем буфер необходимого размера на основании размера нашего канала
   ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
   // прочитанные данные будем помещать в StringBuilder
   StringBuilder builder = new StringBuilder();
   // записываем данные из канала в буфер
   channel.read(byteBuffer);
   // переключаем буфер с режима записи на режим чтения
   byteBuffer.flip();
   // в цикле записываем данные из буфера в StringBuilder
   while (byteBuffer.hasRemaining()) {
       builder.append((char) byteBuffer.get());
   }
   // выводим содержимое StringBuilder в консоли
   System.out.println(builder);
 
   // теперь продолжим нашу программу и запишем данные из строки в файл
   // создаем строку с произвольным текстом
   String someText = "Hello, Amigo!!!!!";
   // создаем для работы с записью новый буфер,
   // а канал пусть остается прежним, т.к. мы будем писать в тот же файл
   // т.е., один канал мы можем использовать как для чтения, так и для записи в файл
   // создаем буфер конкретно под нашу строку — строку переводим в массив и берем его длину
   ByteBuffer byteBuffer2 = ByteBuffer.allocate(someText.getBytes().length);
   // записываем нашу строку в буфер
   byteBuffer2.put(someText.getBytes());
   // переключаем буфер с режима записи на режим чтения,
   // чтобы канал смог прочитать из буфера и записать нашу строку в файл
   byteBuffer2.flip();
   // канал читает информацию из буфера и записывает ее в наш файл
   channel.write(byteBuffer2);
} catch (FileNotFoundException e) {
   e.printStackTrace();
} catch (IOException e) {
   e.printStackTrace();
}

Попробуйте NIO API и он вам понравится!

Комментарии (19)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Владимир Кругман Уровень 51 Expert
26 августа 2023
Откуда вдруг в примерах взялся RandomAccessFile, о котором в теории ни слова? В целом пока неразбериха с NIO - это Path + Files или Channel + Buffer? Собрать паззл пока не получается, теория по NIO по вершкам. Устаревший File с потоками более подробно изложен с примерами и в теории, чем NIO
Екатерина Уровень 111
22 мая 2023
Пожалуйста, разложите информацию более подробно "по полочкам", после прочтения лекции не очень понятно как все-таки решить задачу №2.. Если конкретно - рассмотрите каждый метод класса ByteBuffer, на примерах..
Олег Уровень 79 Expert
26 апреля 2023
Всем привет! вот эта статья мне помогла с 1 задачей... https://translated.turbopages.org/proxy_u/en-ru.ru.3d403bb2-6449285b-804ab802-74722d776562/https/www.baeldung.com/java-filechannel
Igor Stupnik Уровень 33
28 марта 2023
Совет по-братцки для последней задачи, не тестируйте с большим файлом 😿
Олег Уровень 111 Expert
23 февраля 2023
"инициализируем класс RandomAccessFile" - помню, что есть порядок инициализации полей и конструкторов класса, а вот выражение "инициализируем класс" не на слуху. Погуглил - тишина, может кто знает?
19 января 2023
clear() — The position is set to zero, the limit is set to the capacity, and the mark is discarded. flip() - Flips this buffer. The limit is set to the current position and then the position is set to zero. If the mark is defined then it is discarded. Желающим осмыслить методы на практике:

public class Test {
    public static void main(String[] args) {
        ByteBuffer b = ByteBuffer.allocate(16);
        print_b(b);

        b.put((byte) 65); // пишем
        b.put((byte) 60); // пишем
        b.put((byte) 12); // пишем
        print_b(b);

        b.flip();
        b.put((byte)7); // пишем // после flip !
        print_b(b);
        System.out.println(b.get());    // читаем после flip !
        print_b(b);

        b.flip();
        print_b(b);

        b.clear();  // возвращаем limit к "родному" capacity
        print_b(b);
    }
    static void print_b(ByteBuffer b) {
        System.out.println("position=" + b.position() + " ,limit=" + b.limit() + " ,capacity=" + b.capacity());
    }
}
вывод в консоль:

position=0 ,limit=16 ,capacity=16
position=3 ,limit=16 ,capacity=16
position=1 ,limit=3 ,capacity=16
60
position=2 ,limit=3 ,capacity=16
position=0 ,limit=2 ,capacity=16
position=0 ,limit=16 ,capacity=16
Нюанс: прочитать байты свыше limit - нельзя, даже если capacity больше.
Mykola Уровень 28 Expert
11 октября 2022
Нашёл в лекции опечатку: "Сегодня же мы начем рассматривать NIO API (New Input & Output)..."
Владимир Уровень 1 Expert
25 августа 2022
кто мало что понял советую к прочтению https://www.tune-it.ru/web/ivanuskov/blog/-/blogs/java-nio. Там пошагово разбирается работа буфера в картинках.
Андрей Уровень 101 Expert
4 октября 2022
Спасибо!
Andrei Уровень 34
17 мая 2022
Задачи понравились, практические, но в уроках нет достаточно информации чтобы их решать самостоятельно.
Марат Гарипов Уровень 108 Expert
16 мая 2022
во время решения второй задачи, из-за неправильного условия цикла, создал несколько десятков тысяч файлов, замучился удалять)