Что такое java io
Перейти к содержимому

Что такое java io

  • автор:

Java. IO Пространство имен

Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

Классы

добавляет BufferedInputStream функциональные возможности для другого входного потока, а именно возможность буферизации входных данных и поддержки mark методов и reset .

Класс реализует буферизованного выходного потока.

Считывает текст из входного символьного потока, буферизируя символы таким образом, чтобы обеспечить эффективное чтение символов, массивов и строк.

Записывает текст в поток вывода символов, буферизируя символы, чтобы обеспечить эффективную запись отдельных символов, массивов и строк.

Содержит ByteArrayInputStream внутренний буфер, содержащий байты, которые могут быть считаны из потока.

Этот класс реализует поток вывода, в котором данные записываются в массив байтов.

Этот класс реализует буфер символов, который можно использовать в качестве потока ввода символов.

Этот класс реализует буфер символов, который можно использовать в качестве модуля записи.

Базовый класс для исключений преобразования символов.

Методы доступа к консольному устройству на основе символов, если таковые есть, связанному с текущей виртуальной машиной Java.

Поток входных данных позволяет приложению считывать примитивные типы данных Java из базового входного потока способом, не зависящим от компьютера.

Поток вывода данных позволяет приложению записывать примитивные типы данных Java в поток вывода переносимым способом.

Сигнализирует о неожиданном достижении конца файла или потока во время ввода.

Абстрактное представление имен путей к файлам и каталогам.

Экземпляры класса дескриптора файла служат непрозрачным дескриптором для базовой структуры компьютера, представляющей открытый файл, открытый сокет или другой источник или приемник байтов.

получает FileInputStream входные байты из файла в файловой системе.

Сигнализирует о сбое попытки открыть файл, обозначенный указанным именем пути.

Поток вывода файлов — это выходной поток для записи данных в File или в FileDescriptor .

Устаревший код безопасности; не использовать.

Считывает текст из символьных файлов, используя размер буфера по умолчанию.

Записывает текст в символьные файлы, используя размер буфера по умолчанию.

Содержит FilterInputStream другой входной поток, который используется в качестве основного источника данных, возможно, преобразовывает данные по пути или предоставляет дополнительные функциональные возможности.

Этот класс является суперклассом всех классов, которые фильтруют потоки вывода.

Абстрактный класс для чтения отфильтрованных потоков символов.

Абстрактный класс для записи отфильтрованных потоков символов.

Этот абстрактный класс является суперклассом всех классов, представляющих входной поток байтов.

InputStreamReader — это мост от байтовых потоков к потокам символов: он считывает байты и декодирует их в символы с помощью указанного java.nio.charset.Charset charset .

Сообщает, что операция ввода-вывода была прервана.

Возникает, когда среда выполнения сериализации обнаруживает одну из следующих проблем с классом.

Указывает, что один или несколько десериализованных объектов не прошли проверочные тесты.

Возникает при возникновении серьезной ошибки ввода-вывода.

Сигнализирует о возникновении какого-либо исключения ввода-вывода.

Этот класс представляет собой фильтр входного потока, который предоставляет дополнительные функции отслеживания текущего номера строки.

Буферизованный поток ввода символов, который отслеживает номера строк.

Возникает, если сериализация или десериализация не активна.

Возникает, когда экземпляру требуется сериализуемый интерфейс.

ObjectInputStream десериализует примитивные данные и объекты, ранее записанные с помощью ObjectOutputStream.

Предоставьте доступ к постоянным полям, считываемым из входного потока.

ObjectOutputStream записывает примитивные типы данных и графы объектов Java в OutputStream.

Предоставьте программный доступ к постоянным полям для записи в ObjectOutput.

Дескриптор сериализации для классов.

Вспомогательный интерфейс с константами, используемыми реализацией сериализации.

Суперкласс всех исключений, относящихся к классам Object Stream.

Описание сериализуемого поля из класса Serializable.

Исключение, указывающее на сбой операции чтения объекта из-за непрочитанных примитивных данных или конец данных, принадлежащих сериализованному объекту в потоке.

Этот абстрактный класс является суперклассом всех классов, представляющих выходной поток байтов.

