Отличие пакетов io и nio inputstream от reader

Обновлено: 07.07.2024

Узнайте о библиотеках ввода-вывода Java и NIO и о том, чем они отличаются.

1. Обзор

2. Основные характеристики

Давайте начнем с рассмотрения ключевых функций обоих пакетов.

2.1. ЭТА java.эта

В java.io пакет был представлен в Java 1.0 , с Читателем представлен в Java 1.1. Он обеспечивает:

2.2. NIO – java.nio

Пакет java.nio был представлен в Java 1.4 и обновлен в Java 1.7 (NIO.2) с улучшенными файловыми операциями и асинхронным каналом . Он обеспечивает:

  • Буфер для одновременного чтения фрагментов данных
  • Кодировщик символов – для сопоставления необработанных байтов с читаемыми символами/из них
  • Канал – для связи с внешним миром
  • Селектор – для включения мультиплексирования на Выбираемом канале и предоставления доступа к любому каналу , готовому для ввода-вывода
  • неблокирующий режим-читать все, что готово

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

3. Настройте Наш Тестовый Сервер

Здесь мы будем использовать WireMock для имитации другого сервера, чтобы мы могли запускать наши тесты независимо.

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

Давайте добавим зависимость Maven для WireMock с тестом областью действия:

Теперь, когда мы настроили наш макет сервера, мы готовы провести некоторые тесты.

4. Блокировка ввода – вывода- java.io

В этом примере мы создадим запрос GET для получения наших ресурсов. Во-первых, давайте создадим Сокет для доступа к порту , который прослушивает наш сервер WireMock:

4.2. Дождитесь ответа

Давайте используем reader.readLine() для блокировки, дождемся полной строки, а затем добавим строку в наш магазин. Мы будем продолжать читать, пока не получим null, , который указывает на конец потока:

5. Неблокирующий ввод – вывод-java.nio

Теперь давайте посмотрим, как неблокирующая модель ввода-вывода nio пакета работает с тем же примером.

Во-первых, давайте откроем наш SocketChannel :

5.2. Прочитайте ответ

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

Поскольку мы будем обрабатывать текст, нам понадобится ByteBuffer для необработанных байтов и Буфер символов для преобразованных символов (с помощью кодировщика ):

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

Обратите внимание, что если нам нужна особенно высокая производительность, мы можем создать MappedByteBuffer в собственной памяти, используя ByteBuffer.allocateDirect() . Однако в нашем случае использование allocate() из стандартной кучи достаточно быстро.

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

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

Наш SocketChannel.read() возвращает количество прочитанных байтов , которые могут быть записаны в наш буфер. Это будет равно -1, если сокет был отключен.

Когда в нашем буфере не останется места, потому что мы еще не обработали все его данные, то SocketChannel.read() вернет ноль прочитанных байтов, но наш buffer.position() все равно будет больше нуля.

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

5.3. Хранение Данных Из Нашего Буфера

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

Когда я начал изучать стандартный ввод/вывод в 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) и получать только то, что доступно на данный момент, или вообще ничего, если доступных данных пока нет. Вместо того, чтобы оставаться заблокированным пока данные не станут доступными для считывания, поток выполнения может заняться чем-то другим.

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

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

Таким образом неблокирующий режим 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. Представьте, что вы проводите считывание строк текстовой информации:

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


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

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


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


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

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

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

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

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

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

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


Итоги

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

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


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


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

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

Потоки ввода/вывода в Java

В чём заключается разница между IO и NIO?

  • Java IO (input-output) является потокоориентированным, а Java NIO (new/non-blocking io) – буфер-ориентированным. Потокоориентированный ввод/вывод подразумевает чтение/запись из потока/в поток одного или нескольких байт в единицу времени поочередно. Данная информация нигде не кэшируются. Таким образом, невозможно произвольно двигаться по потоку данных вперед или назад. В Java NIO данные сначала считываются в буфер, что дает больше гибкости при обработке данных.
  • Потоки ввода/вывода в Java IO являются блокирующими. Это значит, что когда в потоке выполнения вызывается read() или write() метод любого класса из пакета java.io.* , происходит блокировка до тех пор, пока данные не будут считаны или записаны. Поток выполнения в данный момент не может делать ничего другого. Неблокирующий режим Java NIO позволяет запрашивать считанные данные из канала (channel) и получать только то, что доступно на данный момент, или вообще ничего, если доступных данных пока нет. Вместо того, чтобы оставаться заблокированным пока данные не станут доступными для считывания, поток выполнения может заняться чем-то другим. Тоже самое справедливо и для неблокирующего вывода. Поток выполнения может запросить запись в канал некоторых данных, но не дожидаться при этом пока они не будут полностью записаны.
  • В Java NIO имеются селекторы, которые позволяют одному потоку выполнения мониторить несколько каналов ввода. Т.е. существует возможность зарегистрировать несколько каналов с селектором, а потом использовать один поток выполнения для обслуживания каналов, имеющих доступные для обработки данные, или для выбора каналов, готовых для записи.

