1. Введение
В этом руководстве мы рассмотрим функции Java IO и то, как они изменились в разных версиях Java. Сначала мы рассмотрим пакет java.io
из начальной версии Java. Далее мы рассмотрим пакет java.nio
, представленный в Java 1.4. В конце мы рассмотрим пакет java.nio.file
, широко известный как пакет NIO.2.
2. Пакет Java NIO
Первая версия Java была выпущена с пакетом java.io
, в котором появился класс File
для доступа к файловой системе. Класс File
представляет файлы и каталоги и обеспечивает ограниченные операции с файловой системой. Можно было создавать и удалять файлы, проверять их существование, проверять доступ на чтение/запись и т. д.
Есть у него и некоторые недостатки:
- Отсутствие метода копирования — чтобы скопировать файл, нам нужно создать два экземпляра
File
и использовать буфер для чтения из одного и записи в другой экземплярFile .
- Плохая обработка ошибок — некоторые методы возвращают
логическое значение
в качестве индикатора, если операция выполнена успешно или нет. - Доступен ограниченный набор атрибутов файла — имя, путь, права на чтение/запись, объем памяти и многие другие.
- Blocking API — наш поток блокируется до завершения операции ввода-вывода.
Чтобы прочитать файл, нам нужен экземпляр FileInputStream
для чтения байтов из файла:
@Test
public void readFromFileUsingFileIO() throws Exception {
File file = new File("src/test/resources/nio-vs-nio2.txt");
FileInputStream in = new FileInputStream(file);
StringBuilder content = new StringBuilder();
int data = in.read();
while (data != -1) {
content.append((char) data);
data = in.read();
}
in.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
Затем в Java 1.4 представлен неблокирующий API-интерфейс ввода-вывода, включенный в пакет java.nio
(nio означает новый ввод-вывод). NIO был введен для преодоления ограничений пакета java.io.
В этом пакете представлены три основных класса: Channel
, Buffer
и Selector
.
2.1. Канал
Java NIO Channel
— это класс, который позволяет нам читать и писать в буфер . Класс Channel
похож на Streams
(здесь мы говорим о IO Streams , а не о
потоках
Java 1.8 ) с парой отличий. Канал
— это улица с двусторонним движением, в то время как потоки
обычно односторонние, и они могут читать и записывать асинхронно.
Существует пара реализаций класса Channel
, в том числе FileChannel
для чтения/записи файловой системы, DatagramChannel
для чтения/записи по сети с использованием UDP и SocketChannel
для чтения/записи по сети с использованием TCP.
2.2. Буфер
Буфер — это блок памяти, из которого мы можем читать или записывать в него данные . Объект NIO Buffer
оборачивает блок памяти. Класс Buffer
предоставляет набор функций для работы с блоком памяти. Чтобы работать с объектами Buffer
, нам нужно понимать три основных свойства класса Buffer
: емкость, положение и предел.
- Емкость определяет размер блока памяти. Когда мы записываем данные в буфер, мы можем записать только ограниченную длину. Когда буфер заполнен, нам нужно прочитать данные или очистить их.
- Позиция — это отправная точка, где мы записываем наши данные. Пустой буфер начинается с 0 и достигает
емкости – 1
. Кроме того, когда мы читаем данные, мы начинаем со значения позиции. - Ограничение означает, как мы можем писать и читать из буфера.
Существует несколько вариантов класса Buffer
. По одному для каждого примитивного типа Java, за исключением логического
типа плюс MappedByteBuffer
.
Для работы с буфером нам нужно знать несколько важных методов:
allocate(int value) —
мы используем этот метод для создания буфера определенного размера.flip()
— этот метод используется для переключения из режима записи в режим чтения.clear() —
метод очистки содержимого буфераcompact() —
метод очистки только того контента, который мы уже прочиталиrewind () —
сбрасывает позицию обратно в 0, чтобы мы могли перечитать данные в буфере.
Используя ранее описанные концепции, давайте воспользуемся классами Channel
и Buffer
для чтения содержимого из файла:
@Test
public void readFromFileUsingFileChannel() throws Exception {
RandomAccessFile file = new RandomAccessFile("src/test/resources/nio-vs-nio2.txt", "r");
FileChannel channel = file.getChannel();
StringBuilder content = new StringBuilder();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
content.append((char) buffer.get());
}
buffer.clear();
bytesRead = channel.read(buffer);
}
file.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
После инициализации всех необходимых объектов читаем из канала в буфер. Далее, в цикле while мы помечаем буфер для чтения с помощью метода flip()
и читаем по одному байту за раз, и добавляем его к нашему результату. В конце мы очищаем данные и читаем другую партию.
2.3. Селектор
Java NIO Selector позволяет нам управлять несколькими каналами с помощью одного потока. Чтобы использовать объект селектора для мониторинга нескольких каналов, каждый экземпляр канала должен находиться в неблокирующем режиме, и мы должны его зарегистрировать. После регистрации канала мы получаем объект SelectionKey
, представляющий связь между каналом и селектором. Когда у нас есть несколько каналов, подключенных к селектору, мы можем использовать метод select()
, чтобы проверить, сколько каналов готовы к использованию. После вызова метода select()
мы можем использовать метод selectedKeys()
для получения всех готовых каналов.
2.4. Недостатки пакета NIO
Изменения, внесенные пакетом java.nio
, больше связаны с низкоуровневым вводом-выводом данных. Хотя они разрешили неблокирующий API, другие аспекты оставались проблематичными:
- Ограниченная поддержка символических ссылок
- Ограниченная поддержка доступа к атрибутам файла
- Отсутствуют лучшие инструменты управления файловой системой
3. Пакет Java NIO.2
В Java 1.7 представлен новый пакет java.nio.file
, также известный как пакет NIO.2 . Этот пакет следует асинхронному подходу к неблокирующему вводу-выводу, который не поддерживается в пакете java.nio
. Наиболее значительные изменения связаны с высокоуровневыми манипуляциями с файлами. Они добавляются с помощью классов Files, Path
и Paths .
Наиболее заметным низкоуровневым изменением является добавление AsynchroniousFileChannel
и AsyncroniousSocketChannel
.
Объект пути
представляет собой иерархическую последовательность имен каталогов и файлов, разделенных разделителем . Корневой компонент крайний слева, а файл справа. Этот класс предоставляет служебные методы, такие как getFileName()
, getParent()
и т. д. Класс Path
также предоставляет методы разрешения
и релятивизации
, которые помогают создавать пути между различными файлами. Класс Paths — это набор статических служебных методов, которые получают String
или URI
для создания экземпляров Path .
Класс Files
предоставляет служебные методы, которые используют ранее описанный класс Path
и работают с файлами, каталогами и символическими ссылками. Он также предоставляет способ чтения многих атрибутов файла с помощью метода readAttributes()
.
Наконец, давайте посмотрим, как NIO.2 сравнивается с предыдущими версиями IO, когда дело доходит до чтения файла:
@Test
public void readFromFileUsingNIO2() throws Exception {
List<String> strings = Files.readAllLines(Paths.get("src/test/resources/nio-vs-nio2.txt"));
assertThat(strings.get(0)).isEqualTo("Hello from file!");
}
4. Вывод
В этой статье мы рассмотрели основы пакетов java.nio
и java.nio.file .
Как мы видим, NIO.2 не является новой версией пакета NIO. Пакет NIO представил низкоуровневый API для неблокирующего ввода-вывода, а NIO.2 представил улучшенное управление файлами. Эти два пакета не являются синонимами, а скорее дополняют друг друга. Как всегда, все примеры кода можно найти на GitHub .