OutputStreamWriter — это мост от потоков символов к потокам байтов. Символы, записанные в него, кодируются в байты с помощью указанного java.nio.charset.Charset charset .

Поток входных данных по каналу должен быть подключен к потоку вывода по каналу; поток входных данных, передаваемый по конвейеру, затем предоставляет все байты данных, записанные в поток вывода по каналу.

Поток вывода, передаваемый по каналу, можно подключить к потоку входных данных, чтобы создать канал связи.

Потоки ввода символов по конвейеру.

Потоки символьного вывода по конвейеру.

добавляет PrintStream функциональные возможности в другой поток вывода, а именно возможность удобно печатать представления различных значений данных.

Выводит форматированные представления объектов в поток вывода текста.

добавляет PushbackInputStream функциональные возможности в другой входной поток, а именно возможность «отодвинуть» или «непрочитанные» байты, сохраняя обратно отправляемые байты во внутреннем буфере.

Средство чтения потока символов, которое позволяет возвращать символы в поток.

Экземпляры этого класса поддерживают чтение и запись в файл произвольного доступа.

Абстрактный класс для чтения потоков символов.

Представляет SequenceInputStream логическое объединение других входных потоков.

Эта устаревшая безопасность не поддерживается в Android.

Возникает, когда сведения об элементе управления, считанные из потока объектов, нарушают внутренние проверки согласованности.

Класс StreamTokenizer принимает входной поток и анализирует его в «токены», позволяя считывать маркеры по одному за раз.

Этот класс позволяет приложению создать входной поток, в котором считываемые байты предоставляются содержимым строки.

Поток символов, источником которого является строка.

Поток символов, который собирает свои выходные данные в буфер строки, который затем можно использовать для создания строки.

Сигнализирует о сбое операции синхронизации.

Заключает в оболочку IOException с незаверяемым исключением.

Кодировка символов не поддерживается.

Сигнализирует о том, что строка неправильного формата в «DataInput.

Сигнализирует о том, что во время операции записи было вызвано одно из исключений ObjectStreamException.

Абстрактный класс для записи в потоки символов.

Интерфейсы

— Closeable это источник или назначение данных, которые можно закрыть.

Интерфейс DataInput позволяет считывать байты из двоичного потока и восстанавливать из них данные в любом из примитивных типов Java.

Интерфейс DataOutput обеспечивает преобразование данных из любого из примитивных типов Java в последовательность байтов и запись этих байтов в двоичный поток.

Только удостоверение класса экземпляра Externalizable записывается в поток сериализации, и класс отвечает за сохранение и восстановление содержимого своих экземпляров.

Фильтр для абстрактных имен путей.

Экземпляры классов, реализующих этот интерфейс, используются для фильтрации имен файлов.

— Flushable это назначение данных, которые могут быть сброшены.

ObjectInput расширяет интерфейс DataInput, включив в него чтение объектов.

Интерфейс обратного вызова для проверки объектов в графе.

ObjectOutput расширяет интерфейс DataOutput, включая запись объектов.

Константы, записанные в поток сериализации объектов.

Сериализуемость класса включается классом, реализующим java.

Перечисления

Перечисляет значения, возвращаемые несколькими типами и принимаемые в качестве параметра члена F:Java.IO.ObjectOutputStream.UseProtocolVersion .

Перечисляет значения, возвращаемые несколькими типами.

IO vs NIO

IO (Input & Output) API — это Java API, которое облегчает разработчикам работу с потоками. Скажем, мы получили какие-то данные (например, фамилия, имя и отчество) и нам нужно записать их в файл — в этот момент и приходит время использовать java.io .

Структура библиотеки java.io

Но у Java IO есть свои недостатки, так что давай поговорим по порядку о каждом из них:

  1. Блокирующий доступ для ввода/вывода данных. Проблема состоит в том, что когда разработчик пытается прочитать файл или записать что-то в него, используя Java IO , он блокирует файл и доступ к нему до момента окончания выполнения своей задачи.
  2. Отсутствует поддержка виртуальных файловых систем.
  3. Нет поддержки ссылок.
  4. Очень большое количество checked исключений.