Какие особенности NIO вы знаете?

  • Каналы и селекторы: NIO поддерживает различные типы каналов. Канал является абстракцией объектов более низкого уровня файловой системы (например, отображенные в памяти файлы и блокировки файлов), что позволяет передавать данные с более высокой скоростью. Каналы не блокируются и поэтому Java предоставляет еще такие инструменты, как селектор, который позволяет выбрать готовый канал для передачи данных, и сокет, который является инструментом для блокировки.
  • Буферы: имеет буферизация для всех классов-обёрток примитивов (кроме Boolean). Появился абстрактный класс Buffer, который предоставляет такие операции, как clear, flip, mark и т.д. Его подклассы предоставляют методы для получения и установки данных.
  • Кодировки: появились кодеры и декодеры для отображения байт и символов Unicode. к оглавлению

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

Какие существуют виды потоков ввода/вывода?

Назовите основные классы потоков ввода/вывода.

Разделяют два вида потоков ввода/вывода:

  • байтовые - java.io.InputStream , java.io.OutputStream ;
  • символьные - java.io.Reader , java.io.Writer .

В каких пакетах расположены классы потоков ввода/вывода?

java.io , java.nio . Для работы с потоками компрессированных данных используются классы из пакета java.util.zip

Какие подклассы класса InputStream вы знаете, для чего они предназначены?

  • InputStream - абстрактный класс, описывающий поток ввода;
  • BufferedInputStream - буферизованный входной поток;
  • ByteArrayInputStream позволяет использовать буфер в памяти (массив байтов) в качестве источника данных для входного потока;
  • DataInputStream - входной поток для байтовых данных, включающий методы для чтения стандартных типов данных Java;
  • FileInputStream - входной поток для чтения информации из файла;
  • FilterInputStream - абстрактный класс, предоставляющий интерфейс для классов-надстроек, которые добавляют к существующим потокам полезные свойства;
  • ObjectInputStream - входной поток для объектов;
  • StringBufferInputStream превращает строку ( String ) во входной поток данных InputStream ;
  • PipedInputStream реализует понятие входного канала;
  • PushbackInputStream - разновидность буферизации, обеспечивающая чтение байта с последующим его возвратом в поток, позволяет «заглянуть» во входной поток и увидеть, что оттуда поступит в следующий момент, не извлекая информации.
  • SequenceInputStream используется для слияния двух или более потоков InputStream в единый.

Для чего используется PushbackInputStream ?

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

У класса есть дополнительный метод unread().

Для чего используется SequenceInputStream ?

Класс SequenceInputStream позволяет сливать вместе несколько экземпляров класса InputStream . Конструктор принимает в качестве аргумента либо пару объектов класса InputStream , либо интерфейс Enumeration .

Во время работы класс выполняет запросы на чтение из первого объекта класса InputStream и до конца, а затем переключается на второй. При использовании интерфейса работа продолжится по всем объектам класса InputStream . По достижении конца, связанный с ним поток закрывается. Закрытие потока, созданного объектом класса SequenceInputStream , приводит к закрытию всех открытых потоков.

Какой класс позволяет читать данные из входного байтового потока в формате примитивных типов данных?

Класс DataInputStream представляет поток ввода и предназначен для записи данных примитивных типов, таких, как int , double и т.д. Для каждого примитивного типа определен свой метод для считывания:

