面试官:进行IO操作时,关闭流是否必须遵循特定顺序?

2026-04-28 11:022阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1826个文字,预计阅读时间需要8分钟。

面试官:进行IO操作时,关闭流是否必须遵循特定顺序?

最近阅读了一篇文章,动手实践后,发现了一些不同的观点。在博客中记录下来,主要研究以下两个问题:+ 装饰器的close方法是否会自动关闭被装饰的流?+ 关闭流方法是否有顺序要求?+ 装饰“

前几天看了一篇文章,自己动手试了下,发现有些不一样结论,作博客记录下,本文主要研究两个问题:

  • 包装流的close方法是否会自动关闭被包装的流?
  • 关闭流方法是否有顺序?
包装流的close方法是否会自动关闭被包装的流?

平时我们使用输入流和输出流一般都会使用buffer包装一下,直接看下面代码(这个代码运行正常,不会报错)

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); bufferedOutputStream.write("test write something".getBytes()); bufferedOutputStream.flush(); //从包装流中关闭流 bufferedOutputStream.close(); } }

下面我们来研究下这段代码的bufferedOutputStream.close();方法是否调用了fileOutputStream.close();

先看BufferedOutputStream源代码:

public class BufferedOutputStream extends FilterOutputStream { ...

可以看到它继承FilterOutputStream,并且没有重写close方法,所以直接看FilterOutputStream的源代码:

public void close() throws IOException { try { flush(); } catch (IOException ignored) { } out.close(); }

跟踪out(FilterOutputStream中):

protected OutputStream out; public FilterOutputStream(OutputStream out) { this.out = out; }

再看看BufferedOutputStream中:

public BufferedOutputStream(OutputStream out) { this(out, 8192); } public BufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }

可以看到BufferedOutputStream调用super(out);,也就是说,out.close();调用的是通过BufferedOutputStream传入的被包装的流,这里就是FileOutputStream

我们在看看其他类似的,比如BufferedWriter的源代码:

public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer(); } finally { out.close(); out = null; cb = null; } } }

通过观察各种流的源代码,可得结论:包装的流都会自动调用被包装的流的关闭方法,无需自己调用。

关闭流方法是否有顺序?

由上面的结论,就会产生一个问题:如果手动关闭被包装流会怎么样,这个关闭流有顺序吗?而实际上我们习惯都是两个流都关闭的。

首先我们来做一个简单的实验,基于第一个问题的代码上增加手动增加关闭流的代码,那么就有两种顺序:

面试官:进行IO操作时,关闭流是否必须遵循特定顺序?

1.先关闭被包装流(正常没异常抛出)

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); bufferedOutputStream.write("test write something".getBytes()); bufferedOutputStream.flush(); fileOutputStream.close();//先关闭被包装流 bufferedOutputStream.close(); } } 2.先关闭包装流(正常没异常抛出)

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); bufferedOutputStream.write("test write something".getBytes()); bufferedOutputStream.flush(); bufferedOutputStream.close();//先关闭包装流 fileOutputStream.close(); } }

上述两种写法都没有问题,我们已经知道bufferedOutputStream.close();会自动调用fileOutputStream.close();方法,那么这个方法是怎么执行的呢?我们又看看FileOutputStream的源码:

public void close() throws IOException { synchronized (closeLock) { if (closed) { return; } closed = true; } ...

可以看出它采用同步锁,而且使用了关闭标记,如果已经关闭了则不会再次操作,所以多次调用不会出现问题。

如果没有看过参考文章,我可能就会断下结论,关闭流不需要考虑顺序

我们看下下面的代码(修改自参考文章):

import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("c:\\a.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); BufferedWriter bw = new BufferedWriter(osw); bw.write("java IO close test"); // 从内带外顺序顺序会报异常 fos.close(); osw.close(); bw.close(); } }

会抛出Stream closed的IO异常:

Exception in thread "main" java.io.IOException: Stream closed at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:45) at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:118) at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207) at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129) at java.io.BufferedWriter.close(BufferedWriter.java:264) at IOTest.main(IOTest.java:18)