Работа с файлами всегда несет за собой работу с исключениями: например, попытка создать новый файл, который уже существует, вызовет IOException . В данном случае работа приложения должна быть продолжена и пользователь должен получить уведомление о том, по какой причине файл не может быть создан.

 try < File.createTempFile("prefix", ""); >catch (IOException e) < // Handle IOException >/** * Creates an empty file in the default temporary-file directory * any exceptions will be ignored. This is typically used in finally blocks. * @param prefix * @param suffix * @throws IOException - If a file could not be created */ public static File createTempFile(String prefix, String suffix) throws IOException

Здесь мы видим, что метод createTempFile выбрасывает IOException , когда файл не может быть создан. Это исключение должно быть обработано соответственно. Если попытаться вызвать этот метод вне блока try-catch , то компилятор выдаст ошибку и предложит нам два варианта исправления: окружить метод блоком try-catch или сделать так, чтобы метод, внутри которого вызывается File.createTempFile , выбрасывал исключение IOException (чтобы передать его на верхний уровень для обработки).

Приход к Java NIO и сравнение с Java IO

Java NIO , или Java Non-blocking I/O (иногда — Java New I/O, “новый ввод-вывод”) предназначена для реализации высокопроизводительных операций ввода-вывода.

Давай попробуем сравнить методы Java IO и те, что пришли им на замену.

Сначала поговорим о работе с Java IO :

Класс InputStream

 try(FileInputStream fin = new FileInputStream("C:/javarush/file.txt")) < System.out.printf("File size: %d bytes \n", fin.available()); int i=-1; while((i=fin.read())!=-1)< System.out.print((char)i); >> catch(IOException ex)

Класс FileInputStream предназначен для считывания данных из файла. Он является наследником класса InputStream и поэтому реализует все его методы. Если файл не может быть открыт, то генерируется исключение FileNotFoundException .

Класс OutputStream

 String text = "Hello world!"; // строка для записи try(FileOutputStream fos = new FileOutputStream("C:/javarush/file.txt")) < // переводим нашу строку в байты byte[] buffer = text.getBytes(); fos.write(buffer, 0, buffer.length); System.out.println("The file has been written"); >catch(IOException ex)

Класс FileOutputStream предназначен для записи байтов в файл. Он является производным от класса OutputStream .

Классы Reader и Writer

Класс FileReader позволяет нам читать символьные данные из потоков, а класс FileWriter используется для записи потоков символов. Реализация записи и чтения из файла приведена ниже:

 String fileName = "c:/javarush/Example.txt"; // Создание объекта FileWriter try (FileWriter writer = new FileWriter(fileName)) < // Запись содержимого в файл writer.write("Это простой пример,\n в котором мы осуществляем\n с помощью языка Java\n запись в файл\n и чтение из файла\n"); writer.flush(); >catch (IOException e) < e.printStackTrace(); >// Создание объекта FileReader try (FileReader fr = new FileReader(fileName)) < char[] a = new char[200];// Количество символов, которое будем считывать fr.read(a); // Чтение содержимого в массив for (char c : a) < System.out.print(c); // Вывод символов один за другими >> catch (IOException e)

Теперь поговорим о Java NIO :

Channel

В отличие от потоков, которые используются в Java IO , Channel является двусторонним, то есть может и считывать, и записывать. Канал Java NIO поддерживает асинхронный поток данных как в режиме блокировки, так и в режиме без блокировки.

 RandomAccessFile aFile = new RandomAccessFile("C:/javarush/file.txt", "rw"); FileChannel inChannel = aFile.getChannel(); ByteBuffer buf = ByteBuffer.allocate(100); int bytesRead = inChannel.read(buf); while (bytesRead != -1) < System.out.println("Read " + bytesRead); buf.flip(); while(buf.hasRemaining())< System.out.print((char) buf.get()); >buf.clear(); bytesRead = inChannel.read(buf); > aFile.close(); 

Здесь мы реализовали FileChannel . Для чтения данных из файла мы используем файловый канал. Объект файлового канала может быть создан только вызовом метода getChannel() для файлового объекта, поскольку мы не можем напрямую создать объект файлового канала.