Какие подклассы класса OutputStream вы знаете, для чего они предназначены?

  • OutputStream - это абстрактный класс, определяющий потоковый байтовый вывод;
  • BufferedOutputStream - буферизированный выходной поток;
  • ByteArrayOutputStream - все данные, посылаемые в этот поток, размещаются в предварительно созданном буфере;
  • DataOutputStream - выходной поток байт, включающий методы для записи стандартных типов данных Java;
  • FileOutputStream - запись данных в файл на физическом носителе;
  • FilterOutputStream - абстрактный класс, предоставляющий интерфейс для классов-надстроек, которые добавляют к существующим потокам полезные свойства;
  • PrintStream - выходной поток, включающий методы print() и println() ;
  • ObjectOutputStream - выходной поток для записи объектов;
  • PipedOutputStream реализует понятие выходного канала.

Какие подклассы класса Reader вы знаете, для чего они предназначены?

  • Reader - абстрактный класс, описывающий символьный ввод;
  • BufferedReader - буферизованный входной символьный поток;
  • CharArrayReader - входной поток, который читает из символьного массива;
  • FileReader - входной поток, читающий файл;
  • FilterReader - абстрактный класс, предоставляющий интерфейс для классов-надстроек;
  • InputStreamReader - входной поток, транслирующий байты в символы;
  • LineNumberReader - входной поток, подсчитывающий строки;
  • PipedReader - входной канал;
  • PushbackReader - входной поток, позволяющий возвращать символы обратно в поток;
  • StringReader - входной поток, читающий из строки.

Какие подклассы класса Writer вы знаете, для чего они предназначены?

  • Writer - абстрактный класс, описывающий символьный вывод;
  • BufferedWriter - буферизованный выходной символьный поток;
  • CharArrayWriter - выходной поток, который пишет в символьный массив;
  • FileWriter - выходной поток, пишущий в файл;
  • FilterWriter - абстрактный класс, предоставляющий интерфейс для классов-надстроек;
  • OutputStreamWriter - выходной поток, транслирующий байты в символы;
  • PipedWriter - выходной канал;
  • PrintWriter - выходной поток символов, включающий методы print() и println() ;
  • StringWriter - выходной поток, пишущий в строку;

В чем отличие класса PrintWriter от PrintStream ?

Прежде всего, в классе PrintWriter применен усовершенствованный способ работы с символами Unicode и другой механизм буферизации вывода: в классе PrintStream буфер вывода сбрасывался всякий раз, когда вызывался метод print() или println() , а при использовании класса PrintWriter существует возможность отказаться от автоматического сброса буферов, выполняя его явным образом при помощи метода flush() .

Кроме того, методы класса PrintWriter никогда не создают исключений. Для проверки ошибок необходимо явно вызвать метод checkError() .

Чем отличаются и что общего у InputStream , OutputStream , Reader , Writer ?

  • InputStream и его наследники - совокупность для получения байтовых данных из различных источников;
  • OutputStream и его наследники - набор классов, определяющих потоковый байтовый вывод;
  • Reader и его наследники определяют потоковый ввод символов Unicode;
  • Writer и его наследники определяют потоковый вывод символов Unicode.

Какие классы позволяют преобразовать байтовые потоки в символьные и обратно?

  • OutputStreamWriter — «мост» между классом OutputStream и классом Writer . Символы, записанные в поток, преобразовываются в байты.
  • InputStreamReader — аналог для чтения. При помощи методов класса Reader читаются байты из потока InputStream и далее преобразуются в символы.

Какие классы позволяют ускорить чтение/запись за счет использования буфера?

  • BufferedInputStream(InputStream in) / BufferedInputStream(InputStream in, int size) ,
  • BufferedOutputStream(OutputStream out) / BufferedOutputStream(OutputStream out, int size) ,
  • BufferedReader(Reader r) / BufferedReader(Reader in, int sz) ,
  • BufferedWriter(Writer out) / BufferedWriter(Writer out, int sz)

Какой класс предназначен для работы с элементами файловой системы?

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

Какие методы класса File вы знаете?

