Фоновые знания
Синхронно, асинхронно, блокируя, не блокируя
Прежде всего, эти концепции очень легко запутаться, но они участвуют в NIO, так что давайте подводщим это.
Синхронизация: Когда вызов API возвращается, вызывающий абонент знает, как в результате операция (сколько байтов фактически читается/запись).
Асинхронный: по сравнению с синхронизацией, вызывающий абонент не знает результата операции, когда вызов API возвращается, и обратный вызов уведомит результат позже.
Блокировка: когда нет данных, которые можно прочитать или все данные не могут быть записаны, приостановите текущий поток ожидания.
Не блокировка: при чтении вы можете прочитать столько же, сколько и данные, которые вы можете прочитать, а затем вернуть. При написании вы можете написать столько же, сколько и данные, которые вы можете написать, а затем вернуть.
Для операций ввода/вывода, согласно документам официального веб -сайта Oracle, синхронизация и асинхронное стандарт разделения: «должен ли звонящий ждать операции ввода -вывода». Это «ожидание операции ввода/вывода для завершения» не означает, что данные должны быть прочитаны или должны быть записаны все данные, а скорее вопрос о том, нужно ли абонент ждать, когда операция ввода/вывода фактически выполняется, например, время, когда данные передаются между буфером стека протокола TCP/IP и буфером JVM.
Следовательно, наши часто используемые методы read () и write () являются синхронным вводом/выводом. Синхронный ввод/вывод делится на два режима: блокировка и не блокировка. Если это не блокирующий режим, он будет возвращен непосредственно, когда он обнаружит, что нет данных, которые можно прочитать, и операция ввода-вывода на самом деле не выполняется.
Таким образом, в Java на самом деле есть только три механизмы: синхронное блокирование ввода-вывода, синхронное неблокирующее ввод-вывод и асинхронное ввод-вывод. То, о чем мы говорим ниже, это первые два. JDK1.7 начал вводить асинхронный ввод -вывод, который называется NIO.2.
Традиционный io
Мы знаем, что появление новой технологии всегда сопровождается улучшениями и улучшениями, как и появление Javanio.
Традиционный ввод -вывод блокирует ввод -вывод, а основной проблемой является пустая трата системных ресурсов. Например, для чтения данных подключения TCP мы называем метод inputStream Read (), который приведет к приостановке текущего потока и не будет пробуждено до появления данных. Поток занимает ресурсы памяти (стек резьбы хранения) в течение периода прибытия данных, но ничего не делает. Это то, что говорит поговорка, занимая яму, а не какашки. Чтобы прочитать данные других соединений, мы должны запустить другой поток. Это может быть хорошо, когда существует не так много одновременных соединений, но когда количество соединений достигает определенной масштаба, ресурсы памяти будут потреблять большое количество потоков. С другой стороны, переключение потока требует изменения состояния процессора, таких как значения счетчиков программ и регистров, поэтому переключение между большим количеством потоков также является пустой тратой ресурсов.
Благодаря разработке технологий современные операционные системы предоставляют новые механизмы ввода/вывода, которые могут избежать этой траты ресурсов. Основываясь на этом, родился Javanio, и репрезентативной особенностью NIO является неблокирующий ввод-вывод. Сразу же после этого мы обнаружили, что простое использование не блокирующего ввода-вывода не может решить проблему, потому что в неблокирующем режиме метод read () немедленно вернется при чтении данных. Мы не знаем, когда поступят данные, поэтому мы можем только называть метод Read (), чтобы попробовать еще раз. Это, очевидно, пустая трата ресурсов процессора. Следующим мы можем знать, что компонент селектора был рожден для решения этой проблемы.
Джаванио основные компоненты
1. Канал
концепция
Все операции ввода/вывода в Javanio основаны на объектах канала, так же, как операции потока основаны на потоковых объектах, поэтому необходимо сначала понять, что такое канал. Следующий контент выдержек из документации JDK1.8
Achannel представляет собой соединение приложений к аннексии Shyasahardwreadevice, Afile, AnnetWorksocket, компонента оропрограммы, который может быть выполнен на одном или оперативном входе в/ -Операции, Forexamplereading Orwriting.
Из приведенного выше контента мы видим, что канал представляет собой соединение с определенной организацией, которая может быть файлом, сетевым розетком и т. Д. Другими словами, канал - это мост, предоставляемый Javanio для наших программ для взаимодействия с базовыми услугами ввода -вывода операционной системы.
Каналы являются очень основным и абстрактным описанием, взаимодействуют с различными услугами ввода -вывода, выполняют разные операции ввода -вывода и реализуют различные реализации. Следовательно, конкретные включают в себя FileChannel, Socketchannel и т. Д.
Канал похож на поток при использовании. Он может считывать данные в буфер или записывать данные в буфере в канал.
Конечно, есть также различия, которые в основном отражаются в следующих двух точках:
Канал может быть прочитал и написан, в то время как поток находится в одном уровне (так деленная на inputstream и outputstream)
Канал имеет не блокирующий режим ввода-вывода
выполнить
Наиболее часто используемые реализации каналов в Javanio заключаются в следующем, и видно, что они соответствуют традиционным классам операции ввода -вывода один за другим.
FileChannel: читать и записать файлы
DatagramChannel: коммуникация сетевой протокола UDP
SocketcheNanlan: TCP протокол сетевой коммуникация
Serversocketchannel: Слушайте соединения TCP
2. Буффер
Буфер, используемый в NIO, представляет собой не простой байтовый массив, а инкапсулированный буферный класс. Через API, который он предоставляет, мы можем гибко манипулировать данными. Давайте поближе посмотрим.
В соответствии с базовыми типами Java Nio предоставляет множество типов буферов, таких как Bytebuffer, Charbuffer, Intbuffer и т. Д.
В буфере есть 3 очень важных переменных. Они являются ключом к пониманию рабочего механизма буфера, а именно
емкость (общая мощность)
положение (текущее положение указателя)
Предел (положение чтения/записи границ)
Рабочий метод буфера очень похож на массивы символов в C. В аналогии, емкость - это общая длина массива, позиция является переменной индекса для нас для чтения/записи символов, а предел является позицией конечного символа. Ситуация 3 переменных в начале буфера выглядит следующим образом
Во время процесса чтения/написания буфера позиция будет двигаться назад, а ограничение является границей движения позиции. Нетрудно представить, что при записи в буфер ограничение должно быть установлено на размер емкости, а при чтении в буфер ограничение должно быть установлено на фактическое конечное положение данных. (Примечание. Написание буферных данных на канале является операцией чтения буфера, а чтение данных из канала в буфер - это операция записи буфера)
Перед операциями чтения/записи в буфере мы можем вызвать некоторые вспомогательные методы, предоставленные классом буфера, чтобы правильно установить значения позиции и лимита, главным образом следующим образом
Flip (): установите ограничение на значение позиции, а затем установите положение на 0. Вызовы перед чтением буфер.
rewind (): просто установите положение 0. Обычно его называют перед перечитыванием данных буфера, например, он будет использоваться при чтении данных одного и того же буфера и записывает их в несколько каналов.
clear (): вернуться в исходное состояние, то есть предел равен емкости, позиция, установленная в 0. Вызовите буфер перед написанием.
Compact (): Переместите нечитанные данные (данные между положением и пределом) в начало буфера и установили позицию в следующую позицию в конце этих данных. Фактически, это эквивалентно написанию такого куска данных в буфер.
Затем посмотрите на пример, используйте FileChannel для чтения и записи текстовых файлов, и используйте этот пример для проверки читаемых и записи для записи характеристик канала и основного использования буфера (обратите внимание, что FileChannel не может быть установлен в режим не блокировки).
FileChannel Channel = new randomAccessfile ("test.txt", "rw"). GetCannel (); cannel.position (channel.size ()); // переместить указатель файла в End (добавление записи) bytebuffer bytebuffer = bytebuffer.allocate (20); // Написать данные на Buff world!/n".getBytes(StandardCharsets.UTF_8));// Buffer -> ChannelbyteBuffer.flip();while (byteBuffer.hasRemaining()) {channel.write(byteBuffer);}channel.position(0);// Move the file pointer to the beginning (read from the beginning) CharBuffer charBuffer = CharBuffer.allocate(10); Charsetdecoder decoder = standardcharsets.utf_8.newdecoder (); // Прочитать все данные bytebuffer.clear (); while (channel.read (bytebuffer)! = -1 || bytebuffer.position ()> 0) {bytebuffer.flip (); // decode charbuffer.clear (); decoder.decode (bytebuffer, charbuffer, false); Могут быть оставшиеся данные} channel.close ();В этом примере используются два буфера, где Bytebuffer является буфером данных для чтения и письма канала, а Charbuffer используется для хранения декодированных символов. Использование Clear () и Flip (), как упомянуто выше. Следует отметить, что последнего метода Compact () есть, даже если размер Charbuffer абсолютно достаточно для размещения декодированных данных Bytebuffer, этот Compact () также имеет важное значение. Это связано с тем, что кодирование UTF-8 обычно используемых китайских иелок учитывает 3 байта, поэтому существует высокая вероятность того, что это произойдет в среднем усечении. См. Рисунок ниже:
Когда декодер считывает 0xe4 в конце буфера, его нельзя отображать с юникодом. Третий параметр метода decode (), false, используется для того, чтобы декодер относился к непоколебимым байтам и последующим данным как дополнительные данные. Следовательно, метод decode () остановится здесь, и позиция вернется в положение 0xE4. Таким образом, первый байт, закодированный словом «среда», остается в буфере, и он должен быть уплотнен спереди и сплайсирован вместе с правильными и последующими данными о последовательности. Что касается кодирования персонажей, вы можете сослаться на « Объяснение ANSI, Unicode, BMP, UTF и других концепций кодирования »
Кстати, CharsetDecoder в примере также является новой особенностью Javanio, поэтому вы должны были обнаружить немного. Операции NIO ориентированы на буфер (традиционный ввод-вывод ориентирован).
На данный момент мы узнали об основном использовании канала и буфера. Далее мы поговорим о важных компонентах, позволяющих потоке управлять несколькими каналами.
3. SELECTOR
Что такое селектор
Селектор - это специальный компонент, используемый для сбора состояния (или событий) каждого канала. Сначала мы зарегистрируем канал для селектора и устанавливаем событие, которое мы заботимся, и затем мы можем спокойно подождать событие, вызвав метод select ().
В канале есть следующие 4 события, которые мы смогли послушать:
Принять: есть приемлемая связь
Connect: Подключите успешно
Читать: есть данные для чтения
Напишите: вы можете написать данные
Зачем использовать селектор
Как упомянуто выше, если вы используете блокирующий ввод-вывод, вам нужно многопоточное (пустая трата памяти), и если вы используете не блокирующий ввод-вывод, вам нужно постоянно повторять снова (потребление ЦП). Появление селектора решает эту смущающую проблему. В режиме без блокировки, через селектор, наши потоки работают только для готовых каналов, и нет необходимости пробовать вслепую. Например, когда данные не будут получены во всех каналах, не происходит события чтения, и наш поток будет приостановлен в методе SELECT (), что отдает ресурсы ЦП.
Как использовать
Как показано ниже, создайте селектор и зарегистрируйте канал.
ПРИМЕЧАНИЕ.
SELECTER SELECTER = SELECTER.OPEN (); Channel.ConfigureBlocking (false); selectionKey Key = Channel.Gregister (селектор, selectionKey.op_read);
Второй параметр метода Register () называется «набор интересов», который является набором событий, который вас обеспокоен. Если вы заботитесь о нескольких событиях, разделите их с «битаном или оператором», например,
SelectionKey.op_read | SelectionKey.op_write
Этот метод письма не незнаком с ним. Он воспроизводится на языках программирования, которые поддерживают операции битов. Использование целочисленной переменной может идентифицировать несколько состояний. Как это делается? Это на самом деле очень просто. Например, сначала предопределил некоторые константы, а их значения (бинарные) следующие
Можно обнаружить, что биты с их значением 1 все ошеломлены, поэтому значения, полученные после выполнения кусочка или расчеты на них, не имеют двусмысленности, и они могут быть выведены обратно пропорционально, из которых рассчитываются переменные. Как судить, да, это операция «Биты и». Например, в настоящее время существует значение переменной установки состояния 0011. Нам нужно только определить, составляет ли значение «0011 & op_read» 1 или 0, чтобы определить, содержит ли набор состояние OP_READ.
Затем обратите внимание, что метод Register () возвращает объект SelectionKey, который содержит информацию для этой регистрации, и мы также можем изменить регистрационную информацию через него. Из приведенного ниже примера мы видим, что после Select () мы также подготовим каналы с состоянием готовы, получив коллекцию SelectionKeys.
Полный пример
Концепции и теоретические вещи были объяснены (на самом деле, после написания их здесь, я обнаружил, что не написал много, что так смущает (⊙ˍ⊙)). Давайте посмотрим на полный пример.
В этом примере используется Javanio для реализации однопоточного сервера. Функция очень проста. Он слушает клиентское соединение. Когда соединение установлено, оно читает сообщение клиента и отвечает на сообщение клиенту.
Следует отметить, что я использую символ «/0» (байт со значением 0) для определения конца сообщения.
Однопоточный сервер
открытый класс NioServer {public static void main (string [] args) выбрасывает ioException {// Создать селектор SelectorSelector = selector.open (); // инициализировать подключение к подключению TCP Serversocketchannel diStenChannel = servercocketchannel.open (); listenchannel.bind (new InetsocketAddress (9999)); listenchannel.configureblocking (false); // зарегистрировать в селектор (прослушивать его событие принятия) listenchannel.register (selector, selectionkey.op_accte); // Создать буфер bytebuffer = bytebuffer.allocate (100); while (true) {selector.select (); // block до тех пор, пока событие не будет прослушивается итератор <selectionkey> keyter = selector.selectedkeys (). iterator (); // Доступ к событию канала, выбранным через итератор, в свою очередь, while (keyter.iscectab Socketchannel Channel = ((serversocketchannel) key.channel ()). Accept (); Channel.configureblocking (false); Channel.register (selecter, selectionkey.op_read); System.out.println ("Соединение, установленное с [" + channel.getRemoteaddress () + "]!") buffer.clear (); // Читать до конца потока, указывая на то, что соединение TCP было отключено, // Следовательно, необходимо закрыть канал или отменить прослушивание события чтения // в противном случае, оно будет бесконечно зацикливаться на if (((socketchannel). buffer.flip (); while (buffer.hasreming ()) {byte b = buffer.get (); if (b == 0) {// /0system.out.println () в конце клиентского сообщения; // client buffer. {((Socketchannel) key.channel ()). Write (buffer);}} else {System.out.print ((char) b);}}} // Для событий, которые были обработаны, вы должны вручную удалить keyter.remove ();}}}}}}}}Клиент
Этот клиент используется исключительно для тестирования. Чтобы сделать его менее трудным, он использует традиционные методы письма, а код очень короткий.
Если вам нужно быть более строгим в тестировании, вам следует одновременно запустить большое количество клиентов, чтобы подсчитать время отклика сервера и не отправлять данные сразу после установки соединения, чтобы дать полную игру для преимуществ неблокирующего ввод/вывода на сервере.
Public Class Client {public static void main (string [] args) бросает исключение {сокет сокет = новый сокет ("localhost", 9999); inputStream IS = socket.getInputStream (); outputStream OS = socket.getOutputStream (); // Отправить данные на сервер сначала OS.Write ("Hello, Server!/0". while ((b = is.read ())! = 0) {System.out.print ((char) b);} system.out.println (); socket.close ();}}Суммировать
Вышеуказанное все о быстрого понимания компонентов Nio Core в Java. Я надеюсь, что это будет полезно для всех. Заинтересованные друзья могут продолжать ссылаться на другое соответствующее содержание этого веб -сайта. Если есть какие -либо недостатки, пожалуйста, оставьте сообщение, чтобы указать это. Спасибо, друзья, за вашу поддержку на этом сайте!