Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions lessons/java-core/031/Resource classes. IO Streams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Классы ресурсов. 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`:

```java
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`, в `Cloaseable` – `throws IOException`).

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

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

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

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

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

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

Если в рамках этого подраздела что-то осталось непонятным – могу предложить статью на
[metanit](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` (есть в примерах статьи выше).

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

![img.png](../../../commonmedia/defaultFooter.jpg)

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

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

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

> Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)
>
> Канал: https://t.me/ViamSupervadetVadens
>
> Мой тг: https://t.me/ironicMotherfucker
>
> **Дорогу осилит идущий!**