Наиболее используемые методы класса File :

  • boolean createNewFile() : делает попытку создать новый файл;
  • boolean delete() : делает попытку удалить каталог или файл;
  • boolean mkdir() : делает попытку создать новый каталог;
  • boolean renameTo(File dest) : делает попытку переименовать файл или каталог;
  • boolean exists() : проверяет, существует ли файл или каталог;
  • String getAbsolutePath() : возвращает абсолютный путь для пути, переданного в конструктор объекта;
  • String getName() : возвращает краткое имя файла или каталога;
  • String getParent() : возвращает имя родительского каталога;
  • boolean isDirectory() : возвращает значение true , если по указанному пути располагается каталог;
  • boolean isFile() : возвращает значение true , если по указанному пути находится файл;
  • boolean isHidden() : возвращает значение true , если каталог или файл являются скрытыми;
  • long length() : возвращает размер файла в байтах;
  • long lastModified() : возвращает время последнего изменения файла или каталога;
  • String[] list() : возвращает массив файлов и подкаталогов, которые находятся в определенном каталоге;
  • File[] listFiles() : возвращает массив файлов и подкаталогов, которые находятся в определенном каталоге.

Что вы знаете об интерфейсе FileFilter ?

Интерфейс FileFilter применяется для проверки, попадает ли объект File под некоторое условие. Этот интерфейс содержит единственный метод boolean accept(File pathName) . Этот метод необходимо переопределить и реализовать. Например:

Как выбрать все элементы определенного каталога по критерию (например, с определенным расширением)?

Метод File.listFiles() возвращает массив объектов File , содержащихся в каталоге. Метод может принимать в качестве параметра объект класса, реализующего FileFilter . Это позволяет включить в список только те элементы, для которых метод accept возвращает true (критерием может быть длина имени файла или его расширение).

Что вы знаете о RandomAccessFile ?

Класс java.io.RandomAccessFile обеспечивает чтение и запись данных в произвольном месте файла. Он не является частью иерархии InputStream или OutputStream . Это полностью отдельный класс, имеющий свои собственные (в большинстве своем native) методы. Объяснением этого может быть то, что RandomAccessFile имеет во многом отличающееся поведение по сравнению с остальными классами ввода/вывода так как позволяет, в пределах файла, перемещаться вперед и назад.

RandomAccessFile имеет такие специфические методы как:

  • getFilePointer() для определения текущего местоположения в файле;
  • seek() для перемещения на новую позицию в файле;
  • length() для выяснения размера файла;
  • setLength() для установки размера файла;
  • skipBytes() для того, чтобы попытаться пропустить определённое число байт;
  • getChannel() для работы с уникальным файловым каналом, ассоциированным с заданным файлом;
  • методы для выполнения обычного и форматированного вывода из файла ( read() , readInt() , readLine() , readUTF() и т.п.);
  • методы для обычной или форматированной записи в файл с прямым доступом ( write() , writeBoolean() , writeByte() и т.п.).

Так же следует отметить, что конструкторы RandomAccessFile требуют второй аргумент, указывающий необходимый режим доступа к файлу - только чтение ( "r" ), чтение и запись ( "rw" ) или иную их разновидность.

Какие режимы доступа к файлу есть у RandomAccessFile ?

  • "r" открывает файл только для чтения. Запуск любых методов записи данных приведет к выбросу исключения IOException .
  • "rw" открывает файл для чтения и записи. Если файл еще не создан, то осуществляется попытка создать его.
  • "rws" открывает файл для чтения и записи подобно "rw" , но требует от системы при каждом изменении содержимого файла или метаданных синхронно записывать эти изменения на физический носитель.
  • "rwd" открывает файл для чтения и записи подобно "rws" , но требует от системы синхронно записывать изменения на физический носитель только при каждом изменении содержимого файла. Если изменяются метаданные, синхронная запись не требуется.

Какие классы поддерживают чтение и запись потоков в компрессированном формате?

  • DeflaterOutputStream - компрессия данных в формате deflate.
  • Deflater - компрессия данных в формат ZLIB
  • ZipOutputStream - потомок DeflaterOutputStream для компрессии данных в формат Zip.
  • GZIPOutputStream - потомок DeflaterOutputStream для компрессии данных в формат GZIP.
  • InflaterInputStream - декомпрессия данных в формате deflate.
  • Inflater - декомпрессия данных в формате ZLIB
  • ZipInputStream - потомок InflaterInputStream для декомпрессии данных в формате Zip.
  • GZIPInputStream - потомок InflaterInputStream для декомпрессии данных в формате GZIP.

Класс System позволяет вам перенаправлять стандартный ввод, вывод и поток вывода ошибок, используя простой вызов статического метода:

  • setIn(InputStream) - для ввода;
  • setOut(PrintStream) - для вывода;
  • setErr(PrintStream) - для вывода ошибок.

