Классы ресурсов. I/O Streams

Классы ресурсов. I/O Streams

Дорогу осилит идущий

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

Также вы можете встретить употребление "ресурс" по отношению к классу (или его объектам), предназначенному для работы с внешними ресурсами.

Внутренние ресурсы - это объекты, хранящиеся в памяти JVM (отсюда и разделение на внутренние и внешние ресурсы), но вряд ли вам когда-нибудь понадобится эта информация.


Ресурсы. Классы для работы с ресурсами

Для работы с ресурсами в Java (а также сторонних библиотеках и фреймворках) существует огромное количество различных классов. Их объединяет две вещи:

  1. Все они предназначены для взаимодействия с ресурсами (неожиданно);
  2. Все они являются наследниками интерфейса AutoCloseable (или его наследника Closeable).

AutoCloseable имеет лишь один метод: close(). Он необходим для закрытия ресурса.

На самом деле, мы опосредовано знакомы с некоторыми классами ресурсов:

  • InputStream. Статическое поле in у класса System имеет именно такой тип;
  • PrintStream. Одна из реализаций класса OutputStream. PrintStream – тип статического поля out в классе System;
  • Scanner. Строго говоря, он является не ресурсом, а оберткой над ресурсом, но как класс, расширяющий Closeable, он относится к классам для работы с ресурсами. Метод close() будет вызывать close() у ресурса, который был указан в конструкторе Scanner'а.


Try-with-resource

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

Скажем, если вы создали InputStream, читающий данные из файла – в памяти Java будет храниться содержимое файла, с которым работает ваш InputStream. Это может быть не критично в маленьких программах, имеющих избыток оперативной памяти. Но в больших приложениях это приведет как минимум к замедлению работы.

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

С завершением программы, ресурс будет закрыт силами операционной системы. Но если используемый вами ресурс имеет специфическую логику при закрытии (а это вполне возможно), она не отработает, что может привести к дальнейшим ошибкам.

Таким образом, для любых объектов классов, расширяющих AutoCloseable или Closeable требуется вызывать метод close() сразу после того, как работа с ресурсом завершена.

До Java 7 такую логику обычно оборачивали в try-catch:

InputStream in = null;//создание объекта может выбрасывать исключение, первично присваиваем переменной значение null

try{
  in = …//инициализация переменной in
  //Работа с переменной in
} catch(IOException e){
  //IOException – ошибка ввода/вывода. В большинстве случаев методы ресурсов будут throws IOException (или его наследников)
  //Логика обработки ошибки
} finally {
  in.close(); //при условии, что метод, где это происходит, является throws Exception (или IOException)
}

Закрытие производится в finally, т.к. при выпадении ошибки код в try может быть выполнен не до конца и нет гарантии, что метод close() будет вызван, если расположить его там. Но сам close() тоже может выбросить исключение (В AutoCloseable он помечен как throws Exception, в Cloaseablethrows IOException).

Поэтому метод, работающий ресурсами должен был либо иметь блок throws, либо finally из примера выше становился более раздутым:

…
finally {
  try {
    if(in != null) {//первично переменная была инициализирована null, мы не имеем гарантии, что дальнейшая инициализация была успешна
      in.close();
    }
  } catch(IOException ex){
    //Логика обработки ошибки
  }
}

Однако такой код был настолько объемным и настолько однотипным, что в Java 7 появилась отдельная синтаксическая конструкция, инкапсулирующая в себе работу с классами ресурсов – try-with-resource. Теперь код выше можно переписать так:

try(InputStream in = ...//инициализация переменной in) {
  //Работа с переменной in
} catch(IOException e){
  //Логика обработки ошибки
}

Таким образом, Java берет на себя закрытие ресурса, который создается в скобках после try. Также в () может быть помещено несколько ресурсов, в таком случае необходимо их указать через точку с запятой:

try(InputStream in1 = …; InputStream in2 = …)

Если в рамках этого подраздела что-то осталось непонятным – могу предложить статью на метанит: https://metanit.com/java/tutorial/6.2.php

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


I/O Streams. Reader и Writer

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

Статья, посвященная знакомству с потоками ввода и вывода не слишком сложная. Но сразу замечу, что заучивать все иерархию наследования Input- и Output- Stream нет необходимости. То же актуально и для Reader и Writer.

Кроме того, советую сформировать в голове ассоциацию:

InputStream и Reader – классы, которые читают ресурс. Т.е. передают информацию из ресурса в Java, ее можно будет записать в переменные.

OutputStream и Writer – классы, пишущие в ресурс. Они позволяют значение Java-переменных записать в ресурс.

На этом переходим к статье: https://metanit.com/java/tutorial/6.1.php

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


С теорией на сегодня все!


Я не вижу смысла давать полноценные задания по этой теме, I/O Streams и Reader /Writer достаточно низкоуровневая логика, работа с ней нужна не часто, в большинстве случаев, она сводится к вызову readAllBytes() у InputStream.

Однако для закрепления материала предлагаю попытаться считать какие-нибудь значения из консоли через System.in и поприводить их к переменным разных примитивных типов или строкам. Это может быть достаточно увлекательно, хоть и не сложно.

Подсказка: у String есть конструктор, принимающий byte[]. Как удалить из строки лишние символы вы тоже уже знаете:)


Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)

Канал: https://t.me/+relA0-qlUYAxZjI6

Мой тг: https://t.me/ironicMotherfucker

 

Дорогу осилит идущий!

Report Page