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
,这由InputStream
的read()
方法获取到的-1
确定。在这种情况下,我们甚至不需要显式调用close()
方法。
3.3. try-with-resources
try-with-resources
语句是在Java 7中引入的。它被认为是关闭I/O流的首选方法。
这种方法允许我们在try
语句中定义资源。资源是指在使用完毕后必须关闭的对象。
例如,实现AutoClosable
接口的类(如InputStream
和OutputStream
)作为资源。在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上找到。