Java NIO обеспечивает другой способ работы ввода-вывода, чем стандартный ввод-вывод:
Каналы и буферы: стандартный ввод-вывод работает на основе потоков байтов и символов, тогда как NIO работает на основе каналов (канал) и буферов (буфер). Данные всегда считываются из канала в область буфера или записываются из буфера в область буфера. канал.
Асинхронный ввод-вывод: Java NIO позволяет использовать ввод-вывод асинхронно. Например, когда поток считывает данные из канала в буфер, он все равно может выполнять другие действия. Когда данные записываются в буфер, поток может продолжить их обработку. Запись в канал из буфера аналогична.
Селекторы: Java NIO представляет концепцию селекторов, которые используются для прослушивания событий по нескольким каналам (например: открытие соединения, поступление данных). Таким образом, один поток может прослушивать несколько каналов данных.
Давайте подробно познакомим вас с соответствующими знаниями Java NIO.
Обзор Java NIO
Java NIO состоит из следующих основных частей:
Каналы
Буферы
Селекторы
Хотя в Java NIO есть много других классов и компонентов, на мой взгляд, Channel, Buffer и Selector составляют основной API. Другие компоненты, такие как Pipe и FileLock, представляют собой просто служебные классы, используемые с тремя основными компонентами. Поэтому в этом обзоре я остановлюсь на этих трёх компонентах. Остальные компоненты описаны в отдельных главах.
Канал и буфер
По сути, все операции ввода-вывода в NIO начинаются с канала. Каналы чем-то похожи на потоки. Данные можно считывать из канала в буфер или записывать из буфера в канал. Вот иллюстрация:
Существует несколько типов каналов и буферов. Ниже приведены реализации некоторых основных каналов в JAVA NIO:
Файловый канал
Датаграммный канал
СокетКанал
Серверсокетканал
Как видите, эти каналы охватывают сетевой ввод-вывод UDP и TCP, а также файловый ввод-вывод.
Наряду с этими классами есть еще несколько интересных интерфейсов, но я ради простоты постарался не упоминать их в обзоре. Я объясню их в других главах этого руководства, где они применимы.
Ниже приведена ключевая реализация Buffer в Java NIO:
Байтбуфер
CharBuffer
Двойнойбуфер
FloatBuffer
IntBuffer
Длинныйбуфер
Короткийбуфер
Эти буферы охватывают основные типы данных, которые вы можете отправлять через ввод-вывод: byte, short, int, long, float, double и char.
В Java NIO также есть Mappyteuffer, который используется для представления файлов, отображаемых в памяти. Я не буду объяснять это в обзоре.
Селектор
Селектор позволяет одному потоку обрабатывать несколько каналов. Если ваше приложение открывает несколько соединений (каналов), но трафик каждого соединения очень низкий, использование Selector может быть удобным. Например, на чат-сервере.
Это иллюстрация использования селектора для обработки трех каналов в одном потоке:
Чтобы использовать селектор, вы должны зарегистрировать канал в селекторе, а затем вызвать его метод select(). Этот метод будет блокироваться до тех пор, пока зарегистрированный канал не подготовит событие. После возврата этого метода поток может обрабатывать эти события. Примерами событий являются новые соединения, получение данных и т. д.
Java NIO против IO
(Оригинальный адрес этой части, автор: Якоб Дженков, переводчик: Го Лэй, корректор: Фан Тэнфэй)
Узнав о Java NIO и IO API, сразу же в голову пришел вопрос:
Цитировать
Когда мне следует использовать IO, а когда — NIO? В этой статье я постараюсь доходчиво объяснить различия между Java NIO и IO, сценарии их использования и то, как они влияют на дизайн вашего кода.
Основные различия между Java NIO и IO
В следующей таблице приведены основные различия между Java NIO и IO. Я опишу различия в каждой части таблицы более подробно.
ИО НИО
Потоковая ориентированность Буферная ориентированность
Блокирующий ввод-вывод Неблокирующий ввод-вывод
Селекторы
Потоково-ориентированный и буферно-ориентированный
Первое самое большое различие между Java NIO и IO заключается в том, что IO ориентирован на поток, а NIO — на буфер. Java IO ориентирован на поток, что означает, что один или несколько байтов считываются из потока одновременно, и пока все байты не будут прочитаны, они нигде не кэшируются. Кроме того, он не может перемещать данные в потоке вперед или назад. Если вам нужно переместить данные, считанные из потока, туда и обратно, вам необходимо сначала кэшировать их в буфере. Буферно-ориентированный подход Java NIO немного отличается. Данные считываются в буфер, который позже обрабатывается, перемещаясь вперед и назад по буферу по мере необходимости. Это повышает гибкость обработки. Однако вам также необходимо убедиться, что буфер содержит все данные, которые необходимо обработать. Кроме того, убедитесь, что по мере того, как в буфер считывается больше данных, необработанные данные в буфере не перезаписываются.
Блокирующий и неблокирующий ввод-вывод.
Различные потоки Java IO блокируются. Это означает, что когда поток вызывает read() или write(), поток блокируется до тех пор, пока некоторые данные не будут прочитаны или данные не будут полностью записаны. В течение этого периода поток не может делать ничего другого. Неблокирующий режим Java NIO позволяет потоку отправлять запрос на чтение данных из определенного канала, но он может получить только доступные на данный момент данные. Если в данный момент данных нет, ничего не будет получено. Вместо того, чтобы блокировать поток, он может продолжать выполнять другие действия до тех пор, пока данные не станут доступными для чтения. То же самое касается неблокирующей записи. Поток запрашивает запись некоторых данных в канал, но ему не нужно ждать, пока они будут полностью записаны. Тем временем поток может заниматься другими делами. Потоки обычно используют время простоя при неблокирующем вводе-выводе для выполнения операций ввода-вывода на других каналах, поэтому теперь один поток может управлять несколькими каналами ввода и вывода.
Селекторы
Селекторы Java NIO позволяют одному потоку контролировать несколько входных каналов. Вы можете зарегистрировать несколько каналов с помощью селектора, а затем использовать отдельный поток для «выбора» каналов: эти каналы уже имеют входные данные, которые можно обработать. Или выберите канал, который можно обработать. готов к написанию. Этот механизм выбора позволяет одному потоку легко управлять несколькими каналами.
Как NIO и IO влияют на дизайн приложений
Независимо от того, выберете ли вы набор инструментов IO или NIO, существует несколько аспектов, которые могут повлиять на дизайн вашего приложения:
Вызовы API к классам NIO или IO.
Обработка данных.
Количество потоков, используемых для обработки данных.
вызов API
Конечно, вызовы API при использовании NIO выглядят иначе, чем при использовании IO, но это не является неожиданным, поскольку вместо простого чтения из InputStream побайтно данные необходимо сначала прочитать в буфер, а затем обработать.
Обработка данных
Использование чистого дизайна NIO по сравнению с дизайном ввода-вывода также влияет на обработку данных.
При проектировании ввода-вывода мы читаем данные побайтово из InputStream или Reader. Предположим, вы обрабатываете поток текстовых данных, основанный на строках, например:
Скопируйте код кода следующим образом:
Имя: Анна
Возраст: 25
Электронная почта: [email protected]
Телефон: 1234567890
Поток текстовых строк можно обрабатывать следующим образом:
Скопируйте код кода следующим образом:
InputStream input = … // получаем InputStream из клиентского сокета
Читатель BufferedReader = новый BufferedReader (новый InputStreamReader (вход));
Имя строкиLine = readLine();
Строка ageLine = readLine.readLine();
Строка emailLine= readLine.readLine();
Строка phoneLine= readLine.readLine();
Обратите внимание, что статус обработки определяется тем, как долго выполняется программа. Другими словами, как только метод readLine() возвращает значение, вы точно знаете, что строка текста прочитана. Вот почему readline() блокируется до тех пор, пока не будет прочитана вся строка. Вы также знаете, что эта строка содержит имена; аналогично, когда второй вызов readline() возвращает значение, вы знаете, что эта строка содержит возраст и т. д. Как видите, этот обработчик запускается только при чтении новых данных и знает, что это за данные на каждом этапе. После того как работающий поток обработал некоторые из прочитанных данных, он не будет выполнять откат данных (в основном). Следующий рисунок также иллюстрирует этот принцип:
Чтение данных из заблокированного потока
Хотя реализация NIO будет отличаться, вот простой пример:
Скопируйте код кода следующим образом:
Буфер ByteBuffer = 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(). В противном случае следующие данные, считанные в буфер, могут быть прочитаны не в правильном месте. Это невозможно, но это еще одна проблема, о которой следует знать.
Если буфер полон, его можно обработать. Если это не работает, и это имеет смысл в вашем конкретном случае, возможно, вы сможете справиться с некоторыми из них. Но во многих случаях это не так. На следующем рисунке показано «готовность цикла данных буфера»:
Чтение данных из канала до тех пор, пока все данные не будут прочитаны в буфер.
Подвести итог
NIO позволяет вам управлять несколькими каналами (сетевыми подключениями или файлами), используя всего один поток (или несколько), но компромисс заключается в том, что анализ данных может быть более сложным, чем чтение их из блокирующего потока.
Если вам нужно управлять тысячами подключений, открытых одновременно и каждый раз отправляющих только небольшие объемы данных, например, сервером чата, сервер, реализующий NIO, может быть преимуществом. Аналогичным образом, если вам необходимо поддерживать множество открытых подключений к другим компьютерам, например, в сети P2P, может оказаться полезным использовать отдельный поток для управления всеми исходящими подключениями. Схема расчета нескольких соединений в одну резьбу представлена на рисунке ниже:
Один поток управляет несколькими соединениями
Если у вас небольшое количество соединений, использующих очень высокую пропускную способность и одновременно отправляющих большие объемы данных, возможно, вам подойдет типичная реализация сервера ввода-вывода. На следующем рисунке показана типичная конструкция сервера ввода-вывода:
Типичная конструкция сервера ввода-вывода:
Соединение обрабатывается потоком
Канал
Каналы Java NIO похожи на потоки, но несколько отличаются:
Данные можно считывать из канала и записывать в канал. Но потоки чтения и записи обычно односторонние.
Каналы можно читать и записывать асинхронно.
Данные в канале сначала должны быть прочитаны из буфера или всегда записаны из буфера.
Как упоминалось выше, данные считываются из канала в буфер, а данные записываются из буфера в канал. Как показано ниже:
Реализация канала
Это реализации наиболее важных каналов в Java NIO:
FileChannel: чтение и запись данных из файлов.
DatagramChannel: может читать и записывать данные в сети через UDP.
SocketChannel: может читать и записывать данные в сети через TCP.
ServerSocketChannel: может отслеживать входящие TCP-соединения, как веб-сервер. SocketChannel создается для каждого нового входящего соединения.
Пример базового канала
Ниже приведен пример использования FileChannel для чтения данных в буфер:
Скопируйте код кода следующим образом:
RandomAccessFile aFile = новый RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Читать" + bytesRead);
буф.флип();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
буф.очистить();
bytesRead = inChannel.read(buf);
}
аФайл.закрыть();
Обратите внимание, что вызов buf.flip() сначала считывает данные в буфер, затем инвертирует буфер, а затем считывает данные из буфера. В следующем разделе мы более подробно рассмотрим Buffer.
Буфер
Буфер в Java NIO используется для взаимодействия с каналами NIO. Как известно, данные считываются из канала в буфер и записываются из буфера в канал.
Буфер — это, по сути, блок памяти, в который могут быть записаны данные и из которого данные затем могут быть прочитаны. Эта память упакована как объект NIO Buffer и предоставляет набор методов для удобного доступа к этой памяти.
Основное использование буфера
Использование Buffer для чтения и записи данных обычно состоит из следующих четырех шагов:
Запись данных в буфер
Вызов метода флип()
Чтение данных из буфера
Вызовите метод Clear() или метод Compact().
Когда данные записываются в буфер, буфер записывает, сколько данных было записано. Если вы хотите прочитать данные, вам нужно переключить Buffer из режима записи в режим чтения с помощью метода Flip(). В режиме чтения все данные, ранее записанные в буфер, могут быть прочитаны.
После того как все данные прочитаны, буфер необходимо очистить, чтобы в него можно было снова записать. Существует два способа очистки буфера: вызов метода Clear() или Compact(). Метод Clear() очищает весь буфер. Метод Compact() очистит только прочитанные данные. Любые непрочитанные данные перемещаются в начало буфера, а вновь записанные данные помещаются в буфер после непрочитанных данных.
Вот пример использования Buffer:
Скопируйте код кода следующим образом:
RandomAccessFile aFile = новый RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//создаем буфер емкостью 48 байт
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //читаем в буфер.
while (bytesRead != -1) {
buf.flip();//подготавливаем буфер к чтению
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // читаем по 1 байту за раз
}
buf.clear(); //готовим буфер для записи
bytesRead = inChannel.read(buf);
}
аФайл.закрыть();
Емкость, положение и предел буфера
Буфер — это, по сути, блок памяти, в который могут быть записаны данные и из которого данные затем могут быть прочитаны. Эта память упакована как объект NIO Buffer и предоставляет набор методов для удобного доступа к этой памяти.
Чтобы понять, как работает Buffer, вам необходимо ознакомиться с тремя его свойствами:
емкость
позиция
предел
Значение позиции и предела зависит от того, находится ли буфер в режиме чтения или режиме записи. Независимо от того, в каком режиме находится буфер, значение емкости всегда одно и то же.
Ниже приводится объяснение емкости, положения и предела в режиме чтения и записи с подробными пояснениями после иллюстрации.
емкость
Как блок памяти, Buffer имеет фиксированное значение размера, также называемое «емкостью». В него можно записывать только емкость byte, long, char и других типов. Когда буфер заполнен, его необходимо очистить (путем чтения или очистки данных), прежде чем можно будет продолжить запись данных.
позиция
Когда вы записываете данные в буфер, позиция представляет текущую позицию. Начальное значение позиции равно 0. Когда в буфер записываются байтовые, длинные и т. д. данные, позиция перемещается вперед к следующему блоку буфера, куда можно вставить данные. Максимальное положение может быть емкостью 1.
Когда данные читаются, они также считываются из определенного места. При переключении буфера из режима записи в режим чтения позиция будет сброшена в 0. Когда данные считываются из позиции буфера, позиция перемещается вперед к следующей читаемой позиции.
предел
В режиме записи предел буфера указывает максимальный объем данных, которые вы можете записать в буфер. В режиме записи предел равен емкости буфера.
При переключении буфера в режим чтения предел указывает максимальный объем данных, который вы можете прочитать. Поэтому при переключении буфера в режим чтения предел будет установлен на значение позиции в режиме записи. Другими словами, вы можете прочитать все записанные ранее данные (ограничение установлено на количество записанных данных, это значение является позицией в режиме записи)
Тип буфера
Java NIO имеет следующие типы буферов:
Байтбуфер
MappedByteBuffer
CharBuffer
Двойнойбуфер
FloatBuffer
IntBuffer
Длинныйбуфер
Короткийбуфер
Как видите, эти типы Buffer представляют разные типы данных. Другими словами, байтами в буфере можно манипулировать с помощью типов char, short, int, long, float или double.
MappedByteBuffer немного особенный и будет обсуждаться в отдельной главе.
Распределение буфера
Чтобы получить объект Buffer, вы должны сначала выделить его. Каждый класс Buffer имеет метод распределения. Ниже приведен пример выделения ByteBuffer емкостью 48 байт.
Скопируйте код кода следующим образом:
ByteBuffer buf = ByteBuffer.allocate(48);
При этом выделяется CharBuffer, который может хранить 1024 символа:
Скопируйте код кода следующим образом:
CharBuffer buf = CharBuffer.allocate(1024);
Запись данных в буфер
Есть два способа записи данных в Buffer:
Запись из канала в буфер.
Запись в Buffer через метод put() Buffer.
Пример записи из канала в буфер
Скопируйте код кода следующим образом:
int bytesRead = inChannel.read(buf); //читаем в буфер.
Пример записи Buffer через метод put:
Скопируйте код кода следующим образом:
буф.пут(127);
Существует множество версий метода put, позволяющих записывать данные в буфер разными способами. Например, запись в указанное место или запись массива байтов в буфер. Для получения более подробной информации о реализации Buffer обратитесь к JavaDoc.
метод флип()
Метод переворота переключает буфер из режима записи в режим чтения. Вызов методаlip() вернет позицию в 0 и установит предел значения предыдущей позиции.
Другими словами, позиция теперь используется для обозначения позиции чтения, а предел показывает, сколько байтов, символов и т. д. было записано ранее — сколько байтов, символов и т. д. можно прочитать сейчас.
Чтение данных из буфера
Есть два способа чтения данных из Buffer:
Считайте данные из буфера в канал.
Используйте метод get() для чтения данных из буфера.
Пример чтения данных из буфера в канал:
Скопируйте код кода следующим образом:
//читаем из буфера в канал.
int bytesWritten = inChannel.write(buf);
Пример использования метода get() для чтения данных из Buffer
Скопируйте код кода следующим образом:
байт aByte = buf.get();
Существует множество версий метода get, позволяющих читать данные из буфера разными способами. Например, чтение из указанной позиции или чтение данных из буфера в массив байтов. Для получения более подробной информации о реализации Buffer обратитесь к JavaDoc.
метод перемотки()
Buffer.rewind() возвращает позицию в 0, поэтому вы можете перечитать все данные в буфере. Предел остается неизменным и по-прежнему указывает, сколько элементов (байт, символ и т. д.) можно прочитать из буфера.
методы ясно() и компакт()
После того как данные в буфере прочитаны, буфер должен быть готов к повторной записи. Это можно сделать с помощью методов Clear() или Compact().
Если вызывается методclear(), позиция будет установлена обратно в 0, а предел будет установлен на значение емкости. Другими словами, буфер очищается. Данные в буфере не очищаются, но эти метки подсказывают нам, с чего начать запись данных в буфер.
Если в буфере есть непрочитанные данные и вы вызываете методclear(), данные будут «забыты», что означает, что больше не будет никаких маркеров, сообщающих вам, какие данные были прочитаны, а какие нет.
Если в буфере еще есть непрочитанные данные и они потребуются позже, но вы хотите сначала записать некоторые данные, используйте метод Compact().
Метод Compact() копирует все непрочитанные данные в начало буфера. Затем установите позицию сразу за последним непрочитанным элементом. Атрибут limit по-прежнему имеет значение емкости, как и методclear(). Теперь буфер готов к записи данных, но непрочитанные данные не будут перезаписаны.
методы mark() и сброс()
Вызвав метод Buffer.mark(), вы можете отметить определенную позицию в буфере. Позже вы сможете восстановить это положение, вызвав метод Buffer.reset(). Например:
Скопируйте код кода следующим образом:
буфер.марк();
//вызов буфера.get() пару раз, например, во время синтаксического анализа.
uffer.reset();//устанавливаем позицию обратно на отметку.
методы Equals() и CompareTo().
Вы можете использовать методы Equals() и CompareTo() для двух буферов.
равно()
Когда выполняются следующие условия, это означает, что два буфера равны:
Иметь одинаковый тип (byte, char, int и т. д.).
Количество оставшихся байтов, символов и т. д. в буфере одинаково.
Все оставшиеся байты, символы и т. д. в буфере остаются прежними.
Как видите, метод «равно» сравнивает только часть буфера, а не каждый его элемент. Фактически, он сравнивает только оставшиеся элементы в буфере.
метод CompareTo()
Метод CompareTo() сравнивает оставшиеся элементы (байт, символ и т. д.) двух буферов. Если выполняются следующие условия, один буфер считается «меньше», чем другой буфер:
Первый неравный элемент меньше соответствующего элемента в другом буфере.
Все элементы равны, но первый буфер исчерпывается раньше другого (в первом буфере меньше элементов, чем в другом).
(Аннотация: Остальные элементы — это элементы от позиции до предела)
Разброс/Собрать
(Оригинальный адрес этой части, автор: Якоб Дженков, переводчик: Го Лэй)
Java NIO начинает поддерживать разброс/сборку. Scatter/gather используется для описания операции чтения или записи в канал (Примечание переводчика: канал часто переводится как канал на китайском языке).
Разбросное чтение из канала означает запись считанных данных в несколько буферов во время операции чтения. Таким образом, Канал «разбрасывает» данные, считанные из Канала, в несколько Буферов.
Сбор и запись в канал означает запись данных из нескольких буферов в один и тот же канал во время операции записи. Таким образом, канал «собирает» данные в нескольких буферах и отправляет их в канал.
Scatter/gather часто используется в ситуациях, когда передаваемые данные необходимо обрабатывать отдельно. Например, при передаче сообщения, состоящего из заголовка и тела сообщения, вы можете разбросать тело сообщения и заголовок сообщения по разным буферам. вы можете удобно обрабатывать заголовки и тела сообщений.
Рассеивание чтений
Рассеянное чтение означает чтение данных из одного канала в несколько буферов. Как описано на рисунке ниже:
Пример кода выглядит следующим образом:
Скопируйте код кода следующим образом:
Заголовок ByteBuffer = ByteBuffer.allocate(128);
Тело ByteBuffer = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = {заголовок, тело};
канал.read(bufferArray);
Обратите внимание, что буфер сначала вставляется в массив, а затем массив используется в качестве входного параметра для Channel.read(). Метод read() записывает данные, считанные из канала, в буфер в порядке буфера в массиве. Когда один буфер заполнен, канал записывает в другой буфер.
Рассеянное чтение должно заполнить текущий буфер перед переходом к следующему буферу, что также означает, что оно не подходит для динамических сообщений (Примечание переводчика: размер сообщения не фиксирован). Другими словами, если есть заголовок и тело сообщения, заголовок сообщения должен быть полностью заполнен (например, 128 байт), чтобы рассеянное чтение работало правильно.
Сбор записей
Сбор операций записи означает, что данные записываются из нескольких буферов в один и тот же канал. Как описано на рисунке ниже:
Пример кода выглядит следующим образом:
Скопируйте код кода следующим образом:
Заголовок ByteBuffer = ByteBuffer.allocate(128);
Тело ByteBuffer = ByteBuffer.allocate(1024);
//записываем данные в буферы
ByteBuffer[] bufferArray = {заголовок, тело};
канал.write(bufferArray);
Массив буферов является входным параметром метода write(). Метод write() записывает данные в канал в порядке буферов в массиве. Обратите внимание, что будут записаны только данные между позицией и пределом. Следовательно, если буфер имеет емкость 128 байт, но содержит только 58 байт данных, то в канал будут записаны 58 байт данных. Таким образом, в отличие от рассеянного чтения, сбор и запись могут лучше обрабатывать динамические сообщения.
Передача данных между каналами
(Оригинальный адрес этой части, автор: Якоб Дженков, переводчик: Го Лэй, корректор: Чжоу Тай)
В Java NIO, если один из двух каналов является FileChannel, то вы можете напрямую передавать данные из одного канала (Примечание переводчика: канал часто переводится как канал на китайском языке) в другой канал.
передачаОт()
Метод TransferFrom() FileChannel может передавать данные из исходного канала в FileChannel (Примечание переводчика: в документации JDK этот метод объясняется как передача байтов из заданного читаемого байтового канала в файл этого канала.). Вот простой пример:
Скопируйте код кода следующим образом:
RandomAccessFile fromFile = новый RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = новый RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
длинная позиция = 0;
длинный счетчик = fromChannel.size();
toChannel.transferFrom(позиция, количество, fromChannel);
Позиция входного параметра метода указывает начало записи данных в целевой файл, а счетчик указывает максимальное количество переданных байтов. Если в исходном канале осталось менее count байтов, количество переданных байтов меньше количества запрошенных байтов.
Кроме того, следует отметить, что в реализации SoketChannel SocketChannel будет передавать только подготовленные на данный момент данные (которые могут быть меньше, чем count байт). Таким образом, SocketChannel может не передать все запрошенные данные (количество байтов) в FileChannel.
передачаTo()
Метод TransferTo() передает данные из FileChannel в другие каналы. Вот простой пример:
Скопируйте код кода следующим образом:
RandomAccessFile fromFile = новый RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = новый RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
длинная позиция = 0;
длинный счетчик = fromChannel.size();
fromChannel.transferTo(позиция, количество, toChannel);
Вы заметили, что этот пример особенно похож на предыдущий? За исключением того, что объект FileChannel, вызывающий метод, отличается, все остальное то же самое.
Проблемы, упомянутые выше в отношении SocketChannel, также существуют в методе TransferTo(). SocketChannel будет продолжать передавать данные до тех пор, пока целевой буфер не будет заполнен.
Селектор
(Ссылка на оригинальный текст этого раздела, автор: Якоб Женков, переводчик: Лангжив, корректор: Дин И)
Селектор — это компонент Java NIO, который может обнаружить один или несколько каналов NIO и узнать, готов ли канал к таким событиям, как чтение и запись. Таким образом, один поток может управлять несколькими каналами и, следовательно, несколькими сетевыми соединениями.
(1) Зачем использовать Селектор?
Преимущество использования только одного потока для обработки нескольких каналов заключается в том, что для обработки каналов требуется меньше потоков. Фактически для обработки всех каналов можно использовать только один поток. Для операционной системы переключение контекста между потоками очень затратно, и каждый поток занимает некоторые системные ресурсы (например, память). Поэтому чем меньше потоков используется, тем лучше.
Однако имейте в виду, что современные операционные системы и процессоры становятся все лучше и лучше в многозадачности, поэтому накладные расходы на многопоточность со временем становятся все меньше и меньше. Фактически, если процессор имеет несколько ядер, отказ от использования многозадачности может оказаться пустой тратой мощности процессора. В любом случае, обсуждение этой конструкции должно быть в другой статье. Здесь достаточно знать, что вы можете обрабатывать несколько каналов с помощью Selector.
Ниже приведен пример диаграммы одного потока, использующего селектор для обработки трех каналов:
(2) Создание селектора
Создайте селектор, вызвав метод Selector.open() следующим образом:
Скопируйте код кода следующим образом:
Селектор селектор = Selector.open();
(3) Зарегистрируйте канал в Selector.
Чтобы использовать «Канал» и «Селектор» вместе, канал должен быть зарегистрирован в селекторе. Это достигается с помощью метода SelectableChannel.register() следующим образом:
Скопируйте код кода следующим образом:
канал.configureBlocking(ложь);
Ключ SelectionKey = канал.регистр(селектор,
Selectionkey.OP_READ);
При использовании с селектором канал должен находиться в неблокирующем режиме. Это означает, что вы не можете использовать FileChannel с селектором, поскольку FileChannel нельзя переключить в неблокирующий режим. Сокет-каналы в порядке.
Обратите внимание на второй параметр метода Register(). Это «коллекция по интересам», то есть какие события вас интересуют при прослушивании Канала через Селектор. Существует четыре различных типа событий, которые можно прослушивать:
Соединять
Принимать
Читать
Писать
Канал, запускающий событие, означает, что событие готово. Поэтому канал, который успешно подключается к другому серверу, называется «готовым к соединению». Говорят, что канал сокета сервера «готов к приему», когда он готов принимать входящие соединения. Канал, по которому есть данные для чтения, называется «готовым к чтению». Канал, ожидающий записи данных, можно назвать «готовым к записи».
Эти четыре события представлены четырьмя константами SelectionKey:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
Если вас интересует более одного события, вы можете использовать побитовый оператор ИЛИ для соединения констант следующим образом:
Скопируйте код кода следующим образом:
intinterestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
Коллекции процентов будут упомянуты ниже.
(4)Клавиша выбора
В предыдущем разделе при регистрации канала с помощью селектора метод Register() возвращает объект SelectionKey. Этот объект содержит некоторые свойства, которые могут вас заинтересовать:
сбор процентов
готовая коллекция
Канал
Селектор
Дополнительные объекты (по желанию)
Ниже я опишу эти свойства.
сбор процентов
Как описано в разделе «Регистрация канала с помощью селектора», коллекция интересов — это коллекция интересных событий, которые вы выбираете. Вы можете читать и записывать коллекцию процентов через SelectionKey, вот так:
Скопируйте код кода следующим образом:
intinterestSet = choiceKey.interest();
логическое значение isInterestedInAccept= (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
логическое значение isInterestedInConnect = InterestSet & SelectionKey.OP_CONNECT;
логическое значение isInterestedInRead = InterestSet & SelectionKey.OP_READ;
логическое значение isInterestedInWrite =interestSet и SelectionKey.OP_WRITE;
Видно, что, используя «бит И» для управления коллекцией интересов и заданную константу SelectionKey, вы можете определить, находится ли определенное событие в коллекции интересов.
готовая коллекция
Готовый набор — это набор операций, к которым канал готов. После выбора (Selection) вы сначала получите доступ к готовому набору. Выбор будет описан в следующем разделе. Доступ к готовой коллекции можно получить так:
int ReadySet = SelectionKey.readyOps();
Вы можете использовать тот же метод, что и при обнаружении коллекции интересов, чтобы определить, какие события или операции готовы в канале. Однако также доступны следующие четыре метода, каждый из которых возвращает логический тип:
Скопируйте код кода следующим образом:
SelectionKey.isAcceptable();
SelectionKey.isConnectable();
SelectionKey.isReadable();
SelectionKey.isWritable();
Канал+Селектор
Доступ к каналу и селектору из SelectionKey прост. следующее:
Скопируйте код кода следующим образом:
Каналканал=selectionKey.channel();
Селектор селектор = choiceKey.selector();
дополнительные объекты
К SelectionKey можно прикрепить объект или дополнительную информацию, чтобы легко идентифицировать данный канал. Например, вы можете подключить буфер для использования с каналом или объектом, содержащим агрегированные данные. Как его использовать:
Скопируйте код кода следующим образом:
SelectionKey.attach(Объект);
Объект AttachedObj = SelectionKey.attachment();
Вы также можете прикреплять объекты при регистрации канала в селекторе с помощью метода Register(). нравиться:
Скопируйте код кода следующим образом:
Ключ SelectionKey = канал.регистр(селектор, SelectionKey.OP_READ, theObject);
(5) Выберите канал с помощью селектора.
Как только один или несколько каналов зарегистрированы в селекторе, можно вызвать несколько перегруженных методов select(). Эти методы возвращают те каналы, которые готовы к интересующему вас событию (например, подключение, принятие, чтение или запись). Другими словами, если вас интересуют «готовые к чтению» каналы, метод select() вернет те каналы, для которых готовы события чтения.
Вот метод select():
интервал выбора()
int select (длинный тайм-аут)
интервал выбораNow()
select() блокируется до тех пор, пока хотя бы один канал не будет готов к зарегистрированному вами событию.
select(long timeout) аналогичен select(), за исключением того, что он блокируется на время ожидания до миллисекунд (параметр).
selectNow() не блокируется и немедленно возвращает значение независимо от того, какой канал готов (Примечание переводчика: этот метод выполняет неблокирующую операцию выбора. Если ни один канал не становится доступным для выбора после предыдущей операции выбора, этот метод напрямую возвращает ноль.).
Значение int, возвращаемое методом select(), указывает, сколько каналов готово. То есть, сколько каналов стало готовым с момента последнего вызова метода select(). Если вызывается метод select(), возвращается 1, потому что один канал становится готовым. Если метод select() вызывается снова, если другой канал готов, он снова вернет 1. Если на первом готовом канале не выполняются никакие операции, то теперь есть два готовых канала, но между каждым вызовом метода select() готов только один канал.
выбранныеКлючи()
После вызова метода select() и возвращаемого значения, указывающего, что один или несколько каналов готовы, к готовым каналам в «выбранном наборе ключей» можно получить доступ, вызвав метод selectedKeys() селектора. Как показано ниже:
Скопируйте код кода следующим образом:
Установите selectedKeys = selector.selectedKeys();
При регистрации канала как селектора метод Channel.register() возвращает объект SelectionKey. Этот объект представляет канал, зарегистрированный в селекторе. Доступ к этим объектам можно получить через метод selectedKeySet() класса SelectionKey.
Доступ к готовым каналам можно получить, пройдя по выбранному набору клавиш. следующее:
Скопируйте код кода следующим образом:
Установите selectedKeys = selector.selectedKeys();
Итератор keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
Ключ SelectionKey = keyIterator.next();
если (key.isAcceptable()) {
// соединение было принято ServerSocketChannel.
} еще если (key.isConnectable()) {
// установлено соединение с удаленным сервером.
} еще если (key.isReadable()) {
//канал готов к чтению
} еще если (key.isWritable()) {
//канал готов к записи
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">удалить</a ></tuihighlight>();
}
Этот цикл перебирает каждую клавишу в выбранном наборе ключей и обнаруживает событие готовности для канала, соответствующего каждой клавише.
Обратите внимание на вызов keyIterator.remove() в конце каждой итерации. Селектор не удаляет экземпляры SelectionKey из выбранного набора ключей. Необходимо удалить самостоятельно при обработке канала. В следующий раз, когда канал станет готовым, Селектор снова поместит его в выбранный набор ключей.
Канал, возвращаемый методом SelectionKey.channel(), необходимо преобразовать в тип, который вы хотите обработать, например ServerSocketChannel или SocketChannel и т. д.
(6) пробуждение()
Поток блокируется после вызова метода select(). Даже если ни один канал не готов, существует способ вернуть его из метода select(). Просто позвольте другим потокам вызывать метод Selector.wakeup() для объекта, где первый поток вызвал метод select(). Поток, заблокированный методом select(), вернется немедленно.
Если другой поток вызывает метод пробуждения(), но в данный момент ни один поток не заблокирован для метода select(), следующий поток, вызывающий метод select(), немедленно «проснется».
(7)закрыть()
Вызов метода close() после использования Selector закроет Selector и сделает недействительными все экземпляры SelectionKey, зарегистрированные в Selector. Сам канал не закрывается.
(8) Полный пример
Вот полный пример: откройте Селектор, зарегистрируйте канал в Селекторе (процесс инициализации канала опущен), а затем постоянно отслеживайте, готовы ли четыре события Селектора (принятие, подключение, чтение, запись).
Скопируйте код кода следующим образом:
Селектор селектор = Selector.open();
канал.configureBlocking(ложь);
Ключ SelectionKey = канал.регистр(селектор, SelectionKey.OP_READ);
в то время как (истина) {
int ReadyChannels = selector.select();
если (readyChannels == 0) продолжить;
Установите selectedKeys = selector.selectedKeys();
Итератор keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
Ключ SelectionKey = keyIterator.next();
если (key.isAcceptable()) {
// соединение было принято ServerSocketChannel.
} еще если (key.isConnectable()) {
// установлено соединение с удаленным сервером.
} еще если (key.isReadable()) {
//канал готов к чтению
} еще если (key.isWritable()) {
//канал готов к записи
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">удалить</a ></tuihighlight>();
}
}
файловый канал
(Ссылка на оригинальный текст этого раздела, автор: Якоб Дженков, переводчик: Чжоу Тай, корректор: Дин И)
FileChannel в Java NIO — это канал, подключенный к файлу. Файлы можно читать и записывать через файловые каналы.
FileChannel не может быть переведен в неблокирующий режим, он всегда работает в блокирующем режиме.
Опенфилеканал
Перед использованием FileChannel его необходимо открыть. Однако мы не можем открыть FileChannel напрямую. Нам нужно получить экземпляр FileChannel, используя InputStream, OutputStream или RandomAccessFile. Вот пример открытия FileChannel через RandomAccessFile:
Скопируйте код кода следующим образом:
RandomAccessFile aFile = новый RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
Чтение данных из FileChannel
Вызовите один из нескольких методов read(), чтобы прочитать данные из FileChannel. нравиться:
Скопируйте код кода следующим образом:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
Сначала выделите буфер. Данные, считанные из FileChannel, будут считаны в Buffer.
Затем вызовите метод FileChannel.read(). Этот метод считывает данные из FileChannel в Buffer. Значение int, возвращаемое методом read(), указывает, сколько байтов было прочитано в буфер. Если он возвращает -1, это означает, что достигнут конец файла.
Запись данных в FileChannel
Используйте метод FileChannel.write() для записи данных в FileChannel. Параметром этого метода является буфер. нравиться:
Скопируйте код кода следующим образом:
String newData = "Новая строка для записи в файл..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
буф.очистить();
buf.put(newData.getBytes());
буф.флип();
while(buf.hasRemaining()) {
канал.write(buf);
}
Обратите внимание, что FileChannel.write() вызывается в цикле while. Поскольку нет никакой гарантии, сколько байтов метод write() может записать в FileChannel за один раз, метод write() необходимо вызывать повторно до тех пор, пока в буфере не останется байтов, которые не были записаны в канал.
ЗакрытьФайлКанал
FileChannel должен быть закрыт, когда вы закончите с ним. нравиться:
Скопируйте код кода следующим образом:
канал.закрыть();
Метод позиции FileChannel
Иногда может потребоваться чтение/запись данных в определенном месте FileChannel. Вы можете получить текущую позицию FileChannel, вызвав метод Position().
Вы также можете установить текущую позицию FileChannel, вызвав метод Position(long pos).
Вот два примера:
Скопируйте код кода следующим образом:
длинный pos = канал.позиция();
канал.позиция(поз. +123);
Если вы установите позицию после конца файла, а затем попытаетесь прочитать данные из файлового канала, метод чтения вернет -1 — флаг конца файла.
Если вы установите позицию после конца файла, а затем запишите данные в канал, файл будет расширен до текущей позиции и данные будут записаны. Это может привести к «файловым дырам», разрывам между данными, записанными в физических файлах на диске.
Метод размера FileChannel
Метод size() экземпляра FileChannel вернет размер файла, связанного с этим экземпляром. нравиться:
Скопируйте код кода следующим образом:
длинный fileSize = Channel.size();
Метод усечения FileChannel
Вы можете использовать метод FileChannel.truncate() для перехвата файла. При перехвате файла будет удалена часть после указанной длины файла. нравиться:
Скопируйте код кода следующим образом:
канал.truncate(1024);
В этом примере перехватываются первые 1024 байта файла.
Принудительный метод FileChannel
Метод FileChannel.force() принудительно помещает в канал данные, которые еще не были записаны с диска на диск. По соображениям производительности операционная система кэширует данные в памяти, поэтому нет гарантии, что данные, записанные в FileChannel, будут немедленно записаны на диск. Для этого необходимо вызвать метод Force().
Метод Force() имеет логический параметр, который указывает, следует ли одновременно записывать метаданные файла (информацию о разрешениях и т. д.) на диск.
В следующем примере данные файла и метаданные принудительно сохраняются на диске:
Скопируйте код кода следующим образом:
канал.force(истина);
Розеточный канал
(Ссылка на оригинальный текст этого раздела, автор: Якоб Женков, переводчик: Чжэн Юйтин, корректор: Дин И)
SocketChannel в Java NIO — это канал, подключенный к сетевому сокету TCP. SocketChannel можно создать двумя способами:
Откройте SocketChannel и подключитесь к серверу в Интернете.
Когда новое соединение поступает в ServerSocketChannel, создается SocketChannel.
Открыть канал сокета
Ниже описано, как открыть SocketChannel:
Скопируйте код кода следующим образом:
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
Закрыть канал сокета
Когда вы закончите работу с SocketChannel, вызовите SocketChannel.close(), чтобы закрыть SocketChannel:
Скопируйте код кода следующим образом:
сокетКанал.закрыть();
Чтение данных из SocketChannel
Чтобы прочитать данные из SocketChannel, вызовите один из методов read(). Вот примеры:
Скопируйте код кода следующим образом:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
Сначала выделите буфер. Данные, считанные из SocketChannel, будут помещены в этот буфер.
Затем вызовите SocketChannel.read(). Этот метод считывает данные из SocketChannel в Buffer. Значение int, возвращаемое методом read(), указывает, сколько байтов было прочитано в буфер. Если возвращается -1, это означает, что конец потока прочитан (соединение закрыто).
Запись в SocketChannel
Для записи данных в SocketChannel используется метод SocketChannel.write(), который принимает в качестве параметра Buffer. Примеры следующие:
Скопируйте код кода следующим образом:
String newData = "Новая строка для записи в файл..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
буф.очистить();
buf.put(newData.getBytes());
буф.флип();
while(buf.hasRemaining()) {
канал.write(buf);
}
Обратите внимание, что метод SocketChannel.write() вызывается в цикле while. Метод Write() не может гарантировать, сколько байтов можно записать в SocketChannel. Итак, мы вызываем write() несколько раз, пока в буфере не останется байтов для записи.
неблокирующий режим
Вы можете установить SocketChannel в неблокирующий режим. После установки вы можете вызывать метод Connect(), read() и write() в асинхронном режиме.
соединять()
Если SocketChannel находится в неблокирующем режиме и в это время вызывается метод Connect(), метод может завершиться до того, как соединение будет установлено. Чтобы определить, установлено ли соединение, вы можете вызвать метод FinishConnect(). Так:
Скопируйте код кода следующим образом:
сокетChannel.configureBlocking (ложь);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(!socketChannel.finishConnect() ){
//подожди или сделай что-нибудь еще...
}
писать()
В неблокирующем режиме метод write() может вернуться перед записью чего-либо. Поэтому write() необходимо вызывать в цикле. Примеры уже были, поэтому я не буду здесь вдаваться в подробности.
читать()
В неблокирующем режиме метод read() может вернуться до того, как будут прочитаны какие-либо данные. Поэтому вам нужно обратить внимание на возвращаемое значение int, которое сообщит вам, сколько байтов было прочитано.
Неблокирующий режим и селекторы
Неблокирующий режим лучше работает с селекторами. Зарегистрировав один или несколько каналов SocketChannel в селекторе, вы можете спросить селектор, какой канал готов для чтения, записи и т. д. Комбинация Selector и SocketChannel будет подробно обсуждаться позже.
Канал ServerSocket
(Ссылка на оригинальный текст этого раздела, автор: Якоб Женков, переводчик: Чжэн Юйтин, корректор: Дин И)
ServerSocketChannel в Java NIO — это канал, который может прослушивать новые входящие TCP-соединения, точно так же, как ServerSocket в стандартном вводе-выводе. Класс ServerSocketChannel находится в пакете java.nio.channels.
Вот пример:
Скопируйте код кода следующим образом:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(новый InetSocketAddress(9999));
в то время как (правда) {
SocketChannel сокетChannel =
serverSocketChannel.accept();
// делаем что-то с сокетом Channel...
}
Открыть серверсокетканал
Откройте ServerSocketChannel, вызвав метод ServerSocketChannel.open(). Например:
Скопируйте код кода следующим образом:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Закрыть серверсокетканал
Закройте ServerSocketChannel, вызвав метод ServerSocketChannel.close(). Например:
Скопируйте код кода следующим образом:
serverSocketChannel.close();
Слушайте новые входящие соединения
Прослушивайте новые входящие соединения с помощью метода ServerSocketChannel.accept(). Когда метод Accept() завершает работу, он возвращает SocketChannel, содержащий новое входящее соединение. Таким образом, метод Accept() будет блокироваться до тех пор, пока не появится новое соединение.
Обычно вместо того, чтобы просто прослушивать одно соединение, метод Accept() вызывается в цикле while, как в следующем примере:
Скопируйте код кода следующим образом:
в то время как (правда) {
SocketChannel сокетChannel =
serverSocketChannel.accept();
// делаем что-то с сокетом Channel...
}
Конечно, в цикле while вы также можете использовать другие критерии выхода, кроме true.
неблокирующий режим
ServerSocketChannel можно установить в неблокирующий режим. В неблокирующем режиме метод Accept() возвращает значение немедленно. Если нет нового входящего соединения, возвращаемое значение будет нулевым. Поэтому вам необходимо проверить, имеет ли возвращаемый SocketChannel значение null. нравиться:
Скопируйте код кода следующим образом:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(новый InetSocketAddress(9999));
serverSocketChannel.configureBlocking (ложь);
в то время как (правда) {
SocketChannel сокетChannel =
serverSocketChannel.accept();
если (socketChannel! = ноль) {
// делаем что-то с сокетом Channel...
}
}
Датаграммный канал
(Ссылка на оригинальный текст этого раздела, автор: Якоб Женков, переводчик: Чжэн Юйтин, корректор: Дин И)
DatagramChannel в Java NIO — это канал, который может отправлять и получать UDP-пакеты. Поскольку UDP — это сетевой протокол без установления соединения, его нельзя читать и записывать, как другие каналы. Он отправляет и получает пакеты данных.
OpenDatagramChannel
Вот как открывается DatagramChannel:
Скопируйте код кода следующим образом:
Канал DatagramChannel = DatagramChannel.open();
Channel.socket().bind(новый InetSocketAddress(9999));
DatagramChannel, открытый в этом примере, может принимать пакеты через UDP-порт 9999.
получать данные
Получите данные из DatagramChannel через метод получения(), например:
Скопируйте код кода следующим образом:
ByteBuffer buf = ByteBuffer.allocate(48);
буф.очистить();
канал.получить(буф);
Метод получения() скопирует содержимое полученного пакета данных в указанный буфер. Если буфер не может вместить полученные данные, лишние данные будут отброшены.
Отправить данные
Отправьте данные из DatagramChannel через метод send(), например:
Скопируйте код кода следующим образом:
String newData = "Новая строка для записи в файл..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
буф.очистить();
buf.put(newData.getBytes());
буф.флип();
int bytesSent =channel.send(buf, new InetSocketAddress("jenkov.com", 80));
В этом примере строка символов отправляется на UDP-порт 80 сервера jenkov.com. Поскольку сервер не контролирует этот порт, ничего не произойдет. Он также не уведомит вас о том, был ли получен исходящий пакет, поскольку UDP не имеет никаких гарантий с точки зрения доставки данных.
Подключиться к определенному адресу
DatagramChannel может быть «подключен» к определенному адресу в сети. Поскольку UDP не поддерживает соединение, подключение к определенному адресу не создает реального соединения, такого как канал TCP. Вместо этого DatagramChannel заблокирован, поэтому он может отправлять и получать данные только с определенного адреса.
Вот пример:
Скопируйте код кода следующим образом:
Channel.connect(новый InetSocketAddress("jenkov.com", 80));
После подключения вы также можете использовать методы read() и write(), как и в традиционном канале. Никаких гарантий относительно передачи данных просто нет. Вот несколько примеров:
Скопируйте код кода следующим образом:
int bytesRead = Channel.read(buf);
int bytesWritten = Channel.write(но);
Трубка
(Ссылка на оригинальный текст этого раздела, автор: Якоб Дженков, переводчик: Хуан Чжун, корректор: Дин И)
Канал Java NIO — это одностороннее соединение данных между двумя потоками. Труба имеет канал источника и канал стока. Данные будут записываться в канал приемника и считываться из канала источника.
Вот иллюстрация принципа Pipe:
Создать конвейер
Откройте канал с помощью метода Pipe.open(). Например:
Скопируйте код кода следующим образом:
Труба труба = Pipe.open();
Запись данных в канал
Чтобы записать данные в канал, вам необходимо получить доступ к каналу приемника. Так:
Скопируйте код кода следующим образом:
Pipe.SinkChannel раковинаChannel = труба.sink();
Запишите данные в SinkChannel, вызвав метод write() класса SinkChannel, например:
Скопируйте код кода следующим образом:
String newData = "Новая строка для записи в файл..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
буф.очистить();
buf.put(newData.getBytes());
буф.флип();
while(buf.hasRemaining()) {
<b>sinkChannel.write(buf);</b>
}
[код]
Чтение данных из трубы
Чтобы прочитать данные из канала, вам необходимо получить доступ к исходному каналу, например:
[код]
Pipe.SourceChannel sourceChannel = Pipe.source();
Вызовите метод read() исходного канала, чтобы прочитать данные, например:
Скопируйте код кода следующим образом:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
Значение int, возвращаемое методом read(), сообщит нам, сколько байтов было прочитано в буфер.