Java 资源回收TryWithResources与IO 流的Close方法

在Java中为何要关闭流

GC运行的时间点是不确定的(因为是一条单独存在的线程),引发如下2个问题:

  1. 内存不足需要立刻回收而GC并不会立刻运行
  2. GC运行期间会占用大量系统资源所以某些情况下希望把它推后,或者干脆关掉以便根据性能需求在合式的时候手动执行。

另外,GC只能回收内存。至于各种stream之类,他们一般还开启了各种其他的系统资源,比如文件、输入输出设备(键盘/屏幕等)等等。而这些设备是不能自动关闭的。最后,文件和数据库连接之类的东西还存在读写锁定的问题。这些都导致用户必须手动处理这些资源的开启和关闭。

TryWithResources

使用try语句来声明一个或多个资源,这里的资源都是在程序执行完毕之后必须要被关闭的对象。TryWithResources声明确保了每一个资源最终都会在程序运行的最后被关闭。但我们要求,每一个资源对象必须实现java.lang.AutoCloseable。
如果在Java SE 7 之前,关闭一个流对象,需要如下的写法:

static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    
    
     try {
    
    
     	FileOutputStream fos = new FileOutputStream("d:\\a.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("java IO close test");
        
        // 从外到内顺序关闭ok
        if (bw != null) {
    
    
        	bw.close();
        }
        if (osw!= null) {
    
    
            osw.close();
        }
        if (fos!= null) {
    
    
            fos.close();
        }
    }catch (Exception e){
    
    
    	……        
    } finally {
    
    
    // 假设一个bw流出现了异常,那么直接被捕获了异常
    // 那么后面两个流的close方法没能成功被调用,那么就会导致流没有被关闭
        try{
    
    
                if(osw!= null){
    
    
                  osw.close();
                }
                }catch(Exception e){
    
    
                }
                
                try{
    
    
                if(fos!= null){
    
    
                  fos.close();
                }
                }catch(Exception e){
    
    
                }
		}
}

使用Java SE 7 之后,使用TryWithResources,我们就可以更优雅地关闭流对象了:

static String readFirstLineFromFile(String path) throws IOException {
    
    
    try(FileOutputStream fos = new FileOutputStream("d:\\a.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        BufferedWriter bw = new BufferedWriter(osw))
	}catch (Exception e){
    
    
    }
}

包装流的关闭

问题:

  1. JAVA的IO流使用了装饰模式,关闭最外面的流的时候会自动调用被包装的流的close()方吗?
  2. 如果按顺序关闭流,是从内层流到外层流关闭还是从外层到内存关闭?

问题 1 :
从设计模式上看:
java.io.BufferedInputStream是java.io.InputStream的装饰类。
BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。
因此,可以只调用外层流的close方法关闭其装饰的内层流。
验证例子:
主要思路是:继承后重写close方法,提供一个额外的判断布尔值,来告诉我们内层流对象的close方法是否因为外层流对象调用close方法而调用:

import java.io.*;

public class Test4 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        FileOutputStream fos = new FileOutputStream("d:\\a.txt");
        OutputStreamWriter_my osw = new OutputStreamWriter_my(fos, "UTF-8");
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("java IO close test");
		bw.close();
		if (osw.ifClosed) {
    
    
			System.out.println("外层流导致了内层流的关闭");
        }
	}
}
class OutputStreamWriter_my extends OutputStreamWriter {
    
    
    public OutputStreamWriter_my(OutputStream out, String charsetName) throws UnsupportedEncodingException {
    
    
        super(out, charsetName);
    }
	public boolean ifClosed = false;
	@Override
    public void close() throws IOException {
    
    
		super.close();
        ifClosed = true;
    }
}

问题 2 :
如果不想使用(1)方式关闭流,可以逐个关闭流(可能大家比较习惯吧)

public class Test4 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        FileOutputStream fos = new FileOutputStream("d:\\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();
	}
}
// 报出异常:
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:265)
        at com.fisherman.learnIO.Test4.main(Test4.java:19)

如果改为从外到内的流关闭顺序:

 public static void main(String[] args) throws Exception {
    
    
        FileOutputStream fos = new FileOutputStream("d:\\a.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("java IO close test");
		// 从外到内顺序关闭ok
        bw.close();
        osw.close();
        fos.close();
    }

程序正确执行。
一般情况下是:先打开的后关闭,后打开的先关闭
另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b
例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b
当然完全可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法
如果将节点流关闭以后再关闭处理流,会抛出IO异常;

猜你喜欢

转载自blog.csdn.net/weixin_38370441/article/details/112394700