Какой символ является разделителем при указании пути в файловой системе?

Для различных операционных систем символ разделителя различается. Для Windows это \ , для Linux - / .

В Java получить разделитель для текущей операционной системы можно через обращение к статическому полю File.separator .

Что такое «абсолютный путь» и «относительный путь»?

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

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

Что такое «символьная ссылка»?

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

Обработка ввода и вывода - обычные задачи для программистов Java. В этом руководстве мы рассмотрим исходные библиотеки java.io (IO) и новые библиотеки java.nio (NIO) и их различия при обмене данными по сети.

2. Основные характеристики

Начнем с рассмотрения основных функций обоих пакетов.

2.1. IO - java.io

Пакет java.io был представлен в Java 1.0 , а Reader - в Java 1.1. Это обеспечивает:

2.2. НИО - java.nio

Пакет java.nio был представлен в Java 1.4 и обновлен в Java 1.7 (NIO.2) с расширенными файловыми операциями и ASynchronousSocketChannel . Это обеспечивает:

  • Буфер- для одновременного чтения фрагментов данных
  • CharsetDecoder - для отображения сырых байтов в / из читаемых символов
  • Канал - для общения с внешним миром
  • Selector - для того, чтобы мультиплексирования на SelectableChannel и обеспечивает доступ к любому каналу s, которые готовы к I / O
  • неблокирующий режим - читать все, что готово

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

3. Настройте наш тестовый сервер

Здесь мы будем использовать WireMock для моделирования другого сервера, чтобы мы могли запускать наши тесты независимо.

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

Давайте добавим зависимость Maven для WireMock с областью тестирования :

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

4. Блокировка ввода-вывода - java.io

В этом примере мы создадим запрос GET для извлечения наших ресурсов. Во-первых, давайте создадим Socket для доступа к порту, который прослушивает наш сервер WireMock:

4.2. Ждите ответа

Давайте откроем InputStream в сокете для доступа к ответу, прочтем поток с помощью BufferedReader и сохраним его в StringBuilder :

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

5. Неблокирующий ввод-вывод - java.nio

Теперь давайте посмотрим, как работает неблокирующая модель ввода-вывода пакета nio на том же примере.

Сначала давайте откроем наш SocketChannel :

5.2. Прочитать ответ

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

Поскольку мы будем обрабатывать текст, нам понадобится ByteBuffer для необработанных байтов и CharBuffer для преобразованных символов (с помощью CharsetDecoder ):

В нашем CharBuffer останется свободное место, если данные будут отправлены в многобайтовом наборе символов.

Note that if we need especially fast performance, we can create a MappedByteBuffer in native memory using ByteBuffer.allocateDirect(). However, in our case, using allocate() from the standard heap is fast enough.

When dealing with buffers, we need to know how big the buffer is (the capacity), where we are in the buffer (the current position), and how far we can go (the limit).

So, let's read from our SocketChannel, passing it our ByteBuffer to store our data. Our read from the SocketChannel will finish with our ByteBuffer‘s current position set to the next byte to write to (just after the last byte written), but with its limit unchanged:

Our SocketChannel.read() returns the number of bytes read that could be written into our buffer. This will be -1 if the socket was disconnected.

When our buffer doesn't have any space left because we haven't processed all its data yet, then SocketChannel.read() will return zero bytes read but our buffer.position() will still be greater than zero.

Since our data may arrive in parts, let's wrap our buffer-reading code in a loop with termination conditions to check if our socket is still connected or if we've been disconnected but still have data left in our buffer:

And let's not forget to close() our socket (unless we opened it in a try-with-resources block):

5.3. Storing Data From Our Buffer

The response from the server will contain headers, which may make the amount of data exceed the size of our buffer. So, we'll use a StringBuilder to build our complete message as it arrives.

To store our message, we first decode the raw bytes into characters in our CharBuffer. Then we'll flip the pointers so that we can read our character data, and append it to our expandable StringBuilder. Lastly, we'll clear the CharBuffer ready for the next write/read cycle.

Итак, теперь давайте реализуем наш полный метод storeBufferContents (), передавая наши буферы CharsetDecoder и StringBuilder :

Читайте также: