В следующей таблице приведены основные различия между Java NIO и IO. Я опишу различия в каждой части таблицы более подробно.
Скопируйте код кода следующим образом:
ИО НИО
Потоково-ориентированный и буферно-ориентированный
Блокирующий ввод-вывод Неблокирующий ввод-вывод
Нет селектора
Потоково-ориентированный и буферно-ориентированный
Первое самое большое различие между Java NIO и IO заключается в том, что IO ориентирован на поток, а NIO — на буфер. Java IO ориентирован на поток, что означает, что один или несколько байтов считываются из потока одновременно, и пока все байты не будут прочитаны, они нигде не кэшируются. Кроме того, он не может перемещать данные в потоке вперед или назад. Если вам нужно переместить данные, считанные из потока, туда и обратно, вам необходимо сначала кэшировать их в буфере. Буферно-ориентированный подход Java NIO немного отличается. Данные считываются в буфер, который обрабатывается позже, перемещаясь вперед и назад по буферу по мере необходимости. Это повышает гибкость обработки. Однако вам также необходимо убедиться, что буфер содержит все данные, которые необходимо обработать. Кроме того, убедитесь, что по мере того, как в буфер считывается больше данных, необработанные данные в буфере не перезаписываются.
Блокирующий и неблокирующий ввод-вывод.
Блокируются различные потоки Java IO. Это означает, что когда поток вызывает read() или write(), поток блокируется до тех пор, пока некоторые данные не будут прочитаны или данные не будут полностью записаны. В течение этого периода поток не может делать ничего другого. Неблокирующий режим Java NIO позволяет потоку отправлять запрос на чтение данных из определенного канала, но он может получить только доступные на данный момент данные. Если в данный момент данных нет, ничего не будет получено. Вместо того, чтобы блокировать поток, он может продолжать выполнять другие действия до тех пор, пока данные не станут доступными для чтения. То же самое касается неблокирующей записи. Поток запрашивает запись некоторых данных в канал, но ему не нужно ждать, пока они будут полностью записаны. Тем временем поток может заниматься другими делами. Потоки обычно используют время простоя при неблокирующем вводе-выводе для выполнения операций ввода-вывода на других каналах, поэтому теперь один поток может управлять несколькими каналами ввода и вывода.
Селекторы
Селекторы Java NIO позволяют одному потоку контролировать несколько входных каналов. Вы можете зарегистрировать несколько каналов с помощью селектора, а затем использовать отдельный поток для «выбора» каналов: эти каналы уже имеют входные данные, которые можно обработать. Или выберите канал, который можно обработать. готов к написанию. Этот механизм выбора позволяет одному потоку легко управлять несколькими каналами.
Как NIO и IO влияют на дизайн приложений
Независимо от того, выберете ли вы набор инструментов IO или NIO, существует несколько аспектов, которые могут повлиять на дизайн вашего приложения:
1. Вызовы API к классам NIO или IO.
2. Обработка данных.
3. Количество потоков, используемых для обработки данных.
вызов API
Конечно, вызовы API при использовании NIO выглядят иначе, чем при использовании IO, но это не является неожиданным, поскольку вместо простого чтения из InputStream побайтно данные необходимо сначала прочитать в буфер, а затем обработать.
Обработка данных
Использование чистого дизайна NIO по сравнению с дизайном ввода-вывода также влияет на обработку данных.
При проектировании ввода-вывода мы читаем данные побайтово из InputStream или Reader. Предположим, вы обрабатываете поток текстовых данных, основанный на строках, например:
Скопируйте код кода следующим образом:
Имя: Анна
Возраст: 25
Электронная почта: [email protected]
Телефон: 1234567890
Поток текстовых строк можно обрабатывать следующим образом:
Скопируйте код кода следующим образом:
Читатель BufferedReader = новый BufferedReader (новый InputStreamReader (вход));
Имя строкиLine = readLine();
Строка ageLine = readLine.readLine();
Строка emailLine = readLine.readLine();
Строка phoneLine = readLine();
Обратите внимание, что статус обработки определяется тем, как долго выполняется программа. Другими словами, как только метод readLine() возвращает значение, вы точно знаете, что строка текста была прочитана. Вот почему readline() блокируется до тех пор, пока не будет прочитана вся строка. Вы также знаете, что эта строка содержит имена; аналогично, когда второй вызов readline() возвращает значение, вы знаете, что эта строка содержит возраст и т. д. Как видите, этот обработчик запускается только при чтении новых данных и знает, что это за данные на каждом этапе. После того как работающий поток обработал некоторые из прочитанных данных, он не будет выполнять откат данных (в основном). Следующий рисунок также иллюстрирует этот принцип:
(Java IO: чтение данных из блокирующего потока) Хотя реализация NIO будет другой, вот простой пример:
Скопируйте код следующим образом: ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(буфер);
Обратите внимание на вторую строку, считывающую байты из канала в ByteBuffer. Когда этот вызов метода возвращает значение, вы не знаете, все ли необходимые вам данные находятся в буфере. Все, что вам известно, это то, что буфер содержит несколько байтов, что немного усложняет обработку.
Предположим, что после первого вызова read(buffer) данные, считанные в буфер, составляют только половину строки, например «Имя: An», можете ли вы обработать данные? Очевидно нет, нужно дождаться, пока вся строка данных будет прочитана в кэш. До этого любая обработка данных бессмысленна.
Итак, как узнать, содержит ли буфер достаточно данных для обработки? Ну, ты не знаешь. Обнаруженные методы могут только просматривать данные в буфере. В результате вам придется несколько раз проверять данные буфера, прежде чем вы узнаете, что все данные находятся в буфере. Это не только неэффективно, но и может загромождать программное решение. Например:
Скопируйте код кода следующим образом:
Буфер ByteBuffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(буфер);
while(!ufferFull(bytesRead)) {
bytesRead = inChannel.read(буфер);
}
Метод bufferFull() должен отслеживать, сколько данных было прочитано в буфер, и возвращать true или false, в зависимости от того, заполнен ли буфер. Другими словами, если буфер готов к обработке, он полон.
Метод bufferFull() сканирует буфер, но должен оставаться в том же состоянии, что и до вызова метода bufferFull(). В противном случае следующие данные, считанные в буфер, могут быть прочитаны не в правильном месте. Это невозможно, но это еще одна проблема, о которой следует знать.
Если буфер полон, его можно обработать. Если это не работает, и это имеет смысл в вашем конкретном случае, возможно, вы сможете справиться с некоторыми из них. Но во многих случаях это не так. На следующем рисунке показано «готовность цикла данных буфера»:
3) Количество потоков, используемых для обработки данных
NIO позволяет вам управлять несколькими каналами (сетевыми подключениями или файлами), используя всего один поток (или несколько), но компромисс заключается в том, что анализ данных может быть более сложным, чем чтение их из блокирующего потока.
Если вам нужно управлять тысячами подключений, открытых одновременно и каждый раз отправляющих только небольшие объемы данных, например, сервером чата, сервер, реализующий NIO, может быть преимуществом. Аналогичным образом, если вам необходимо поддерживать множество открытых подключений к другим компьютерам, например, в сети P2P, может оказаться полезным использовать отдельный поток для управления всеми исходящими подключениями. План проектирования нескольких соединений в одной нити следующий:
Java NIO: один поток, управляющий несколькими соединениями
Если у вас небольшое количество подключений, использующих очень высокую пропускную способность и одновременно отправляющих большие объемы данных, возможно, вам подойдет типичная реализация сервера ввода-вывода. На следующем рисунке показана типичная конструкция сервера ввода-вывода:
Java IO: типичная конструкция сервера ввода-вывода — одно соединение обрабатывается одним потоком.