而如果把bw.close();放在第一,其他顺序任意,即修改成下面两种:

bw.close(); osw.close(); fos.close(); bw.close(); fos.close(); osw.close();

都不会报错,这是为什么呢,我们立即看看BufferedWriter的close源码:

public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer(); } finally { out.close(); out = null; cb = null; } } }

里面调用了flushBuffer()方法,也是抛异常中的错误方法:

void flushBuffer() throws IOException { synchronized (lock) { ensureOpen(); if (nextChar == 0) return; out.write(cb, 0, nextChar); nextChar = 0; } }

可以看到很大的一行

out.write(cb, 0, nextChar);

这行如果在流关闭后执行就会抛IO异常,有时候我们会写成:

fos.close(); fos = null; osw.close(); osw = null; bw.close(); bw = null;

这样也会抛异常,不过是由于flushBuffer()ensureOpen()抛的,可从源码中看出:

private void ensureOpen() throws IOException { if (out == null) throw new IOException("Stream closed"); } void flushBuffer() throws IOException { synchronized (lock) { ensureOpen(); if (nextChar == 0) return; out.write(cb, 0, nextChar); nextChar = 0; } }

如何防止这种情况?

直接写下面这种形式就可以:

bw.close(); bw = null;

结论:一个流上的close方法可以多次调用,理论上关闭流不需要考虑顺序,但有时候关闭方法中调用了write等方法时会抛异常。


由上述的两个结论可以得出下面的建议:

关闭流只需要关闭最外层的包装流,其他流会自动调用关闭,这样可以保证不会抛异常。如:

bw.close(); //下面三个无顺序 osw = null; fos = null; bw = null;

注意的是,有些方法中close方法除了调用被包装流的close方法外还会把包装流置为null,方便JVM回收。bw.close()中的:

public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer(); } finally { out.close(); out = null; cb = null; } } }

finally中就有把out置为null的代码,所以有时候不需要自己手动置为null。(个人建议还是写一下,不差多少执行时间)

来源:blog.csdn.net/maxwell_nc/article/details/49151005

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

本文共计1826个文字,预计阅读时间需要8分钟。

面试官:进行IO操作时,关闭流是否必须遵循特定顺序?

最近阅读了一篇文章,动手实践后,发现了一些不同的观点。在博客中记录下来,主要研究以下两个问题:+ 装饰器的close方法是否会自动关闭被装饰的流?+ 关闭流方法是否有顺序要求?+ 装饰“

前几天看了一篇文章,自己动手试了下,发现有些不一样结论,作博客记录下,本文主要研究两个问题:

  • 包装流的close方法是否会自动关闭被包装的流?
  • 关闭流方法是否有顺序?
包装流的close方法是否会自动关闭被包装的流?

平时我们使用输入流和输出流一般都会使用buffer包装一下,直接看下面代码(这个代码运行正常,不会报错)

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); bufferedOutputStream.write("test write something".getBytes()); bufferedOutputStream.flush(); //从包装流中关闭流 bufferedOutputStream.close(); } }

下面我们来研究下这段代码的bufferedOutputStream.close();方法是否调用了fileOutputStream.close();

先看BufferedOutputStream源代码:

public class BufferedOutputStream extends FilterOutputStream { ...

可以看到它继承FilterOutputStream,并且没有重写close方法,所以直接看FilterOutputStream的源代码:

public void close() throws IOException { try { flush(); } catch (IOException ignored) { } out.close(); }

跟踪out(FilterOutputStream中):

protected OutputStream out; public FilterOutputStream(OutputStream out) { this.out = out; }

再看看BufferedOutputStream中:

public BufferedOutputStream(OutputStream out) { this(out, 8192); } public BufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }

可以看到BufferedOutputStream调用super(out);,也就是说,out.close();调用的是通过BufferedOutputStream传入的被包装的流,这里就是FileOutputStream

我们在看看其他类似的,比如BufferedWriter的源代码:

public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer(); } finally { out.close(); out = null; cb = null; } } }

通过观察各种流的源代码,可得结论:包装的流都会自动调用被包装的流的关闭方法,无需自己调用。

关闭流方法是否有顺序?

由上面的结论,就会产生一个问题:如果手动关闭被包装流会怎么样,这个关闭流有顺序吗?而实际上我们习惯都是两个流都关闭的。

首先我们来做一个简单的实验,基于第一个问题的代码上增加手动增加关闭流的代码,那么就有两种顺序:

面试官:进行IO操作时,关闭流是否必须遵循特定顺序?

1.先关闭被包装流(正常没异常抛出)

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); bufferedOutputStream.write("test write something".getBytes()); bufferedOutputStream.flush(); fileOutputStream.close();//先关闭被包装流 bufferedOutputStream.close(); } } 2.先关闭包装流(正常没异常抛出)

import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); bufferedOutputStream.write("test write something".getBytes()); bufferedOutputStream.flush(); bufferedOutputStream.close();//先关闭包装流 fileOutputStream.close(); } }

上述两种写法都没有问题,我们已经知道bufferedOutputStream.close();会自动调用fileOutputStream.close();方法,那么这个方法是怎么执行的呢?我们又看看FileOutputStream的源码:

public void close() throws IOException { synchronized (closeLock) { if (closed) { return; } closed = true; } ...

可以看出它采用同步锁,而且使用了关闭标记,如果已经关闭了则不会再次操作,所以多次调用不会出现问题。

如果没有看过参考文章,我可能就会断下结论,关闭流不需要考虑顺序

我们看下下面的代码(修改自参考文章):

import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; public class IOTest { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("c:\\a.txt"); OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); BufferedWriter bw = new BufferedWriter(osw); bw.write("java IO close test"); // 从内带外顺序顺序会报异常 fos.close(); osw.close(); bw.close(); } }

会抛出Stream closed的IO异常:

Exception in thread "main" java.io.IOException: Stream closed at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:45) at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:118) at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207) at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129) at java.io.BufferedWriter.close(BufferedWriter.java:264) at IOTest.main(IOTest.java:18)

而如果把bw.close();放在第一,其他顺序任意,即修改成下面两种:

bw.close(); osw.close(); fos.close(); bw.close(); fos.close(); osw.close();

都不会报错,这是为什么呢,我们立即看看BufferedWriter的close源码:

public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer(); } finally { out.close(); out = null; cb = null; } } }

里面调用了flushBuffer()方法,也是抛异常中的错误方法:

void flushBuffer() throws IOException { synchronized (lock) { ensureOpen(); if (nextChar == 0) return; out.write(cb, 0, nextChar); nextChar = 0; } }

可以看到很大的一行

out.write(cb, 0, nextChar);

这行如果在流关闭后执行就会抛IO异常,有时候我们会写成:

fos.close(); fos = null; osw.close(); osw = null; bw.close(); bw = null;

这样也会抛异常,不过是由于flushBuffer()ensureOpen()抛的,可从源码中看出:

private void ensureOpen() throws IOException { if (out == null) throw new IOException("Stream closed"); } void flushBuffer() throws IOException { synchronized (lock) { ensureOpen(); if (nextChar == 0) return; out.write(cb, 0, nextChar); nextChar = 0; } }

如何防止这种情况?

直接写下面这种形式就可以:

bw.close(); bw = null;

结论:一个流上的close方法可以多次调用,理论上关闭流不需要考虑顺序,但有时候关闭方法中调用了write等方法时会抛异常。


由上述的两个结论可以得出下面的建议:

关闭流只需要关闭最外层的包装流,其他流会自动调用关闭,这样可以保证不会抛异常。如:

bw.close(); //下面三个无顺序 osw = null; fos = null; bw = null;

注意的是,有些方法中close方法除了调用被包装流的close方法外还会把包装流置为null,方便JVM回收。bw.close()中的:

public void close() throws IOException { synchronized (lock) { if (out == null) { return; } try { flushBuffer(); } finally { out.close(); out = null; cb = null; } } }

finally中就有把out置为null的代码,所以有时候不需要自己手动置为null。(个人建议还是写一下,不差多少执行时间)

来源:blog.csdn.net/maxwell_nc/article/details/49151005

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!