Кроме FileChannel у нас есть и другие реализации каналов:

  • FileChannel — работа с файлами
  • DatagramChannel — канал для работы по UDP-соединению
  • SocketChannel — канал для работы по TCP-соединению
  • ServerSocketChannel содержит в себе SocketChannel и схож с принципом работы веб-сервера

Обрати внимание: FileChannel нельзя переключить в неблокирующий режим. Неблокирующий режим Java NIO позволяет запрашивать считанные данные из канала (channel) и получать только то, что доступно на данный момент, или вообще ничего, если доступных данных пока нет. В это же время SelectableChannel и его реализации могут устанавливаться в неблокирующем режиме с помощью метода connect() .

Selector

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

Реализация связи каналов и селектора

 Selector selector = Selector.open(); channel.configureBlocking(false); // неблокирующий режим SelectionKey key = channel.register(selector, SelectionKey.OP_READ); 

Таким образом мы создаем наш Selector и связываем его с SelectableChannel .

Для использования с селектором канал должен находиться в неблокирующем режиме. Это значит, что вы не можете использовать FileChannel с селектором, поскольку FileChannel нельзя переключить в неблокирующий режим. Сокетные каналы будут работать нормально.

Здесь мы можем отметить, что в нашем примере SelectionKey — это набор операций, которые можно выполнить с каналом. Мы можем узнать состояние канала с помощью клавиши выбора.

  • SelectionKey.OP_CONNECT — канал, который готов к подключению к серверу.
  • SelectionKey.OP_ACCEPT — канал, который готов принимать входящие соединения.
  • SelectionKey.OP_READ — канал, который готов к чтению данных.
  • SelectionKey.OP_WRITE — канал, который готов к записи данных.

Buffer

Данные считываются в буфер для последующей обработки. Разработчик может двигаться по буферу вперед и назад, что дает нам немного больше гибкости при обработке данных. В то же время нам нужно проверять, содержит ли буфер необходимый для корректной обработки объем данных. Также не забывай следить, чтобы при чтении данных в буфер не уничтожить еще не обработанные данные, находящиеся там.

 ByteBuffer buf = ByteBuffer.allocate (2048); int bytesRead = channel.read(buf); buf.flip(); // меняем режим на чтение while (buf.hasRemaining()) < byte data = buf.get(); // есть методы для примитивов >buf.clear(); // очистили и можно переиспользовать 

Основные свойства буфера:

Основные атрибуты
capacity Размер буфера, который является длиной массива.
position Начальная позиция для работы с данными. limit Операционный лимит. Для операций чтения предел — это объем данных, который можно поместить в оперативный режим, а для операций записи — предел емкости или доступная для записи квота, указанная ниже.
mark Индекс значения, до которого будет сброшен параметр position при вызове метода reset() .

Теперь давай немного поговорим о том, что нового появилось в Java NIO2 .

Path

Path представляет из себя путь в файловой системе. Он содержит имя файла и список каталогов, определяющих путь к нему.

 Path relative = Paths.get("Main.java"); System.out.println("Файл: " + relative); //получение файловой системы System.out.println(relative.getFileSystem()); 

Paths — это совсем простой класс с единственным статическим методом get() . Его создали исключительно для того, чтобы из переданной строки или URI получить объект типа Path .

 Path path = Paths.get("c:\\data\\file.txt"); 

Files

Files — это утилитный класс, с помощью которого мы можем напрямую получать размер файла, копировать их, и не только.

 Path path = Paths.get("files/file.txt"); boolean pathExists = Files.exists(path); 

FileSystem

FileSystem предоставляет интерфейс к файловой системе. Файловая система работает как фабрика для создания различных объектов ( Path , PathMatcher , Files ). Этот объект помогает получить доступ к файлам и другим объектам в файловой системе.

 try < FileSystem filesystem = FileSystems.getDefault(); for (Path rootdir : filesystem.getRootDirectories()) < System.out.println(rootdir.toString()); >> catch (Exception e)

Тест производительности

Для этого теста давай возьмем два файла. Первый — маленький файл с текстом, а второй — большой видеоролик.

Создаем файл и добавляем в него немного слов и символов:

