Что такое java nio
Что такое java nio
The Java NIO (New Input/Output) API defines buffers, which are containers for data, and other structures, such as charsets, channels, and selectable channels. Charsets are mappings between bytes and Unicode characters. Channels represent connections to entities capable of performing I/O operations. Selectable channels are those that can be multiplexed, which means that they can process multiple I/O operations in one channel.
The following code examples demonstrate the Java NIO API:
They are containers for a fixed amount of data of a specific primitive type. See the java.nio package and Table 9-1.
Table 9-1 Buffer Classes
Buffer Class | Description |
---|---|
Buffer | Base class for buffer classes. |
ByteBuffer | Buffer for bytes. |
MappedByteBuffer | Buffer for bytes that is mapped to a file. |
CharBuffer | Buffer for the char data type. |
DoubleBuffer | Buffer for the double data type. |
FloatBuffer | Buffer for the float data type. |
IntBuffer | Buffer for the int data type. |
LongBuffer | Buffer for the long data type. |
ShortBuffer | Buffer for the short data type. |
They are named mappings between sequences of 16-bit Unicode characters and sequences of bytes. Support for charsets include decoders and encoders, which translate between bytes and Unicode characters. See the java.nio.charset package and Table 9-2.
Table 9-2 Charset Classes
They represent an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing. See the java.nio.channels package and Table 9-3.
Table 9-3 Channel Interfaces and Classes
Multiplexing is the ability to process multiple I/O operations in one channel. A selectable channel can be put into blocking or non-blocking mode. In blocking mode, every I/O operation invoked upon the channel will block until it completes. In non-blocking mode, an I/O operation will never block and may transfer fewer bytes than were requested or possibly no bytes at all.
A selectable channel that can send and receive UDP (User Datagram Protocol) packets.
You can create datagram channels with different protocol families:
A selectable channel for stream-oriented listening sockets.
Like datagram channels, you can create server socket channels that are for Internet Protocol sockets or Unix Domain sockets.
A selectable channel for stream-oriented connecting sockets.
Like datagram channels, you can create socket channels that are for Internet Protocol sockets or Unix Domain sockets.
Основные отличия Java IO и Java NIO
Когда я начал изучать стандартный ввод/вывод в Java, то первое время был немного шокирован обилием интерфейсов и классов пакета java.io.*, дополненных еще и целым перечнем специфических исключений.
Потратив изрядное количество часов на изучение и реализацию кучи разнообразных туториалов из Интернета, начал чувствовать себя уверенно и вздохнул с облегчением. Но в один прекрасный момент понял, что для меня все только начинается, так как существует еще и пакет java.nio.*, известный ещё под названием Java NIO или Java New IO. Вначале казалось, что это тоже самое, ну типа вид сбоку. Однако, как оказалось, есть существенные отличия, как в принципе работы, так и в решаемых с их помощью задачах.
Разобраться во всем этом мне здорово помогла статья Джакоба Дженкова (Jakob Jenkov) – “Java NIO vs. IO”. Ниже она приводиться в адаптированном виде.
Поспешу заметить, что статья не является руководством по использованию Java IO и Java NIO. Её цель – дать людям, начинающим изучать Java, возможность понять концептуальные отличия между двумя указанными инструментами организации ввода/вывода.
Основные отличия между Java IO и Java NIO
IO | NIO |
---|---|
Потокоориентированный | Буфер-ориентированный |
Блокирующий (синхронный) ввод/вывод | Неблокирующий (асинхронный) ввод/вывод |
Селекторы |
Потокоориентированный и буфер-ориентированный ввод/вывод
Основное отличие между двумя подходами к организации ввода/вывода в том, что Java IO является потокоориентированным, а Java NIO – буфер-ориентированным. Разберем подробней.
Потокоориентированный ввод/вывод подразумевает чтение/запись из потока/в поток одного или нескольких байт в единицу времени поочередно. Данная информация нигде не кэшируются. Таким образом, невозможно произвольно двигаться по потоку данных вперед или назад. Если вы хотите произвести подобные манипуляции, вам придется сначала кэшировать данные в буфере.
Подход, на котором основан Java NIO немного отличается. Данные считываются в буфер для последующей обработки. Вы можете двигаться по буферу вперед и назад. Это дает немного больше гибкости при обработке данных. В то же время, вам необходимо проверять содержит ли буфер необходимый для корректной обработки объем данных. Также необходимо следить, чтобы при чтении данных в буфер вы не уничтожили ещё не обработанные данные, находящиеся в буфере.
Блокирующий и неблокирующий ввод/вывод
Потоки ввода/вывода (streams) в Java IO являются блокирующими. Это значит, что когда в потоке выполнения (tread) вызывается read() или write() метод любого класса из пакета java.io.*, происходит блокировка до тех пор, пока данные не будут считаны или записаны. Поток выполнения в данный момент не может делать ничего другого.
Неблокирующий режим Java NIO позволяет запрашивать считанные данные из канала (channel) и получать только то, что доступно на данный момент, или вообще ничего, если доступных данных пока нет. Вместо того, чтобы оставаться заблокированным пока данные не станут доступными для считывания, поток выполнения может заняться чем-то другим.
Каналы – это логические (не физические) порталы, через которые осуществляется ввод/вывод данных, а буферы являются источниками или приёмниками этих переданных данных. При организации вывода, данные, которые вы хотите отправить, помещаются в буфер, а он передается в канал. При вводе, данные из канала помещаются в предоставленный вами буфер.
Каналы напоминают трубопроводы, по которым эффективно транспортируются данные между буферами байтов и сущностями по ту сторону каналов. Каналы – это шлюзы, которые позволяют получить доступ к сервисам ввода/вывода операционной системы с минимальными накладными расходами, а буферы – внутренние конечные точки этих шлюзов, используемые для передачи и приема данных.
Тоже самое справедливо и для неблокирующего вывода. Поток выполнения может запросить запись в канал некоторых данных, но не дожидаться при этом пока они не будут полностью записаны.
Таким образом неблокирующий режим Java NIO позволяет использовать один поток выполнения для решения нескольких задач вместо пустого прожигания времени на ожидание в заблокированном состояний. Наиболее частой практикой является использование сэкономленного времени работы потока выполнения на обслуживание операций ввода/вывода в другом или других каналах.
Селекторы
Селекторы в Java NIO позволяют одному потоку выполнения мониторить несколько каналов ввода. Вы можете зарегистрировать несколько каналов с селектором, а потом использовать один поток выполнения для обслуживания каналов, имеющих доступные для обработки данные, или для выбора каналов, готовых для записи.
Чтобы лучше понять концепцию и выгоду от применения селекторов, давайте абстрагируемся от программирования и представим себе железнодорожный вокзал. Вариант без селектора: есть три железнодорожных пути (каналы), на каждый из них в любой момент времени может прибыть поезд (данные из буфера), на каждом пути постоянно ожидает сотрудник вокзала (поток выполнения), задача которого – обслуживание прибывшего поезда. В результате трое сотрудников постоянно находятся на вокзале даже если там вообще нет поездов. Вариант с селектором: ситуация та же, но для каждой платформы есть индикатор, сигнализирующий сотруднику вокзала (поток выполнения) о прибытии поезда. Таким образом на вокзале достаточно присутствия одного сотрудника.
Влияние Java NIO и Java IO на дизайн приложения
Выбор между Java NIO и Java IO может на следующие аспекты дизайна вашего приложения:
1. API обращений к классам ввода/вывода;
2. Обработка данных;
3. Количество потоков выполнения, использованных для обработки данных.
API обращений к классам ввода/вывода
Естественно, использование Java NIO серьезно отличается от использования Java IO. Так как, вместо чтения данных байт за байтом с использованием, например InputStream, данные для начала должны быть считаны в буфер и браться для обработки уже оттуда.
Обработка данных
Обработка данных при использовании Java NIO тоже отличается.
Как уже упоминалось, при использовании Java IO вы читаете данные байт за байтом с InputStream или Reader. Представьте, что вы проводите считывание строк текстовой информации:
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890
Этот поток строк текста может обрабатываться следующим образом:
Обратите внимание, как состояние процесса обработки зависит от того, насколько далеко продвинулось выполнение программы. Когда первый метод readLine() возвращает результат выполнения, вы уверенны – целая строка текста была считана. Метод является блокирующим и действие блокировки продолжается до тех пор, пока вся строка не будет считана. Вы также четко понимаете, что данная строка содержит имя. Подобно этому, когда метод вызывается во второй раз, вы знаете, что в результате получите возраст.
Как вы видите, прогресс в выполнении программы достигается только тогда, когда доступны новые данные для чтения, и для каждого шага вы знаете что это за данные. Когда поток выполнения достигает прогресса в считывании определенной части данных, поток ввода (в большинстве случаев) уже не двигает данные назад. Данный принцип хорошо демонстрирует следующая схема:
Имплементация с использованием Java IO будет выглядеть несколько иначе:
Обратите внимание на вторую строчку кода, в которой происходит считывание байтов из канала в ByteBuffer. Когда возвращается результат выполнения данного метода, вы не можете быть уверенны, что все необходимые вам данные находятся внутри буфера. Все, что вам известно, так это то, что буфер содержит некоторые байты. Это немного усложняет процесс обработки.
Представьте, что после первого вызова метода read(buffer), в буфер было считано только половину строки. Например, “Name: An”. Сможете ли вы обработать такие данные? Наверное, что нет. Вам придется ждать пока, по крайней мере, одна полная строка текста не будет считана в буфер.
Так как же вам узнать, достаточно ли данных для корректной обработки содержит буфер? А никак. Единственный вариант узнать, это посмотреть на данные, содержащиеся внутри буфера. В результате вам придется по нескольку раз проверять данные в буфере, пока они не станут доступными для корректной обработки. Это неэффективно и может негативно сказаться на дизайне программы. Например:
Метод bufferFull() должен следить за тем, сколько данных считано в буфер и возвращать true или false, в зависимости от того, заполнен буфер или нет. Другими словами, если буфер готов к обработке, то он считается заполненным.
Также метод bufferFull() должен оставлять буфер в неизмененном состоянии, поскольку в противном случае следующая порция считанных данных может быть записана в неправильное место.
Если буфер заполнен, данные из него могут быть обработаны. Если он не заполнен вы все же будете иметь возможность обработать уже имеющиеся в нем данные, если это имеет смысл в вашем конкретном случае. В большинстве случаев – это бессмысленно.
Следующая схема демонстрирует процесс определения готовности данных в буфере для корректной обработки:
Итоги
Java NIO позволяет управлять несколькими каналами (сетевыми соединениями или файлами) используя минимальное число потоков выполнения. Однако ценой такого подхода является более сложный, чем при использовании блокирующих потоков, парсинг данных.
Если вам необходимо управлять тысячами открытых соединений одновременно, причем каждое из них передает лишь незначительный объем данных, выбор Java NIO для вашего приложения может дать преимущество. Дизайн такого типа схематически изображен на следующем рисунке:
Если вы имеете меньшее количество соединений, по которым передаются большие объемы данных, то лучшим выбором станет классический дизайн системы ввода/вывода:
Важно понимать, что Java NIO отнюдь не является заменой Java IO. Его стоит рассматривать как усовершенствование – инструмент, позволяющий значительно расширить возможности по организации ввода/вывода. Грамотное использование мощи обоих подходов позволит вам строить хорошие высокопроизводительные системы.
Стоит заметить, что с выходом версии Java 1.7 появился еще и Java NIO.2, но присущие ему новшества касаются, в первую очередь, работы с файловым вводом/выводом, поэтому выходят за рамки этой статьи.
Учебник по Java NIO
1. Введение
Java NIO — это библиотека, представленная в Java 1.4. Java NIO с момента своего запуска предоставила альтернативный способ обработки операций ввода-вывода и сетевых транзакций. Он считается альтернативой библиотекам Java Networking и Java IO. Java NIO была разработана с целью сделать транзакции для ввода и вывода асинхронными и неблокирующими. Концепция блокирующего и неблокирующего ввода-вывода объясняется в следующих разделах.
Содержание
2. Терминологии в Java NIO
Java NIO представил множество новых терминов в обработке ввода-вывода с использованием Java. В более раннем сценарии ввод-вывод Java был основан на символьных потоках и байтовых потоках. Однако в Java NIO данные теперь считываются и обрабатываются по каналам — либо через канал в буфер, либо через буфер в канал. В этом разделе мы обсудим различные термины, связанные с Java NIO, чтобы помочь нам лучше понять дальнейшее руководство.
2.1 Блокировка ввода и вывода
С символьными потоками и байтовыми потоками файл был загружен в память JVM и заблокирован для чтения или записи. Это привело к ситуации блокировки для других программ, пытающихся прочитать тот же файл. Такой сценарий приводит к потере мощности процессора, поскольку программы вынуждены были ждать, несмотря на доступную вычислительную мощность. Такая схема чтения или записи данных называется блокированием ввода и вывода.
2.2 Неблокирующий ввод и вывод
С появлением неблокирующего входа и выхода данные начали считываться в каналы. В неблокирующем устройстве ввода и вывода JVM использует каналы для буферизации данных. Каналы позволяют читать данные динамически, не блокируя файл для внешнего использования. Каналы являются буферизованной частью памяти, которая заполняется, как только предыдущие данные буфера были прочитаны. Это гарантирует, что файл не будет заблокирован во время полного цикла чтения, и другие программы будут иметь к нему необходимый доступ.
Java NIO в основном состоит из трех терминов:
Эти условия будут объявлены далее в этой статье.
3. Java NIO — Терминологии
Как обсуждалось выше, неблокирующий ввод-вывод Java работает на каналах и буферах. В этом разделе мы попытаемся понять эти термины вместе с дополнительными селекторами терминов. Эти условия важны для того, чтобы следовать дальнейшему учебнику.
3.1 Буферы
Буфер — это фиксированная часть памяти, используемая для хранения этих данных перед их чтением в канал. Буфер обеспечивает предварительную загрузку данных определенного размера для ускорения чтения файлов, входных данных и потоков данных. Размер буфера настраивается в блоках от 2 до степени n.
Различные типы буферов, которые могут использоваться в зависимости от типа ввода:
Каждый буфер предназначен для его конкретного использования. Буферы, обычно используемые для файлов, являются ByteBuffer и CharBuffer. Краткий пример создания ByteBuffer был показан ниже.
В приведенном выше коде создается буфер из 48 байтов. Размер буфера является обязательным для указания. 3-я строка выделяет область памяти 48 байтов для буфера buf. Это гарантирует, что необходимая память предварительно выделена для буфера. Процесс чтения и записи в буфер необходимо понять, прежде чем мы перейдем к их использованию для чтения и записи канала. На рисунке ниже показан типичный буфер чтения байтов из файла.
Как видно, буфер читает и толкает первый байт влево. Буфер является распределением памяти типа «Последний пришел — первый вышел». Следовательно, когда вы хотите прочитать файл с помощью буфера, необходимо перевернуть его, прежде чем вы сможете прочитать файл. Без переворота данные вышли бы в обратном порядке. Чтобы перевернуть буфер, необходимо выполнить простую строку кода, как показано ниже:
3.1.1 Свойства буфера
Буфер по сути имеет 3 свойства:
Во время записи в буфер позиция буфера — это позиция, в которой записывается текущий байт. Во время процесса чтения из буфера позиция буфера — это позиция, из которой читается байт. Положение буфера продолжает динамически меняться, когда мы продолжаем чтение или запись.
Во время записи в буфер предел буфера — это максимальный размер данных, которые могут быть записаны в буфер. По существу, ограничение буфера и емкость буфера являются синонимами во время записи в буфер. Однако во время чтения из буфера предел буфера — это количество доступных байтов для чтения из буфера. Следовательно, ограничение буфера продолжает уменьшаться по мере выталкивания байтов.
Емкость буфера — это максимальные данные, которые можно записать в буфер или прочитать из буфера в любой момент времени. Таким образом, размер 48, выделенный выше, также называется буферной емкостью.
3.1.2 Чтение и запись в буфер
Буфер, по сути, является средой для хранения данных, пока поток не прочитает данные из него и не запросит новые данные. Основным шагом к чтению данных из источника ввода является чтение данных в буфер. Для считывания данных в буфер используется фрагмент кода, показанный ниже.
3.1.3 Пометить и сбросить буфер
Во время процесса чтения часто возникают ситуации, когда вам необходимо повторно прочитать данные с определенной позиции. В обычном сценарии, когда вы получаете данные из буфера, они считаются пропавшими. Тем не менее, можно пометить буфер по определенному индексу, чтобы можно было снова прочитать его из определенной позиции. Код ниже демонстрирует, как это делается.
Основными приложениями отметки и сброса являются многократный анализ данных, многократное повторение информации, отправка команды на определенное количество раз и многое другое.
3.2 Каналы
Каналы являются основной средой для неблокирующего ввода-вывода. Каналы аналогичны потокам, доступным для блокировки ввода-вывода. Эти каналы поддерживают данные по сетям, а также файлы ввода-вывода данных. Каналы читают данные из буферов по мере необходимости. Буферы хранят данные до тех пор, пока они не будут прочитаны.
Каналы имеют несколько реализаций в зависимости от данных, которые нужно прочитать. Стандартные реализации, доступные для Каналов, перечислены ниже:
Как можно понять из названий каналов, они также охватывают сетевой IO-трафик UDP + TCP в дополнение к IO-файлу. В отличие от потоков, которые могут либо читать, либо записывать в определенный момент, один и тот же канал может беспрепятственно читать и записывать ресурсы. Каналы поддерживают асинхронное чтение и запись, что обеспечивает чтение данных без ущерба для выполнения кода. Буферы, рассмотренные выше, поддерживают эту асинхронную работу каналов.
3.2.1 Канал Scatter и Gather
Java NIO по своей природе поддерживает рассеяние и сбор данных для чтения и записи данных в несколько буферов. Java NIO достаточно умен, чтобы иметь возможность управлять чтением и записью в нескольких буферах.
Разброс Java NIO используется для разбивки чтения из канала на несколько буферов. Реализация кода довольно проста. Все, что вам нужно сделать, это добавить массив буферов в качестве аргумента для чтения. Фрагмент кода для того же самого был показан ниже.
Что такое java nio
Потоки ввода/вывода в Java
В чём заключается разница между IO и NIO?
Какие особенности NIO вы знаете?
Каналы (channels) – это логические (не физические) порталы, абстракции объектов более низкого уровня файловой системы (например, отображенные в памяти файлы и блокировки файлов), через которые осуществляется ввод/вывод данных, а буферы являются источниками или приёмниками этих переданных данных. При организации вывода, данные, которые необходимо отправить, помещаются в буфер, который затем передается в канал. При вводе, данные из канала помещаются в заранее предоставленный буфер.
Каналы напоминают трубопроводы, по которым эффективно транспортируются данные между буферами байтов и сущностями по ту сторону каналов. Каналы – это шлюзы, которые позволяют получить доступ к сервисам ввода/вывода операционной системы с минимальными накладными расходами, а буферы – внутренние конечные точки этих шлюзов, используемые для передачи и приема данных.
Какие существуют виды потоков ввода/вывода?
Назовите основные классы потоков ввода/вывода.
Разделяют два вида потоков ввода/вывода:
В каких пакетах расположены классы потоков ввода/вывода?
Какие подклассы класса InputStream вы знаете, для чего они предназначены?
Разновидность буферизации, обеспечивающая чтение байта с последующим его возвратом в поток. Класс PushbackInputStream представляет механизм «заглянуть» во входной поток и увидеть, что оттуда поступит в следующий момент, не извлекая информации.
У класса есть дополнительный метод unread().
Какой класс позволяет читать данные из входного байтового потока в формате примитивных типов данных?
Какие подклассы класса OutputStream вы знаете, для чего они предназначены?
Какие подклассы класса Reader вы знаете, для чего они предназначены?
Какие подклассы класса Writer вы знаете, для чего они предназначены?
Какие классы позволяют преобразовать байтовые потоки в символьные и обратно?
Какие классы позволяют ускорить чтение/запись за счет использования буфера?
Какой класс предназначен для работы с элементами файловой системы?
File работает непосредственно с файлами и каталогами. Данный класс позволяет создавать новые элементы и получать информацию существующих: размер, права доступа, время и дату создания, путь к родительскому каталогу.
Какие методы класса File вы знаете?
Наиболее используемые методы класса File :
Как выбрать все элементы определенного каталога по критерию (например, с определенным расширением)?
RandomAccessFile имеет такие специфические методы как:
Какие классы поддерживают чтение и запись потоков в компрессированном формате?
Существует ли возможность перенаправить потоки стандартного ввода/вывода?
Класс System позволяет вам перенаправлять стандартный ввод, вывод и поток вывода ошибок, используя простой вызов статического метода:
Какой символ является разделителем при указании пути в файловой системе?
Что такое «абсолютный путь» и «относительный путь»?
Абсолютный (полный) путь — это путь, который указывает на одно и то же место в файловой системе, вне зависимости от текущей рабочей директории или других обстоятельств. Полный путь всегда начинается с корневого каталога.
Относительный путь представляет собой путь по отношению к текущему рабочему каталогу пользователя или активного приложения.
Что такое «символьная ссылка»?
Символьная (символическая) ссылка (также «симлинк», Symbolic link) — специальный файл в файловой системе, в котором, вместо пользовательских данных, содержится путь к файлу, который должен быть открыт при попытке обратиться к данной ссылке (файлу). Целью ссылки может быть любой объект: например, другая ссылка, файл, каталог или даже несуществующий файл (в последнем случае, при попытке открыть его, должно выдаваться сообщение об отсутствии файла).
Символьные ссылки используются для более удобной организации структуры файлов на компьютере, так как: