InputStream.close: примеры (JAVA)

Примеры и разъяснение использования InputStream.close
Раздел: Потоки ввода-вывода (Streams) байтовые
InputStream.close: void

Общее описание и сигнатура

Метод InputStream.close() предназначен для завершения работы входного потока и освобождения связанных ресурсов (например, дескрипторов файлов, сетевых соединений, нативных буферов). В Java InputStream определён в java.io и реализует интерфейс Closeable, а значит метод close() имеет следующую сигнатуру:

public void close() throws java.io.IOException

Основные свойства и поведение:

  • Аргументы: метод не принимает параметров.
  • Возвращаемое значение: void, возвращает ничего.
  • Исключения: может бросить IOException, если при закрытии произошла ошибка ввода-вывода.
  • Идемпотентность: для большинства реализаций повторный вызов close() безопасен и просто игнорируется; поведение зависит от конкретного подкласса.
  • Взаимодействие с обёртками: закрытие внешнего (обёрнутого) потока обычно приводит к закрытию вложенного потока. Некоторые классы (например, FilterInputStream) закрывают делегируемый поток по умолчанию.
  • Связь с AutoCloseable: поскольку Closeable расширяет AutoCloseable, потоки можно использовать в try-with-resources, что обеспечивает автоматический вызов close() и сохранение подавленных исключений.
  • Последствия закрытия: после закрытия операции чтения или получения дескриптора обычно приводят к IOException или к возврату -1 (в случае некоторых реализаций), поэтому поток становится недоступен.

Примечание: конкретные реализации (например, ByteArrayInputStream) могут делать close() «пустой» операцией и не выбрасывать исключений.

Простые варианты использования

Несколько коротких примеров с кодом и результатом.

1) Ручное закрытие в finally (стандартный приём до Java 7):

InputStream in = null;
try {
    in = new FileInputStream("test.txt");
    int b = in.read();
    System.out.println(b);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (in != null) {
        try {
            in.close();
        } catch (IOException ignore) {
        }
    }
}
(выведет первый байт файла или исключение при ошибке)

2) Try-with-resources (рекомендуемый способ):

try (InputStream in = new FileInputStream("test.txt")) {
    byte[] buf = new byte[16];
    int read = in.read(buf);
    System.out.println(read);
} catch (IOException e) {
    e.printStackTrace();
}
(выведет количество прочитанных байт или стек при ошибке)

3) Закрытие уже закрытого потока (на многих реализациях безопасно):

ByteArrayInputStream bais = new ByteArrayInputStream(new byte[] {1,2});
bais.close();
bais.close();
System.out.println("closed twice");
closed twice

4) Ошибка при закрытии приводит к IOException:

// Симуляция через кастомный InputStream
InputStream bad = new InputStream() {
    @Override
    public int read() throws IOException { return -1; }
    @Override
    public void close() throws IOException { throw new IOException("close failed"); }
};
try {
    bad.close();
} catch (IOException e) {
    System.out.println(e.getMessage());
}
close failed

Похожие API в Java

  • OutputStream.close() - закрывает поток вывода; особенности: может потребоваться flush перед закрытием, закрытие также может выбросить IOException.
  • Reader.close() / Writer.close() - аналоги для символьных потоков в java.io.
  • java.nio.channels.Channel.close() - метод для каналов NIO; бросает IOException, часто используется вместе с ByteBuffer и каналами файлов.
  • Socket.close() - для сетевых сокетов; при закрытии разрывает соединение и может влиять на потоковую семантику (shutdown).

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

Аналоги в других языках и отличия

Краткие соответствия и примерные отличия с небольшими примерами и результатами.

  • PHP: fclose($handle). Освобождает ресурс. Пример:
$f = fopen('php://memory', 'r');
fclose($f);
var_dump(is_resource($f));
bool(false) // в большинстве версий
  • JavaScript (Node.js): stream.destroy() или закрытие файла через fs.close(). В потоках Web Streams - readable.cancel() или writable.close(). Пример (Node):
const fs = require('fs');
const fd = fs.openSync('test.txt', 'r');
fs.closeSync(fd);
console.log('closed');
closed
  • Python: file.close() и менеджер контекста with open(...) as f:. Пример:
with open('test.txt','rb') as f:
    b = f.read(1)
print(b)
b'\x..' или b'' в зависимости от содержимого
  • C#: Stream.Close() и using (вариант с Dispose). Пример:
using (var fs = new FileStream("test.txt", FileMode.Open)) {
    int b = fs.ReadByte();
    Console.WriteLine(b);
}
(первый байт или -1)
  • Go: интерфейс io.Closer и defer f.Close(). Пример:
f, _ := os.Open("test.txt")
defer f.Close()
buf := make([]byte, 1)
f.Read(buf)
fmt.Println(buf[0])
(число - значение первого байта)
  • Kotlin: использует те же java.io API и try-with-resources через use: FileInputStream(path).use { ... }.
  • Lua: file:close() для файлов, аналогично освобождает ресурс.

