NIO Java IO

Java задняя часть база данных Архитектура

В прошлой статье говорилось о пяти самых основных моделях ввода-вывода. Я считаю, что у каждого должно быть определенное понимание концепций, связанных с вводом-выводом. В этой статье в основном говорится о Java NIO на основе мультиплексированного ввода-вывода.

задний план

С момента рождения Java существует множество моделей ввода-вывода, от самой ранней Java IO до более поздней версии Java NIO и последней версии Java AIO. Каждая модель ввода-вывода имеет свои особенности. Подробную информацию см. в моей предыдущей статье.Предварительное изучение Java IO, и Java NIO среди них очень широко используется, особенно в области высокой параллелизма, например, наши общие Netty, Mina и другие фреймворки основаны на нем, я думаю, все что-то знают, давайте взглянем на специфическую структуру Java NIO.

Архитектура 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);

init-buffer

2. Запишите два байта в буфер:

buffer.put("my".getBytes());

write-buffer-1

3. Запишите в буфер четыре байта:

buffer.put("blog".getBytes());

write-buffer-2

4. Теперь нам нужно получить данные из Буфера, для начала сначала конвертируем режим записи в режим чтения:

  buffer.flip();

Давайте посмотрим, что делает метод flip().

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

Как видно из исходного кода, метод flip изменяет соответствующие атрибуты в соответствии с текущими соответствующими атрибутами буфера, поэтому после метода flip() текущее состояние буфера:

read-buffer

5. Затем читаем данные из Буфера

Существует множество способов чтения данных из Buffer, например, get(), get(byte []) и т. д. Конкретные методы см. в официальной документации по API Buffer. Здесь мы используем простейший метод get() для получения данных. :

  byte a = buffer.get();
  byte b = buffer.get();

В это время состояние буфера показано на следующем рисунке:

read-buffer-2

Таким образом мы можем прочитать нужные нам данные и, наконец, вызвать метод clear(), чтобы установить буфер в начальное состояние.

Суммировать

В этой статье в основном объясняются три важных компонента Java NIO, которые также более важны в процессе фактического использования.Освоение взаимосвязей между ними может сделать вас более знакомым со всей архитектурой Java NIO, и ваше понимание будет относительно более глубоким. глубину и проанализировать, как этот шаблон отображается на мультиплексирующую модель ввода-вывода, и понять причины преимуществ Java NIO в сценариях с высокой степенью параллелизма.