% touch text.txt

Наш файл по итогу занимает в памяти 42 байта:

Теперь напишем код, который будет копировать наш файл из одной папки в другую. Проверим его работу на маленьком и большом файлах, и тем самым сравним скорость работы IO , NIO и NIO2 .

Код для копирования, написанный на Java IO :

 public static void main(String[] args) < long currentMills = System.currentTimeMillis(); long startMills = currentMills; File src = new File("/Users/IdeaProjects/testFolder/text.txt"); File dst = new File("/Users/IdeaProjects/testFolder/text1.txt"); copyFileByIO(src, dst); currentMills = System.currentTimeMillis(); System.out.println("Время выполнения в миллисекундах: " + (currentMills - startMills)); >public static void copyFileByIO(File src, File dst) < try(InputStream inputStream = new FileInputStream(src); OutputStream outputStream = new FileOutputStream(dst))< byte[] buffer = new byte[1024]; int length; // Читаем данные в байтовый массив, а затем выводим в OutStream while((length = inputStream.read(buffer)) >0) < outputStream.write(buffer, 0, length); >> catch (IOException e) < e.printStackTrace(); >> 

И код для Java NIO :

 public static void main(String[] args) < long currentMills = System.currentTimeMillis(); long startMills = currentMills; File src = new File("/Users/IdeaProjects/testFolder/text.txt"); File dst = new File("/Users/IdeaProjects/testFolder/text2.txt"); // копия nio copyFileByChannel(src, dst); currentMills = System.currentTimeMillis(); System.out.println("Время выполнения в миллисекундах: " + (currentMills - startMills)); >public static void copyFileByChannel(File src, File dst) < // 1. Получаем FileChannel исходного файла и целевого файла try(FileChannel srcFileChannel = new FileInputStream(src).getChannel(); FileChannel dstFileChannel = new FileOutputStream(dst).getChannel())< // 2. Размер текущего FileChannel long count = srcFileChannel.size(); while(count >0) < /**============================================================= * 3. Записать байты из FileChannel исходного файла в целевой FileChannel * 1. srcFileChannel.position (): начальная позиция в исходном файле не может быть отрицательной * 2. count: максимальное количество переданных байтов, не может быть отрицательным * 3. dstFileChannel: целевой файл *==============================================================*/ long transferred = srcFileChannel.transferTo(srcFileChannel.position(), count, dstFileChannel); // 4. После завершения переноса измените положение исходного файла на новое место srcFileChannel.position(srcFileChannel.position() + transferred); // 5. Рассчитаем, сколько байтов осталось перенести count -= transferred; >> catch (FileNotFoundException e) < e.printStackTrace(); >catch (IOException e) < e.printStackTrace(); >> 

Код для Java NIO2 :

 public static void main(String[] args) < long currentMills = System.currentTimeMillis(); long startMills = currentMills; Path sourceDirectory = Paths.get("/Users/IdeaProjects/testFolder/test.txt"); Path targetDirectory = Paths.get("/Users/IdeaProjects/testFolder/test3.txt"); Files.copy(sourceDirectory, targetDirectory); currentMills = System.currentTimeMillis(); System.out.println("Время выполнения в миллисекундах: " + (currentMills - startMills)); >

Начнем с маленького файла.

Время выполнения с помощью Java IO в среднем было 1 миллисекунду. Запуская тест несколько раз, получаем резульат от 0 до 2 миллисекунд.

Время выполнения в миллисекундах: 1

Время выполнения с помощью Java NIO гораздо больше. Среднее время — 11 миллисекунд. Результаты были от 9 до 16. Это связанно с тем, что Java IO работает не так, как наша операционная система. IO перемещает и обрабатывает файлы один за другим, в то время как операционная система отправляет данные в одном большом виде. А NIO показал плохие результаты из-за того, что он ориентирован на буфер, а не на поток, как IO .

Время выполнения в миллисекундах: 12

И так же запустим наш тест для Java NIO2 . NIO2 имеет улучшенное управление с файлами по сравнению с Java NIO . Из-за этого результаты обновленной библиотеки так отличаются:

Время выполнения в миллисекундах: 3

