моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Описывает базовые знания NIO на уровне ava для базового обзора.
В Java NIO (неблокирующий ввод-вывод или новый ввод-вывод) — это новый набор API операций ввода-вывода, представленный в Java SE 1.4 и последующих версиях.
По сравнению с традиционной моделью ввода-вывода она обеспечивает более высокую эффективность и лучшие возможности одновременной обработки. Ключевой особенностью NIO является его неблокирующая функция, которая позволяет одному потоку управлять несколькими каналами ввода-вывода, тем самым значительно повышая производительность приложений в сценариях с высоким уровнем параллелизма.
Сценарий использования Java NIO особенно подходит для ситуаций, когда необходимо обрабатывать большое количество одновременных соединений. Например, на сетевом сервере один поток может управлять тысячами клиентских подключений без необходимости выделять независимый поток для каждого соединения. Может значительно снизить потребление системных ресурсов и улучшить возможности обработки.
Канал — это среда потока данных в Java NIO. Он является двунаправленным и может использоваться для чтения или записи данных. Основное преимущество каналов заключается в том, что они неблокируются, а это означает, что один поток может управлять несколькими каналами. Когда на канале не происходит никаких событий, поток не блокируется и вместо этого может выполнять другие задачи. К основным типам каналов относятся:
FileChannel
: используется для операций чтения и записи файла. Его можно использовать для записи данных из буфера в файл или для чтения данных из файла в буфер.SocketChannel
: используется для TCP-соединений в сетевых коммуникациях и может использоваться для чтения и записи данных.ServerSocketChannel
: используется для приема новых соединений SocketChannel, аналогично традиционному ServerSocket.DatagramChannel
: используется для связи UDP, которая может отправлять и получать дейтаграммы.Буфер — это объект, используемый для хранения данных в NIO. Это массив байтов, который может записывать и считывать данные из него. Буфер имеет определенную емкость и два важных свойства: позицию и предел.
Основные типы буферов:ByteBuffer
、CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
иDoubleBuffer
, каждый тип соответствует примитивному типу данных.
Селектор — это мультиплексор в NIO, который позволяет одному потоку отслеживать события из нескольких каналов, такие как чтение, запись, подключение и получение событий. Селектор уведомляет приложение, когда один из каналов готов к операциям ввода-вывода. Использование селекторов значительно улучшает возможности параллельной обработки сетевых приложений, поскольку нет необходимости создавать поток для каждого соединения.
К основным методам селекторов относятся:
select()
: Блокируется до тех пор, пока хотя бы один канал не будет готов к операциям ввода-вывода.selectedKeys()
: Возвращает набор, содержащий объекты SelectionKey всех подготовленных каналов.wakeup()
: Прерывание заблокированоselect()
передача.SelectionKey
Это связь между селекторами и каналами. Она представляет статус регистрации канала в селекторе, включая каналы, селекторы, коллекции заинтересованных событий и готовые коллекции событий.
Это наиболее традиционная модель ввода-вывода. В Java традиционные API-интерфейсы Socket и ServerSocket основаны на блокировке ввода-вывода. В этом режиме, когда поток инициирует операцию чтения или записи, он блокируется до завершения операции ввода-вывода. Если для операции чтения нет данных для чтения или операция записи не может быть завершена немедленно, поток будет ждать завершения операции.
Функции:
Неблокирующий ввод-вывод является частью платформы Java NIO (новый ввод-вывод), которая позволяет потокам инициировать операции чтения и записи без блокировки. Если для операции чтения нет данных для чтения или операция записи не может быть завершена немедленно, поток не будет приостановлен и сможет продолжать выполнять другие задачи.
Функции:
Мультиплексирование заключается в одновременном контроле нескольких файловых дескрипторов через один поток и работе с дескриптором только тогда, когда он готов (обычно это означает, что данные доступны для чтения или буфер записи доступен для записи). Селекторы используются в Java для реализации мультиплексирования.
Функции:
Уведомление:
В практических приложениях неблокирующий ввод-вывод часто используется в сочетании с мультиплексированием. Например, сервер может использовать селектор для мониторинга нескольких SocketChannel. Если в SocketChannel есть данные, которые можно читать или записывать, селектор уведомит сервер, и сервер будет обрабатывать этот конкретный SocketChannel неблокирующим способом.
В Java Stream и Channel — это два разных способа обработки потоков данных. Они принадлежат стандартной библиотеке ввода-вывода Java и библиотеке NIO соответственно. Вот подробное сравнение двух концепций:
Stream является частью стандартной модели ввода-вывода Java (блокирующий ввод-вывод), которая обеспечивает способ последовательного чтения и записи данных. Поток разделен на два типа: InputStream и OutputStream, которые используются для чтения и записи данных соответственно.Эти потоки могут быть потоками байтов (например,InputStream
, OutputStream
) или поток символов (например,Reader
, Writer
)。
Функции:
close()
метод освобождения ресурсов.Начиная с Java 7, оператор try-with-resources может автоматически закрывать реализацию.AutoCloseable
Ресурсы интерфейса, включая Stream. Channel является частью модели Java NIO (New IO), которая обеспечивает более высокий уровень абстракции, чем Stream, что позволяет более эффективно обрабатывать данные. Каналы могут быть двунаправленными, то есть их можно использовать для чтения и записи данных.Основные типы каналов включают в себяFileChannel
、SocketChannel
、ServerSocketChannel
иDatagramChannel
。
Функции:
Подведем итог
В Java модели операций ввода-вывода можно классифицировать по двум измерениям: синхронный/асинхронный и блокирующий/неблокирующий.
определение : Синхронный блокирующий ввод-вывод является наиболее традиционной моделью ввода-вывода. Когда поток вызывает операцию ввода-вывода (например, чтение или запись), поток блокируется до завершения операции. Это означает, что поток не может выполнять никаких других задач до завершения операции.
Функции:
InputStream
、OutputStream
、Socket
иServerSocket
сцена.Пример:
Используйте традиционныеInputStream
иOutputStream
Для чтения и записи файлов:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class SyncBlockingIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
определение : при синхронном неблокирующем вводе-выводе поток не будет блокироваться после вызова операции ввода-вывода. Если операцию невозможно завершить немедленно, метод немедленно завершает работу, обычно возвращая специальное значение (например, -1 или 0) или выдавая исключение, указывающее, что операция не завершена.
Функции:
Channels
иBuffers
, позвонивconfigureBlocking(false)
Установите канал в неблокирующий режим.Пример:
Использование НИОFileChannel
Для неблокирующего чтения и записи:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
public class SyncNonBlockingIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
inChannel.configureBlocking(false);
while (inChannel.read(buffer) > 0) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
определение: Использование модели синхронного мультиплексного ввода-вывода.Selector
(селектор) для мониторинга несколькихChannel
событие.Когда поток вызываетSelector.select()
, он будет блокироваться до тех пор, пока не появится хотя бы одинChannel
Происходят события (такие как чтение, запись, запрос на соединение и т. д.).
Функции:
Channel
, улучшает параллелизм.Selector
иSelectionKey
Механизм, который может эффективно обрабатывать большое количество одновременных соединений.Пример:
Использование НИОFileChannel
Для неблокирующего чтения и записи:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;
public class SyncMultiplexingIOExample {
public static void main(String[] args) throws IOException {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = ssc.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
}
}
keys.clear();
}
}
}
}
}
определение : В асинхронной неблокирующей модели ввода-вывода поток возвращается сразу после начала операции ввода-вывода, не дожидаясь ее завершения. Когда операция будет завершена, поток будет уведомлен асинхронно через функцию обратного вызова или уведомление о событии.
Функции:
AsynchronousChannel
Интерфейс и реализация его подкласса, напримерAsynchronousFileChannel
иAsynchronousSocketChannel
。Пример:
Использование НИОAsynchronousFileChannel
Выполните асинхронное чтение и запись файлов:
import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
public class AsyncNonBlockingIOExample {
public static void main(String[] args) throws IOException, InterruptedException {
AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(Paths.get("input.txt"), StandardOpenOption.READ);
AsynchronousFileChannel outChannel = AsynchronousFileChannel.open(Paths.get("output.txt"), StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocate(1024);
CountDownLatch latch = new CountDownLatch(1);
inChannel.read(buffer, 0, buffer, 0, (channel, result) -> {
buffer.flip();
outChannel.write(buffer, 0, buffer, 0, null);
latch.countDown();
});
latch.await();
inChannel.close();
outChannel.close();
}
}
Примечание. Асинхронная модель требует поддержки базовой операционной системы (ядра).
определение : Теоретически такой модели не существует, поскольку «асинхронность» означает, что операция выполняется в фоновом режиме и поток не блокируется. Поэтому асинхронная блокировка ввода-вывода является противоречивой концепцией и не появится на практике.
Выбор подходящей модели ввода-вывода имеет решающее значение для производительности и управления ресурсами. В сценариях с высоким уровнем параллелизма чаще всего выбирают синхронный неблокирующий ввод-вывод или синхронный мультиплексный ввод-вывод. В сценариях, требующих высочайшей производительности и скорости ответа, первым выбором является асинхронный неблокирующий ввод-вывод.
Концепция нулевого копирования
Технология нулевого копирования означает, что в процессе передачи данных из одного места в другое данные не нужно копировать между пространством пользователя и пространством ядра или, по крайней мере, уменьшает количество таких копий, тем самым повышая эффективность работы система. При традиционных операциях ввода-вывода, когда данные считываются из сети или с диска, они сначала копируются в буфер в пространстве ядра, а затем копируются из пространства ядра в буфер в пространстве пользователя. Напротив, при нулевом копировании данные могут обрабатываться непосредственно в пространстве ядра или напрямую передаваться из пространства ядра на сетевое устройство, тем самым уменьшая операцию копирования ЦП, уменьшая накладные расходы системы и повышая эффективность передачи данных.
Источник нулевой копии
Концепция нулевого копирования впервые появилась при проектировании операционных систем с целью устранить узкое место в производительности, вызванное копированием данных при традиционных операциях ввода-вывода. В ранних компьютерных системах для всех операций ввода-вывода требовалось несколько копий данных между пространством пользователя и пространством ядра. Это постепенно стало узким местом производительности после того, как высокоскоростные сети и диски большой емкости стали обычным явлением.
Ключевые технические моменты
Реализация на Java:
Java поддерживает технологию нулевого копирования через структуру NIO (New I/O). НИО представилFileChannel
иSocketChannel
и другие классы, которые обеспечивают более эффективные операции ввода-вывода. Конкретно,FileChannel.transferTo()
иFileChannel.transferFrom()
Методы могут передавать данные непосредственно из файлового канала в канал сокета или наоборот, не загружая данные в буфер, что обеспечивает нулевое копирование.
Например, предположим, что вам нужно отправить содержимое большого файла в сеть. Традиционный подход заключается в том, чтобы сначала прочитать содержимое файла в буфер, а затем записать его в сеть. Это включает в себя две операции копирования: одну с диска в буфер и другую из буфера в сеть.при использованииtransferTo()
При использовании этого метода данные можно передавать напрямую с диска в сеть без необходимости использования промежуточного буфера, что уменьшает количество копий и обеспечивает нулевое копирование.
Пример использования ByteBuffer:
ByteBuffer
и другиеBuffer
класс (например,CharBuffer
,ShortBuffer
и т. д.) предоставляют буферы, которые можно заполнять или очищать без прямого копирования между пространством пользователя и пространством ядра.ByteBuffer
Может использоваться прямо или косвенно в технологии нулевого копирования:
ByteBuffer.allocateDirect(size)
СозданныйByteBuffer
Экземпляры отображаются непосредственно в физическую память, минуя кучу Java.Когда такой буфер сравнивается сFileChannel
илиSocketChannel
При совместном использовании данные могут передаваться напрямую между физической памятью и аппаратными устройствами без необходимости создания дополнительных копий через кучу Java. Это обеспечивает полное отсутствие копирования на некоторых платформах.FileChannel
изtransferTo()
иtransferFrom()
: Эти методы позволяют хранить данные непосредственно вFileChannel
иSocketChannel
передается междуByteBuffer
в качестве посредника. Это означает, что данные можно передавать напрямую с диска в сеть или наоборот без необходимости копирования между пространством пользователя и пространством ядра.ByteBuffer
изwrap()
метод:wrap()
позволяет обернуть существующий массив байтов или другой буфер вByteBuffer
, поэтому нет необходимости копировать данные в новый буфер. Это полезно, чтобы избежать ненужного копирования данных.ByteBuffer
изslice()
метод:slice()
метод создает представление буфера без копирования базовых данных.Это означает, что большоеByteBuffer
Разделить на несколько буферов меньшего размера без копирования данных.Объедините ByteBuffer с FileChannel:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class ZeroCopyExample {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
long transferred = fileChannel.transferTo(0, fileChannel.size(), System.out);
System.out.println("Transferred bytes: " + transferred);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Уведомление:
Следует отметить, что на самом деле полной нулевой копии не существует. Упомянутая здесь нулевая копия предназначена только для самого нашего приложения, у которого нет копии пользовательского уровня. Но даже на пользовательском уровне невозможно вообще не иметь операций копирования, а максимально сократить количество копий. Поэтому мы можем понять, что термин «нулевое копирование» на самом деле относится к технологии уменьшения количества копий данных. и это не означает, что реальной операции копирования не существует.