В прошлой статье говорилось о пяти самых основных моделях ввода-вывода. Я считаю, что у каждого должно быть определенное понимание концепций, связанных с вводом-выводом. В этой статье в основном говорится о Java NIO на основе мультиплексированного ввода-вывода.
задний план
С момента рождения Java существует множество моделей ввода-вывода, от самой ранней Java IO до более поздней версии Java NIO и последней версии Java AIO. Каждая модель ввода-вывода имеет свои особенности. Подробную информацию см. в моей предыдущей статье.Предварительное изучение Java IO, и Java NIO среди них очень широко используется, особенно в области высокой параллелизма, например, наши общие Netty, Mina и другие фреймворки основаны на нем, я думаю, все что-то знают, давайте взглянем на специфическую структуру Java NIO.
Архитектура Java NIO
На самом деле модель Java NIO относительно проста, она имеет три основных ядра: селектор, канал и буфер, давайте посмотрим на взаимосвязь между ними:
Отношения между ними очень четкие.Один поток соответствует одному селектору, один селектор соответствует нескольким каналам, а один канал соответствует одному буферу.Конечно, это обычная практика.Один канал также может соответствовать нескольким селекторам, и один канал соответствует нескольким буферам.
Selector
Лично я считаю, что Selector — самая большая функция Java NIO. Как мы уже говорили, традиционный ввод-вывод Java бессилен перед лицом большого количества запросов ввода-вывода, поскольку для каждого запроса ввода-вывода обслуживания требуется поток. Проблема в том, что системные ресурсы чрезвычайно потребляется, пропускная способность резко падает, вызывая проблемы, связанные с системой, так как же Java NIO решает эту проблему? Ответ: Селектор. Короче говоря, он соответствует контролирующей роли в мультиплексировании мультиплексирования ввода-вывода. Он отвечает за унифицированное управление запросами ввода-вывода, мониторинг соответствующих событий ввода-вывода и уведомление соответствующих потоков для обработки. В этом режиме нет необходимости в каждый запрос ввода-вывода выделяет поток отдельно, что также уменьшает большое количество блокирующих потоков и снижение использования ресурсов.Поэтому Selector является сущностью Java NIO.На Java мы можем написать:
// 打开服务器套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 服务器配置为非阻塞
ssc.configureBlocking(false);
// 进行服务的绑定
ssc.bind(new InetSocketAddress("localhost", 8001));
// 通过open()方法找到Selector
Selector selector = Selector.open();
// 注册到selector,等待连接
ssc.register(selector, SelectionKey.OP_ACCEPT);
...
Channel
Первоначальное значение Channel — это значение канала, короче говоря, он представляет собой канал данных в Java NIO, но у этого канала есть особенность, то есть он двунаправленный, то есть мы можем получать данные из канала, и также Вы можете записывать данные в канал, в отличие от Java BIO, для чтения данных и записи данных требуются разные каналы данных, такие как наиболее распространенный поток ввода и поток вывода, но все они однонаправленные.Как новый дизайн, канал помогает системе поддерживать обработка IO-запроса передачи данных при относительно небольших затратах, но он реально не хранит данные.Всегда используется совместно с буферной областью (Buffer).Кроме того, существует четыре основных типа каналов:
- FileChannel: канал, используемый при чтении и записи файлов.
- DatagramChannel: канал для передачи данных соединения UDP, соответствующий DatagramSocket в Java IO.
- SocketChannel: канал для передачи данных TCP-соединения, соответствующий Socket в Java IO.
- ServersocketChannel: прослушивание канала при подключении сокета соответствует serversocket в Java IO.
Конечно, наиболее важными и наиболее часто используемыми являются SocketChannel и ServerSocketChannel, которые также являются сущностью Java NIO.ServerSocketChannel можно установить в неблокирующий режим, а затем объединить с Selector, можно реализовать мультиплексирование ввода-вывода, и один поток может использоваться для управления несколькими соединениями Socket.Вышеприведенный код может быть параметризован.
Buffer
Как следует из названия, буфер означает буфер. Его основная функция в Java NIO — служить буферной областью для данных. Буфер соответствует определенному каналу, считывая данные из канала или записывая данные в канал. Буфер очень похож на массив. Но он предоставляет больше возможностей, чтобы облегчить нам работу с данными в буфере.Позже я в основном проанализирую его три атрибута емкость, положение и предел.Давайте сначала посмотрим на категорию распределения буфера (это не относится к конкретным данным типы буфера) — это прямой буфер и буфер кучи, так почему же существует два типа буфера? Сначала рассмотрим их особенности:
Прямой буфер:
- Непосредственно размещается в системной памяти;
- Нет необходимости нести затраты на копирование базы данных из памяти в память Java;
- Хотя Прямой Буфер размещается непосредственно в системной памяти, при его повторном использовании в реальную память будет загружаться только та страница данных, которая действительно нуждается в данных, а остальные по-прежнему существуют в виртуальной памяти, что не вызовет трата ресурсов фактической памяти;
- В сочетании со специальным машинным кодом многобайтовые блоки могут считываться последовательно за раз;
- Поскольку он размещается непосредственно в системной памяти, он не управляется сборщиком мусора Java и не будет автоматически переработан;
- Стоимость создания и разрушения относительно высока;
Буфер кучи:
- Распределенный в куче Java, жизненный цикл управляется сборщиком мусора Java, и дополнительное обслуживание не требуется;
- Относительно низкая стоимость создания;
В соответствии с их характеристиками мы можем примерно обобщить их применимые сценарии:
Если этот буфер можно использовать повторно, и вы также хотите работать с несколькими байтами или у вас высокие требования к производительности, вы можете выбрать использование прямого буфера, но его кодирование будет относительно сложным, и есть другие моменты, на которые следует обратить внимание. Наоборот, используйте Heap Buffer, соответствующий метод создания Buffer:
//创建Heap Buffer
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
//创建Direct Buffer
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
Давайте посмотрим на его три свойства:
- Емкость: как следует из названия, его значение - емкость, которая представляет максимальную емкость буфера, что очень похоже на размер массива.Инициализация не может быть изменена, если вы не измените структуру буфера;
- Ограничение: как следует из названия, его значение - это предел, который представляет собой текущий максимальный предел буфера.В режиме записи предел обычно равен емкости.В режиме чтения вам необходимо контролировать его значение и считывать нужные данные в комбинации с положением;
- Позиция: как следует из названия, его значение — позиция, представляющая позицию текущей операции буфера, это начальная позиция при следующей операции, которую вы выполняете в следующий раз;
Далее я буду использовать диаграмму, чтобы помочь вам понять. Теперь мы предполагаем, что есть буфер вместимостью 10. Мы впервые написали в нем определенный байт данных, а затем прочитайте данные, которые нам нужны от него в соответствии с кодировкой правила:
1. Начальный буфер:
ByteBuffer buffer = ByteBuffer.allocate(10);
2. Запишите два байта в буфер:
buffer.put("my".getBytes());
3. Запишите в буфер четыре байта:
buffer.put("blog".getBytes());
4. Теперь нам нужно получить данные из Буфера, для начала сначала конвертируем режим записи в режим чтения:
buffer.flip();
Давайте посмотрим, что делает метод flip().
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
Как видно из исходного кода, метод flip изменяет соответствующие атрибуты в соответствии с текущими соответствующими атрибутами буфера, поэтому после метода flip() текущее состояние буфера:
5. Затем читаем данные из Буфера
Существует множество способов чтения данных из Buffer, например, get(), get(byte []) и т. д. Конкретные методы см. в официальной документации по API Buffer. Здесь мы используем простейший метод get() для получения данных. :
byte a = buffer.get();
byte b = buffer.get();
В это время состояние буфера показано на следующем рисунке:
Таким образом мы можем прочитать нужные нам данные и, наконец, вызвать метод clear(), чтобы установить буфер в начальное состояние.
Суммировать
В этой статье в основном объясняются три важных компонента Java NIO, которые также более важны в процессе фактического использования.Освоение взаимосвязей между ними может сделать вас более знакомым со всей архитектурой Java NIO, и ваше понимание будет относительно более глубоким. глубину и проанализировать, как этот шаблон отображается на мультиплексирующую модель ввода-вывода, и понять причины преимуществ Java NIO в сценариях с высокой степенью параллелизма.