А теперь давай попробуем протестировать наш большой файл, видео на 521 МБ. Задача будет точно такой же: скопировать в другую папку. Смотрим!

Результаты с Java IO :

Время выполнения в миллисекундах: 1866

А вот результат Java NIO :

Время выполнения в миллисекундах: 205

Java NIO справился с файлом в 9 раз быстрее при первом тесте. Повторные тесты показывали примерно такие же результаты.

А так же попробуем наш тест на Java NIO2 :

Время выполнения в миллисекундах: 360

Почему же такой результат? Просто потому что у нас нет особого смысла сравнивать производительность между ними, так как они служат разным целям. NIO представляет собой более абстрактный низкоуровневый ввод-вывод данных, а NIO2 ориентирован на управление файлами.

Итоги

Можем смело заявить, что Java NIO существенно повышает эффективность работы с файлами за счет использования внутри блоков. Еще один плюс состоит в том, что библиотека NIO разбита на две части: одна для работы с файлами, вторая — для работы в сети.

Новый API, который используется в Java NIO2 для работы с файлами, предлагает множество полезных функций:

  • гораздо более полезную адресацию файловой системы с помощью Path ,
  • значительно улучшенную работу с ZIP-файлами с использованием пользовательского поставщика файловой системы,
  • доступ к специальным атрибутам файла,
  • множество удобных методов, например, чтение всего файла с помощью одной команды, копирование файла с помощью одной команда и т. д.

Все это связано с файлом и файловой системой, и все довольно высокого уровня.

В современных реалиях Java NIO занимает около 80-90% работы с файлами, хотя доля Java IO тоже еще существенна.

�� P. S. Тесты проводились на MacBook Pro 14’ 16/512. Результаты теста могут отличатся от операционной системы и параметров рабочей машины программиста.

Основные отличия Java IO и Java NIO

Когда я начал изучать стандартный ввод/вывод в Java, то первое время был немного шокирован обилием интерфейсов и классов пакета java.io.*, дополненных еще и целым перечнем специфических исключений.

Потратив изрядное количество часов на изучение и реализацию кучи разнообразных туториалов из Интернета, начал чувствовать себя уверенно и вздохнул с облегчением. Но в один прекрасный момент понял, что для меня все только начинается, так как существует еще и пакет java.nio.*, известный ещё под названием Java NIO или Java New IO. Вначале казалось, что это тоже самое, ну типа вид сбоку. Однако, как оказалось, есть существенные отличия, как в принципе работы, так и в решаемых с их помощью задачах.

Разобраться во всем этом мне здорово помогла статья Джакоба Дженкова (Jakob Jenkov) – “Java NIO vs. IO”. Ниже она приводиться в адаптированном виде.

Поспешу заметить, что статья не является руководством по использованию Java IO и Java NIO. Её цель – дать людям, начинающим изучать Java, возможность понять концептуальные отличия между двумя указанными инструментами организации ввода/вывода.

Основные отличия между Java IO и Java NIO
IO NIO
Потокоориентированный Буфер-ориентированный
Блокирующий (синхронный) ввод/вывод Неблокирующий (асинхронный) ввод/вывод
Селекторы
Потокоориентированный и буфер-ориентированный ввод/вывод

Основное отличие между двумя подходами к организации ввода/вывода в том, что Java IO является потокоориентированным, а Java NIO – буфер-ориентированным. Разберем подробней.

Потокоориентированный ввод/вывод подразумевает чтение/запись из потока/в поток одного или нескольких байт в единицу времени поочередно. Данная информация нигде не кэшируются. Таким образом, невозможно произвольно двигаться по потоку данных вперед или назад. Если вы хотите произвести подобные манипуляции, вам придется сначала кэшировать данные в буфере.

Подход, на котором основан Java NIO немного отличается. Данные считываются в буфер для последующей обработки. Вы можете двигаться по буферу вперед и назад. Это дает немного больше гибкости при обработке данных. В то же время, вам необходимо проверять содержит ли буфер необходимый для корректной обработки объем данных. Также необходимо следить, чтобы при чтении данных в буфер вы не уничтожили ещё не обработанные данные, находящиеся в буфере.