Отличия: в большинстве языков закрытие ресурса освобождает нативные дескрипторы, но семантика ошибок и авто-управления ресурсами различается: Java предоставляет try-with-resources и механизм подавления исключений; Python и C# имеют аналоги в виде менеджеров контекста и using.

Типичные ошибки и ситуации

  • Забыть закрыть поток - утечка дескрипторов и возможный исчерпанный пул файловых дескрипторов. Пример:
for (int i=0;i<100000;i++) {
    new FileInputStream("test.txt"); // ресурс не закрывается
}
System.out.println("done");
Возможная ошибка: Too many open files (в зависимости от ОС)
  • Подавление исключения при закрытии без логирования - потеря информации об ошибках. Неправильно:
try { in.close(); } catch (IOException ignore) { }
(информация об ошибке теряется)
  • Закрытие System.in - влияние на всю программу, так как поток глобален. Пример:
System.in.close();
int x = System.in.read(); // бросит IOException
java.io.IOException: Stream closed
  • Ожидание, что close автоматически выполнит flush аналогично OutputStream - у InputStream flush не применимо, но при комбинировании с обёртками поведение может быть неожиданным.
  • Закрытие обёрнутого потока, когда это не нужно. Решение - использовать обёртку, которая переопределяет close как no-op:
FilterInputStream noClose = new FilterInputStream(in) {
    @Override
    public void close() throws IOException { /* no-op */ }
};
noClose.close(); // не закроет in
(вложенный in остаётся открытым)

Эволюция механизма закрытия потоков

  • Java 7: добавлен try-with-resources и механизм подавления исключений (suppressed exceptions), что упростило корректное закрытие ресурсoв и учёт исключений из close().
  • Java 9: улучшение try-with-resources - переменные, эффективно финальные, можно использовать без повторного объявления в заголовке try.
  • Семантика close() в базовом API не менялась: метод остаётся void throws IOException, конкретные реализации могли получить дополнительную обработку в новых версиях, но обратная совместимость сохраняется.

Расширенные и редкие сценарии использования

Несколько подробных примеров с пояснениями.

1) Try-with-resources с несколькими ресурсами и подавленными исключениями:

Пример java
try (InputStream in = new FileInputStream("a.txt");
     InputStream in2 = new FileInputStream("b.txt")) {
    // работа с потоками
    throw new RuntimeException("work failed");
} catch (Exception e) {
    e.printStackTrace();
}
// В стеке будут видны RuntimeException и, при ошибках в close(), исключения как suppressed

Пояснение: если при закрытии одного из потоков возникнет IOException, оно добавится как suppressed к основному исключению, а не потеряется.

2) Предотвращение закрытия вложенного потока (обёртка no-close):

Пример java
class NoCloseInputStream extends FilterInputStream {
    protected NoCloseInputStream(InputStream in) { super(in); }
    @Override
    public void close() throws IOException { /* ignore */ }
}

FileInputStream fis = new FileInputStream("test.txt");
InputStream wrapper = new NoCloseInputStream(fis);
wrapper.close();
System.out.println(fis.read()); // поток fis всё ещё открыт
(значение первого байта или -1)

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

3) Ресурсное ограничение и таймаут при закрытии (через отдельный поток):

Пример java
// Пример иллюстративный: при зависании close() можно прервать поток
InputStream slow = new InputStream() {
    @Override
    public int read() throws IOException { return -1; }
    @Override
    public void close() throws IOException {
        try { Thread.sleep(10000); } catch (InterruptedException e) { throw new IOException("interrupted"); }
    }
};
Thread t = new Thread(() -> {
    try { slow.close(); } catch (IOException e) { System.out.println(e.getMessage()); }
});
t.start();
Thread.sleep(100); // даёт начало
t.interrupt();
interrupted

Пояснение: закрытие, выполняющее тяжёлую блокирующую работу, может блокировать поток выполнения. Управление прерыванием помогает избежать зависаний при завершении приложения.

4) Пользовательская реализация InputStream с освобождением нативных ресурсов:

Пример java
class NativeBackedInputStream extends InputStream {
    private long nativeHandle; // nativе ресурс
    public NativeBackedInputStream(long handle) { this.nativeHandle = handle; }
    @Override
    public int read() throws IOException { return -1; }
    @Override
    public void close() throws IOException {
        if (nativeHandle != 0) {
            // вызов native-метода освобождения
            // Native.free(nativeHandle);
            nativeHandle = 0;
        }
    }
}

NativeBackedInputStream n = new NativeBackedInputStream(123L);
try { n.close(); } catch (IOException e) { }
(освобождение нативных ресурсов выполнено)

Пояснение: при интеграции с нативным кодом важно корректно освобождать нативные дескрипторы и учесть поведение при многократном close().

5) Взаимодействие PipedInputStream и PipedOutputStream:

Пример java
PipedOutputStream out = new PipedOutputStream();
PipedInputStream in = new PipedInputStream(out);
out.write(1);
out.close();
int r = in.read();
System.out.println(r);
in.close();
1

Пояснение: закрытие выходного потока сигнализирует о конце данных для входного потока, после чего read() вернёт -1.

джава InputStream.close function comments

En
InputStream.close Closes this input stream and releases any system resources associated with the stream