1. 概述

在Java I/O操作中确保正确的流关闭至关重要。这对于资源管理和代码健壮性至关重要。

在这个教程中,我们将深入探讨为什么需要关闭I/O流

2. 为何不关闭I/O流时会发生什么?

在完成对I/O流的所有操作后立即显式关闭它们是一种良好的实践。忽视关闭它们可能会导致各种问题。

在本节中,我们将探讨这些问题。

2.1. 资源泄漏

每次打开一个I/O流时,它总会占用一些系统资源。直到调用close()方法,资源才会被释放。

某些I/O流实现会在finalize()方法中自动关闭自己。当垃圾收集器(GC)触发时,会调用finalize()方法。

然而,我们不能保证GC会被调用,也无法确定何时调用。有可能在GC调用之前资源就耗尽了。因此,我们不应完全依赖GC来回收系统资源。

2.2. 数据损坏

我们经常使用BufferedOutputStream包装OutputStream,以提供缓冲能力,减少每次写入操作的开销。这是一种常见的做法,旨在提高数据写入性能。

BufferedOutputStream内部的缓冲区是临时存储数据的阶段区域。当缓冲区达到特定大小或调用flush()方法时,数据将被写入目标。

当我们向BufferedOutputStream写完数据后,可能还有最后一部分数据尚未写入目标,导致数据损坏。调用close()方法会调用flush(),将缓冲区中的剩余数据写入目标。

2.3. 文件锁定

使用FileOutputStream写入文件时,某些操作系统(如Windows)会在我们的应用程序中保留文件。这会阻止其他应用在FileOutputStream关闭之前写入或访问文件。

3. 关闭I/O流

现在让我们来看看关闭Java I/O流的几种方法。这些方法有助于避免上述问题,并确保适当的资源管理。

3.1. try-catch-finally

这是传统的关闭I/O流的方法。我们在finally块中关闭I/O流,确保无论操作是否成功,close()方法都会被调用:

InputStream inputStream = null;
OutputStream outputStream = null;

try {
    inputStream = new BufferedInputStream(wrappedInputStream);
    outputStream = new BufferedOutputStream(wrappedOutputStream);
    // Stream operations...
}
finally {
    try {
        if (inputStream != null)
            inputStream.close();
    }
    catch (IOException ioe1) {
        log.error("Cannot close InputStream");
    }
    try {
        if (outputStream != null)
            outputStream.close();
    }
    catch (IOException ioe2) {
        log.error("Cannot close OutputStream");
    }
}

正如我们所展示的,close()方法也可能抛出IOException。因此,在关闭I/O流时,我们必须在finally块中添加另一个try-catch块。当处理大量I/O流时,这个过程会变得繁琐。

3.2. Apache Commons IO

Apache Commons IO 是一个功能强大的Java库,提供了用于I/O操作的实用类和方法。

要使用它,请在pom.xml中包含以下依赖项

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.1</version>
</dependency>

Apache Commons库简化了在finally块中关闭复杂任务,如I/O流:

InputStream inputStream = null;
OutputStream outputStream = null;

try {
    inputStream = new BufferedInputStream(wrappedInputStream);
    outputStream = new BufferedOutputStream(wrappedOutputStream);
    // Stream operations...
}
finally {
    IOUtils.closeQuietly(inputStream);
    IOUtils.closeQuietly(outputStream);
}

IOUtils.closeQuietly()高效地关闭I/O流,无需进行空值检查,并处理关闭过程中出现的异常。

除了IOUtils.closeQuietly()库还提供了AutoCloseInputStream类,自动关闭包裹的InputStream

InputStream inputStream = AutoCloseInputStream.builder().setInputStream(wrappedInputStream).get();

byte[] buffer = new byte[256];
while (inputStream.read(buffer) != -1) {
    // Other operations...
}

上面的例子从InputStream读取数据。AutoCloseInputStream会在到达InputStream的输入结束时自动关闭InputStream,这由InputStreamread()方法获取到的-1确定。在这种情况下,我们甚至不需要显式调用close()方法。

3.3. try-with-resources

try-with-resources语句是在Java 7中引入的。它被认为是关闭I/O流的首选方法。

这种方法允许我们在try语句中定义资源。资源是指在使用完毕后必须关闭的对象。

例如,实现AutoClosable接口的类(如InputStreamOutputStream)作为资源。在try-catch块之后,它们将自动关闭。这消除了在finally块中显式调用close()方法的需求

try (BufferedInputStream inputStream = new BufferedInputStream(wrappedInputStream);
     BufferedOutputStream outputStream = new BufferedOutputStream(wrappedOutputStream)) {
    // Stream operations...
}

Java 9进一步改进了try-with-resources语法。我们可以将资源变量声明在try-with-resources块之前,并直接在try语句中指定其变量名:

InputStream inputStream = new BufferedInputStream(wrappedInputStream);
OutputStream outputStream = new BufferedOutputStream(wrappedOutputStream);

try (inputStream; outputStream) {
    // Stream operations...
}

4. 总结

在这篇文章中,我们探讨了关闭I/O流的各种策略,从传统的在finally块中调用close()方法的方法,到Apache Commons IO等库提供的更简洁方法,以及try-with-resources的优雅之处。

随着一系列不同的技术,我们可以选择最适合我们的代码库的方法,确保顺畅且无错误的I/O操作。

如往常一样,文章中展示的源代码可在GitHub上找到。