1. Overview
1.概述
It’s important to ensure the proper closing of IO streams within the realm of Java IO operations. This is significant for resource management and code robustness.
确保在 Java IO 操作范围内正确关闭 IO 流非常重要。这对资源管理和代码的健壮性非常重要。
In this tutorial, we’ll explore in detail why it’s necessary to close IO streams.
在本教程中,我们将详细探讨为什么必须关闭 IO 流。
2. What Happens When IO Streams Aren’t Closed?
2.当 IO 流没有关闭时会发生什么?
It’s always a good practice to explicitly close IO streams right after finishing all operations on them. Neglecting to close them may cause various issues.
在完成对 IO 流的所有操作后,显式关闭 IO 流始终是一种好的做法。忽略关闭可能会导致各种问题。
In this section, we’ll take a look at these issues.
在本节中,我们将探讨这些问题。
2.1. Resource Leakage
2.1.资源泄漏
Whenever we open an IO steam, it always takes up a bit of system resources. The resources aren’t released until the IO stream close() method is called.
每当我们打开 IO 蒸汽时,总会占用一些系统资源。在调用 IO 流 close() 方法之前,这些资源不会被释放。
Certain IO stream implementations can close themselves automatically within their finalize() method. The finalize() method is invoked whenever the garbage collector (GC) is triggered.
某些 IO 流实现可以在其 finalize() 方法中自动关闭自身。每当 垃圾收集器 (GC) 被触发时,finalize() 方法就会被调用。
However, there’s no guarantee that the GC will be invoked, nor on when it will be called. It’s possible that the resources run out before the GC is called. Therefore, we shouldn’t solely depend on GC to reclaim system resources.
但是,并不能保证 GC 会被调用,也不能保证何时会被调用。有可能在调用 GC 之前,资源就已经耗尽。因此,我们不应完全依赖 GC 来回收系统资源。
2.2. Data Corruption
2.2.数据损坏
We often wrap a BufferedOutputStream around an OutputStream to provide buffered capability to reduce the overhead of each write operation. It’s a common practice and aims to improve the performance in writing data.
我们经常将 BufferedOutputStream 包裹在 OutputStream 周围,以提供缓冲功能,从而减少每次写操作的开销。这是一种常见的做法,旨在提高写入数据的性能。
The internal buffer within the BufferedOutputStream is a staging area for data that is for temporary storage. Whenever the buffer reaches a certain size or the flush() method is called, the data will be written to the destination.
BufferedOutputStream 中的内部缓冲区是数据的暂存区。每当缓冲区达到一定大小或调用 flush() 方法时,数据就会被写入目的地。
After we finish writing data to the BufferedOutputStream, it’s possible that the last chunk of data isn’t yet written to the target, leading to data corruption. Calling the close() method invokes flush() to write the remaining data in the buffer.
在我们完成向 BufferedOutputStream 中写入数据后,最后一块数据有可能尚未写入目标,从而导致数据损坏。调用 close() 方法将调用 flush() 来写入缓冲区中的剩余数据。
2.3. File Locking
2.3.文件锁定
When we write data to a file using FileOutputStream, some operating systems such as Windows hold the file in our application. This prevents other applications from writing or even accessing the file until the FileOutputStream is closed.
当我们使用 FileOutputStream 向文件写入数据时,某些操作系统(如 Windows)会在我们的应用程序中保留该文件。在关闭 FileOutputStream 之前,其他应用程序无法写入或访问该文件。
3. Closing IO Streams
3.关闭 IO 流
Now let’s take a look at a few approaches for closing Java IO streams. These approaches help avoid the issues we discussed above and ensure proper resource management.
现在,让我们来看看关闭 Java IO 流的几种方法。这些方法有助于避免上述问题,并确保适当的资源管理。
3.1. try-catch-finally
3.1.最后尝试捕获</em
This is the traditional way of closing IO streams. We close IO streams in the finally block. This ensures the close() method is invoked no matter whether the operations are successful or not:
这是关闭 IO 流的传统方法。我们在 finally 块中关闭 IO 流。这样可以确保无论操作成功与否,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");
}
}
As we’ve demonstrated, the close() method could raise an IOException as well. Therefore, we must put another try-catch block in the finally block when closing the IO streams. This process becomes cumbersome when there are numerous IO streams we have to deal with.
正如我们所演示的,close() 方法也可能引发 IOException 异常。因此,在关闭 IO 流时,我们必须在 finally 块中放入另一个 try-catch 块。当我们需要处理大量 IO 流时,这一过程将变得非常繁琐。
3.2. Apache Commons IO
3.2.Apache Commons IO
Apache Commons IO is a versatile Java library that provides utility classes and methods for IO operations.
Apache Commons IO 是一个通用的 Java 库,为 IO 操作提供了实用类和方法。
To use it, let’s include the following dependency in our pom.xml:
要使用它,让我们在 pom.xml 中加入以下 依赖关系:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
The Apache Commons library simplifies complex tasks such as closing IO streams in a finally block:
Apache Commons 库简化了复杂的任务,例如在 finally 块中关闭 IO 流:
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() efficiently closes IO streams without the need for null checking and taking care of exceptions that occur during the closing process.
IOUtils.closeQuietly()无需空值检查即可高效关闭 IO 流,并处理关闭过程中出现的异常。
Besides IOUtils.closeQuietly(), the library also provides the AutoCloseInputStream class to automate the closure of the wrapped InputStream:
除了 IOUtils.closeQuietly() 之外,该库还提供了 AutoCloseInputStream 类,用于自动关闭封装的 InputStream :
InputStream inputStream = AutoCloseInputStream.builder().setInputStream(wrappedInputStream).get();
byte[] buffer = new byte[256];
while (inputStream.read(buffer) != -1) {
// Other operations...
}
The example above reads data from the InputStream. AutoCloseInputStream closes the InputStream automatically as soon as it reaches the end of the input, which is determined by getting -1 from the read() method in the InputStream. In this case, we don’t even need to call the close() method explicitly.
上面的示例从 InputStream 中读取数据。AutoCloseInputStream会在 InputStream 到达输入结束时自动关闭 InputStream,输入结束是通过从 InputStream 中的 read() 方法中获取 -1 来确定的。在这种情况下,我们甚至不需要明确调用 close() 方法。
3.3. try-with-resources
3.3.资源尝试</em
The try-with-resources block was introduced in Java 7. It is considered the preferred way of closing IO streams.
try-with-resources 块是在 Java 7 中引入的。它被认为是关闭 IO 流的首选方法。
This approach allows us to define resources within the try statement. A resource is an object that must be closed when we finish using it.
这种方法允许我们在try语句中定义资源。资源是一个对象,当我们使用完它时,必须将其关闭。
For instance, classes such as InputStream and OutputStream that implement the AutoClosable interface are used as resources. They’ll be automatically closed after the try-catch block. This eliminates the need to call the close() method in the finally block explicitly:
例如,实现了 AutoClosable 接口的 InputStream 和 OutputStream 等类被用作资源。它们将在 try-catch 块之后自动关闭。这消除了在 finally 块中显式调用 close() 方法的需要:
try (BufferedInputStream inputStream = new BufferedInputStream(wrappedInputStream);
BufferedOutputStream outputStream = new BufferedOutputStream(wrappedOutputStream)) {
// Stream operations...
}
Further advancements emerged in Java 9, refining the try-with-resources syntax. We can declare the resource variables ahead of the try-with-resources block, and specify their variable names directly in the try statement:
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. Conclusion
4.结论
In this article, we examined various strategies for closing IO streams, from traditional methods invoking the close() method in the finally blocks to more streamlined approaches offered by libraries like Apache Commons IO and the elegance of try-with-resources.
在本文中,我们研究了关闭 IO 流的各种策略,从在 finally 块中调用 close() 方法的传统方法,到 Apache Commons IO 等库提供的更精简的方法,以及 try-with-resources 的优雅。
With a spectrum of various techniques, we can choose the approach that aligns best with our codebase and ensures smooth and error-free IO operations.
通过各种技术,我们可以选择与代码库最匹配的方法,确保流畅、无差错的 IO 操作。
As usual, the source code presented in the article is available over on GitHub.
与往常一样,文章中介绍的源代码可在 GitHub 上获取。