Блокирующий и неблокирующий ввод/вывод

Потоки ввода/вывода (streams) в Java IO являются блокирующими. Это значит, что когда в потоке выполнения (tread) вызывается read() или write() метод любого класса из пакета java.io.*, происходит блокировка до тех пор, пока данные не будут считаны или записаны. Поток выполнения в данный момент не может делать ничего другого.

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

Каналы (channels)

Каналы – это логические (не физические) порталы, через которые осуществляется ввод/вывод данных, а буферы являются источниками или приёмниками этих переданных данных. При организации вывода, данные, которые вы хотите отправить, помещаются в буфер, а он передается в канал. При вводе, данные из канала помещаются в предоставленный вами буфер.

Каналы напоминают трубопроводы, по которым эффективно транспортируются данные между буферами байтов и сущностями по ту сторону каналов. Каналы – это шлюзы, которые позволяют получить доступ к сервисам ввода/вывода операционной системы с минимальными накладными расходами, а буферы – внутренние конечные точки этих шлюзов, используемые для передачи и приема данных.

Тоже самое справедливо и для неблокирующего вывода. Поток выполнения может запросить запись в канал некоторых данных, но не дожидаться при этом пока они не будут полностью записаны.

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

Селекторы

Селекторы в Java NIO позволяют одному потоку выполнения мониторить несколько каналов ввода. Вы можете зарегистрировать несколько каналов с селектором, а потом использовать один поток выполнения для обслуживания каналов, имеющих доступные для обработки данные, или для выбора каналов, готовых для записи.

Чтобы лучше понять концепцию и выгоду от применения селекторов, давайте абстрагируемся от программирования и представим себе железнодорожный вокзал. Вариант без селектора: есть три железнодорожных пути (каналы), на каждый из них в любой момент времени может прибыть поезд (данные из буфера), на каждом пути постоянно ожидает сотрудник вокзала (поток выполнения), задача которого – обслуживание прибывшего поезда. В результате трое сотрудников постоянно находятся на вокзале даже если там вообще нет поездов. Вариант с селектором: ситуация та же, но для каждой платформы есть индикатор, сигнализирующий сотруднику вокзала (поток выполнения) о прибытии поезда. Таким образом на вокзале достаточно присутствия одного сотрудника.

Влияние Java NIO и Java IO на дизайн приложения

Выбор между Java NIO и Java IO может на следующие аспекты дизайна вашего приложения:
1. API обращений к классам ввода/вывода;
2. Обработка данных;
3. Количество потоков выполнения, использованных для обработки данных.

API обращений к классам ввода/вывода

Естественно, использование Java NIO серьезно отличается от использования Java IO. Так как, вместо чтения данных байт за байтом с использованием, например InputStream, данные для начала должны быть считаны в буфер и браться для обработки уже оттуда.

Обработка данных

Обработка данных при использовании Java NIO тоже отличается.
Как уже упоминалось, при использовании Java IO вы читаете данные байт за байтом с InputStream или Reader. Представьте, что вы проводите считывание строк текстовой информации:

Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890

Этот поток строк текста может обрабатываться следующим образом:

InputStream input = . ; BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String nameLine = reader.readLine(); String ageLine = reader.readLine(); String emailLine = reader.readLine(); String phoneLine = reader.readLine(); 

Обратите внимание, как состояние процесса обработки зависит от того, насколько далеко продвинулось выполнение программы. Когда первый метод readLine() возвращает результат выполнения, вы уверенны – целая строка текста была считана. Метод является блокирующим и действие блокировки продолжается до тех пор, пока вся строка не будет считана. Вы также четко понимаете, что данная строка содержит имя. Подобно этому, когда метод вызывается во второй раз, вы знаете, что в результате получите возраст.

Как вы видите, прогресс в выполнении программы достигается только тогда, когда доступны новые данные для чтения, и для каждого шага вы знаете что это за данные. Когда поток выполнения достигает прогресса в считывании определенной части данных, поток ввода (в большинстве случаев) уже не двигает данные назад. Данный принцип хорошо демонстрирует следующая схема:

Имплементация с использованием Java IO будет выглядеть несколько иначе:

ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); 

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

