Java NIO (New Input/Output) — новый пакет API ввода-вывода — был представлен в J2SE 1.4 в 2002 году. Целью Java NIO является повышение производительности задач с интенсивным вводом-выводом на платформе Java. Десять лет спустя многие Java-разработчики все еще не знают, как в полной мере использовать NIO, и еще меньше людей знают, что обновленный API ввода-вывода (NIO.2) был представлен в Java SE 7. Самый большой вклад NIO и NIO.2 в платформу Java заключается в повышении производительности основного компонента разработки приложений Java: обработки ввода/вывода. Однако ни один из пакетов не очень полезен и подходит не для всех сценариев. При правильном использовании Java NIO и NIO.2 могут значительно сократить время, затрачиваемое на некоторые распространенные операции ввода-вывода. В этом суперсила NIO и NIO.2, и в этой статье я покажу вам 5 простых способов их использования.
Уведомления об изменениях (поскольку для каждого события требуется прослушиватель)
Селекторы и асинхронный ввод-вывод: улучшение мультиплексирования с помощью селекторов
Канал - Обещания и реальность
Отображение памяти - на клинке использована хорошая сталь
Кодировка символов и поиск
История компании NIO
Почему пакет расширения, который существует уже 10 лет, является новым пакетом ввода-вывода для Java? Причина в том, что большинству Java-программистов достаточно базовых операций ввода-вывода. В повседневной работе большинству разработчиков Java не требуется изучать NIO. Если пойти еще дальше, NIO — это больше, чем просто пакет повышения производительности. Вместо этого это набор различных функций, связанных с вводом-выводом Java. NIO достигает повышения производительности, делая производительность Java-приложений «ближе к сути», а это означает, что API-интерфейсы NIO и NIO.2 открывают доступ к низкоуровневым системным операциям. Цена NIO заключается в том, что, хотя он предоставляет более мощные возможности управления вводом-выводом, он также требует от нас более тщательного использования и практики, чем базовое программирование ввода-вывода. Еще одной особенностью NIO является его ориентация на выразительность приложений, которую мы увидим в следующих упражнениях.
Начните изучать NIO и NIO.2
По NIO имеется множество справочных материалов — некоторые избранные ссылки в справочных материалах. Для изучения NIO и NIO.2 необходима документация Java 2 SDK Standard Edition (SE) и документация Java SE 7. Чтобы использовать код из этой статьи, вам необходимо использовать JDK 7 или более поздней версии.
Многие разработчики впервые сталкиваются с NIO при обслуживании приложений: работающее приложение становится все медленнее и медленнее, поэтому некоторые предлагают использовать NIO для повышения скорости отклика. NIO более эффективен в плане повышения производительности приложений, но конкретные результаты зависят от базовой системы (обратите внимание, что NIO зависит от платформы). Если вы впервые используете NIO, вам необходимо тщательно все взвесить. Вы обнаружите, что способность NIO повышать производительность зависит не только от ОС, но и от используемой вами JVM, виртуального контекста хоста, характеристик запоминающего устройства и даже данных. Поэтому работу по измерению эффективности выполнить относительно сложно. Вам следует уделить особое внимание, особенно если ваша система имеет переносимую среду развертывания.
После понимания вышеизложенного у нас нет никаких беспокойств. Теперь давайте рассмотрим пять важных функций NIO и NIO.2.
1. Уведомление об изменении (поскольку для каждого события требуется прослушиватель)
Общей проблемой среди разработчиков, заинтересованных в NIO и NIO.2, является производительность Java-приложений. По моему опыту, уведомление об изменении файла в NIO.2 — самая интересная (и недооцененная) функция нового API ввода/вывода.
Многие приложения уровня предприятия требуют специальной обработки в следующих ситуациях:
Когда файл загружается в папку FTP
Когда определение в конфигурации изменено
При загрузке проекта документа
Когда происходят другие события файловой системы
Все это примеры уведомлений об изменениях или ответов на изменения. В более ранних версиях Java (и других языков) опрос был лучшим способом обнаружить эти события изменения. Опрос — это особый вид бесконечного цикла: проверьте файловую систему или другие объекты и сравните ее с предыдущим состоянием. Если изменений нет, продолжайте проверку через интервал примерно в несколько сотен миллисекунд или 10 секунд. Это происходит в бесконечном цикле.
NIO.2 обеспечивает лучший способ обнаружения изменений. Листинг 1 представляет собой простой пример.
Листинг 1. Механизм уведомления об изменении в NIO.2
Скопируйте код кода следующим образом:
импортировать java.nio.file.attribute.*;
importjava.io.*;
импортджава.утилизатор.*;
importjava.nio.file.Path;
importjava.nio.file.Paths;
importjava.nio.file.StandardWatchEventKinds;
importjava.nio.file.WatchEvent;
importjava.nio.file.WatchKey;
importjava.nio.file.WatchService;
importjava.util.Список;
publicclassWatcher{
publicstaticvoidmain(String[]args){
Paththis_dir=Paths.get(".");
System.out.println("Сейчас просматриваю текущий каталог...");
пытаться{
WatchServicewatcher=this_dir.getFileSystem().newWatchService();
this_dir.register(наблюдатель,StandardWatchEventKinds.ENTRY_CREATE);
WatchKeywatckKey=watcher.take();
List<WatchEvent<<64;>>events=watckKey.pollEvents();
для (WatchEventevent: события) {
System.out.println("Кто-то только что создал файл'"+event.context().toString()+"'.");
}
}catch(Исключение){
System.out.println("Ошибка:"+e.toString());
}
}
}
Скомпилируйте этот код и выполните его из командной строки. В том же каталоге создайте новый файл, например, запустите команду touchexample или copyWatcher.classexample. Вы увидите следующее сообщение с уведомлением об изменении:
Кто-нибудь, просто создайте поле «пример 1».
Этот простой пример показывает, как начать использовать функциональность JavaNIO. В то же время он также представляет класс NIO.2 Watcher, который является более прямым и простым в использовании, чем схема опроса в исходном вводе-выводе.
Следите за орфографическими ошибками
Копируя код из этой статьи, следите за орфографическими ошибками. Например, объект StandardWatchEventKinds в листинге 1 имеет форму множественного числа. Даже в документации Java.net это написано неправильно.
Советы
Механизм уведомлений в NIO проще в использовании, чем старый метод опроса, который заставит вас игнорировать подробный анализ конкретных требований. Когда вы впервые используете прослушиватель, вам необходимо тщательно рассмотреть семантику используемых вами концепций. Например, знать, когда закончится изменение, важнее, чем знать, когда оно начнется. К такому анализу следует подходить очень осторожно, особенно в таких распространенных сценариях, как перемещение папок FTP. NIO — очень мощный пакет, но в нем также есть некоторые тонкие «подводные камни», которые могут вызвать путаницу у тех, кто с ним не знаком.
2. Селекторы и асинхронный ввод-вывод: улучшение мультиплексирования с помощью селекторов.
Новички в NIO обычно связывают его с «неблокирующим вводом/выводом». NIO — это больше, чем просто неблокирующий ввод-вывод, но это восприятие не совсем неверно: базовый ввод-вывод Java блокирует ввод-вывод, то есть он ждет, пока операция не завершится, однако это неблокирующий или асинхронный ввод-вывод. — это наиболее часто используемая функция NIO, а не вся NIO.
Неблокирующий ввод-вывод NIO управляется событиями и демонстрируется в примере прослушивания файловой системы в листинге 1. Это означает определение селектора (обратного вызова или прослушивателя) для канала ввода-вывода, после чего программа может продолжить работу. Когда в этом селекторе происходит событие, например получение строки ввода, селектор «просыпается» и выполняется. Все это реализуется посредством одного потока, что существенно отличается от стандартного ввода-вывода Java.
В листинге 2 показана многопортовая сетевая программа echoer, реализованная с использованием селектора NIO. Это модификация небольшой программы, созданной Грегом Трэвисом в 2003 году (см. список ресурсов). В Unix и Unix-подобных системах уже давно реализованы эффективные селекторы, которые являются хорошей эталонной моделью для моделей высокопроизводительного программирования в сетях Java.
Листинг 2. Селектор NIO
Скопируйте код кода следующим образом:
importjava.io.*;
importjava.net.*;
импортджава.нио.*;
importjava.nio.channels.*;
импортджава.утилизатор.*;
publicclassMultiPortEcho
{
частныеинтпорты[];
PrivateByteBufferechoBuffer=ByteBuffer.allocate(1024);
publicMultiPortEcho(intports[])throwsIOException{
this.ports=порты;
configure_selector();
}
Privatevoidconfigure_selector () бросаетIOException {
//Создаем новый селектор
Selectorselector=Selector.open();
//Открываем прослушиватель на каждом порту и регистрируем каждый
//с селектором
for(inti=0;i<ports.length;++i){
ServerSocketChannelssc=ServerSocketChannel.open();
ssc.configureBlocking (ложь);
ServerSocketss=ssc.socket();
InetSocketAddressaddress = newInetSocketAddress (порты [i]);
сс.bind(адрес);
SelectionKeykey=ssc.register(селектор,SelectionKey.OP_ACCEPT);
System.out.println("Goingtolistenon"+ports[i]);
}
в то время как (правда) {
intnum=selector.select();
SetselectedKeys=selector.selectedKeys();
Iteratorit=selectedKeys.iterator();
в то время как (it.hasNext ()) {
SelectionKeykey=(SelectionKey)it.next();
if((key.readyOps()&SelectionKey.OP_ACCEPT)
==КлючВыбора.OP_ACCEPT){
//Принимаем новое соединение
ServerSocketChannelssc=(ServerSocketChannel)key.channel();
SocketChannelsc=ssc.accept();
sc.configureBlocking (ложь);
//Добавляем новое соединение в селектор
SelectionKeynewKey=sc.register(селектор,SelectionKey.OP_READ);
это.удалить();
System.out.println("Получено соединение"+sc);
}elseif((key.readyOps()&SelectionKey.OP_READ)
==КлючВыбора.OP_READ){
//Чтение данных
SocketChannelsc=(SocketChannel)key.channel();
//Эходанные
intbytesEchoed=0;
пока (правда) {
echoBuffer.clear();
intnumber_of_bytes = sc.read (echoBuffer);
если (количество_байтов <= 0) {
перерыв;
}
echoBuffer.flip();
sc.write(echoBuffer);
bytesEchoed+=number_of_bytes;
}
System.out.println("Echoed"+bytesEchoed+"from"+sc);
это.удалить();
}
}
}
}
staticpublicvoidmain(Stringargs[])throwsException{
если(args.length<=0){
System.err.println("Использование:javaMultiPortEchoport[portport...]");
Система.выход(1);
}
intports[]=newint[args.length];
for(inti=0;i<args.length;++i){
порты[i]=Integer.parseInt(args[i]);
}
newMultiPortEcho(порты);
}
}
Скомпилируйте этот код и запустите его с помощью команды типа javaMultiPortEcho80058006. После успешного запуска этой программы запустите простой telnet или другой эмулятор терминала для подключения к интерфейсам 8005 и 8006. Вы увидите, что эта программа повторяет все полученные символы - и делает это через поток Java.
3. Отрывок: обещание и реальность
В NIO канал может представлять любой объект, который можно читать и записывать. Его роль — обеспечить абстракцию для файлов и сокетов. Каналы NIO поддерживают согласованный набор методов, поэтому вам не нужно уделять особое внимание различным объектам при кодировании, будь то стандартный вывод, сетевое соединение или используемый канал. Эта особенность каналов унаследована от потоков в базовом вводе-выводе Java. Потоки обеспечивают блокирующий ввод-вывод; каналы поддерживают асинхронный ввод-вывод.
NIO часто рекомендуют из-за его высокой производительности, но, точнее, из-за быстрого времени отклика. В некоторых сценариях производительность NIO хуже, чем у базового ввода-вывода Java. Например, для простого последовательного чтения и записи небольшого файла производительность, достигаемая просто за счет потоковой передачи, может быть в два-три раза выше, чем соответствующая реализация событийно-ориентированного кодирования на основе каналов. В то же время немультиплексные каналы, то есть отдельный канал для каждого потока, работают намного медленнее, чем несколько каналов, регистрирующих свои селекторы в одном потоке.
Теперь, когда вы раздумываете, использовать ли потоки или каналы, попробуйте задать себе следующие вопросы:
Сколько объектов ввода-вывода вам нужно для чтения и записи?
Являются ли различные объекты ввода-вывода последовательными или все они должны происходить одновременно?
Должны ли ваши объекты ввода-вывода сохраняться в течение короткого периода времени или на протяжении всего времени существования вашего процесса?
Подходит ли ваш ввод-вывод для обработки в одном потоке или в нескольких разных потоках?
Сетевая связь и локальный ввод-вывод выглядят одинаково или у них разные модели?
Такой анализ является лучшей практикой при принятии решения о том, использовать ли потоки или каналы. Помните: NIO и NIO.2 не заменяют базовый ввод-вывод, а дополняют его.
4. Отображение памяти – на клинке использована хорошая сталь
Самым значительным улучшением производительности в NIO является отображение памяти. Отображение памяти — это служба системного уровня, которая рассматривает раздел файла, используемый в программе, как память.
Существует множество потенциальных эффектов сопоставления памяти, больше, чем я могу здесь рассказать. На более высоком уровне это может привести к тому, что производительность ввода-вывода при доступе к файлам достигнет скорости доступа к памяти. Доступ к памяти часто на несколько порядков быстрее, чем доступ к файлу. Листинг 3 представляет собой простой пример карты памяти NIO.
Листинг 3. Отображение памяти в NIO
Скопируйте код кода следующим образом:
importjava.io.RandomAccessFile;
importjava.nio.MappedByteBuffer;
importjava.nio.channels.FileChannel;
publicclassmem_map_example{
Privatestaticintmem_map_size=20*1024*1024;
PrivatestaticStringfn="example_memory_mapped_file.txt";
publicstaticvoidmain(String[]args)throwsException {
RandomAccessFilememoryMappedFile=newRandomAccessFile(fn,"rw");
//Сопоставление файла в памяти
MappedByteBufferout = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE,0,mem_map_size);
//Запись вMemoryMappedFile
for(inti=0;i<mem_map_size;i++){
out.put((byte)'A');
}
System.out.println("File'"+fn+"'isnow"+Integer.toString(mem_map_size)+"bytesfull.");
//Чтение из файла, отображенного в память.
for(inti=0;i<30;i++){
System.out.print((char)out.get(i));
}
System.out.println("/nReadingfrommemory-mappedfile'"+fn+"'iscomplete.");
}
}
В листинге 3 этот простой пример создает файл example_memory_mapped_file.txt размером 20 МБ, заполняет его символом A, а затем считывает первые 30 байтов. В практических приложениях отображение памяти не только хорошо повышает скорость ввода-вывода, но также позволяет нескольким различным устройствам чтения и записи одновременно обрабатывать один и тот же образ файла. Эта технология мощная, но в то же время опасная, но при правильном использовании она увеличит вашу скорость ввода-вывода в несколько раз. Как мы все знаем, торговые операции на Уолл-стрит используют технологию отображения памяти, чтобы получить преимущества за секунды или даже миллисекунды.
5.Кодировка символов и поиск
Последняя особенность NIO, которую я хочу объяснить в этой статье, — это charset, пакет, используемый для преобразования различных кодировок символов. До NIO Java реализовывала большую часть тех же функций, встроенных в метод getByte. charset популярен, поскольку он более гибок, чем getBytes, и может быть реализован на более низком уровне, что приводит к повышению производительности. Это еще более ценно для поиска неанглийских языков, которые чувствительны к кодировке, порядку и другим языковым особенностям.
В листинге 4 показан пример преобразования символов Юникода в Java в символы Latin-1.
Список 4.Персонажи в NIO
Скопируйте код кода следующим образом:
Stringsome_string="Это строка, которая Javanatively хранит Юникод.";
Charsetlatin1_charset=Charset.forName("ISO-8859-1");
CharsetEncodelatin1_encoder=charset.newEncoder();
ByteBufferlatin1_bbuf=latin1_encoder.encode(CharBuffer.wrap(some_string));
Обратите внимание, что наборы символов и каналы предназначены для совместного использования, поэтому программа может работать нормально при координации сопоставления памяти, асинхронного ввода-вывода и преобразования кодировки.
Резюме: Конечно, есть еще кое-что, что нужно знать.
Цель этой статьи — познакомить разработчиков Java с некоторыми наиболее важными (и полезными) функциями NIO и NIO.2. Вы можете использовать основу, созданную этими примерами, чтобы понять некоторые другие методы NIO, например, знания, которые вы узнаете о каналах, могут помочь вам понять обработку символических ссылок в файловой системе в NIO's Path; Вы также можете обратиться к списку ресурсов, который я дал позже, в котором представлены некоторые документы для углубленного изучения нового API ввода-вывода Java.