Представьте, что после первого вызова метода read(buffer), в буфер было считано только половину строки. Например, “Name: An”. Сможете ли вы обработать такие данные? Наверное, что нет. Вам придется ждать пока, по крайней мере, одна полная строка текста не будет считана в буфер.

Так как же вам узнать, достаточно ли данных для корректной обработки содержит буфер? А никак. Единственный вариант узнать, это посмотреть на данные, содержащиеся внутри буфера. В результате вам придется по нескольку раз проверять данные в буфере, пока они не станут доступными для корректной обработки. Это неэффективно и может негативно сказаться на дизайне программы. Например:

ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) )

Метод bufferFull() должен следить за тем, сколько данных считано в буфер и возвращать true или false, в зависимости от того, заполнен буфер или нет. Другими словами, если буфер готов к обработке, то он считается заполненным.

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

Если буфер заполнен, данные из него могут быть обработаны. Если он не заполнен вы все же будете иметь возможность обработать уже имеющиеся в нем данные, если это имеет смысл в вашем конкретном случае. В большинстве случаев – это бессмысленно.

Следующая схема демонстрирует процесс определения готовности данных в буфере для корректной обработки:

Итоги

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

Если вам необходимо управлять тысячами открытых соединений одновременно, причем каждое из них передает лишь незначительный объем данных, выбор Java NIO для вашего приложения может дать преимущество. Дизайн такого типа схематически изображен на следующем рисунке:

Если вы имеете меньшее количество соединений, по которым передаются большие объемы данных, то лучшим выбором станет классический дизайн системы ввода/вывода:

Важно понимать, что Java NIO отнюдь не является заменой Java IO. Его стоит рассматривать как усовершенствование – инструмент, позволяющий значительно расширить возможности по организации ввода/вывода. Грамотное использование мощи обоих подходов позволит вам строить хорошие высокопроизводительные системы.

Стоит заметить, что с выходом версии Java 1.7 появился еще и Java NIO.2, но присущие ему новшества касаются, в первую очередь, работы с файловым вводом/выводом, поэтому выходят за рамки этой статьи.

PS: также в данном посте использованы материалы очень достойной статьи Nino Guarnacci – «Java.nio vs Java.io»

Урок 1. Введение в Java IO

Хочу вам представить первую серию уроков на сайте, а именно серию уроков по Java IO. В этом уроке я вас познакомлю с Java IO API для чего он нужен и чем он вам поможет.

Сразу же хочу сказать читателям, которые слышали о Java NIO, то что немного позже будет и по Java NIO API серия уроков, просто для начинающих я думаю будет полезно знать и о IO API.

О Java IO

IO API – (Input & Output) в первую очередь это Java API, которые облегчают работу с потоками.

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

В Java библиотека IO API находится в пакете java.io и для того чтобы начать использовать IO достаточно импортировать данную библиотеку в ваш класс.

Input and Output – Назначение

В java.io существуют так называемые потоки ввода и вывода (InputStream and OutputStream).

В основном java.io предназначен для чтения и записи данных в ресурс:

2) при работе с сетевым подключением;

3) System.err, System.in, System.out;

4) при работе с буфером.

Процесс чтения данных из ресурса и запись их в назначенное место показан на рисунку ниже.

Программа которая должна считать данные с потока и записать в поток показана на рисунке ниже.

Как видите в этом случае Ресурс связан с InputStream /Reader, а он в свою очередь связан с программой и через него программа получает поток данных.

Программа связанна с OutputStream / Writer, и с его помощью записываем данные которые отдала программа в Destination.

Java IO – Способности

В Java IO много классов которые в основном работают с потоками чтения и записи, и решают различные задачи:

– получения доступа к файлам;

– получение сетевого соединения;

– работа с буфером;

– доступ к внутринему буферу памяти;

– чтение и запись текста;

– чтение и запись примитивных данных (long, int, float …);

– чтение и запись объектов.

Все эти возможности вам предоставит Java IO.

Классы Java IO API

Базовые

Массивы

Files

Буферизация

Это самые распространяемые по использованию классы, но в нашей серии уроков мы на примерах рассмотрим все классы Java IO API.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *