前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java-IO 流的Close方法

Java-IO 流的Close方法

作者头像
Fisherman渔夫
发布2020-02-17 23:42:11
3.5K0
发布2020-02-17 23:42:11
举报
文章被收录于专栏:渔夫渔夫

一、在Java中为何要关闭流

GC运行的时间点是不确定的(因为是一条单独存在的线程),所以很多时候你不能直接控制什么时候发生GC。这个带来的问题有两点,一个是有时候你的内存不足需要立刻回收而GC并不会立刻运行;另外一个是因为GC运行期间会占用大量系统资源所以某些情况下你会希望把它推后,或者干脆关掉以便根据性能需求在合式的时候手动执行。

另外,GC只能回收内存。至于各种stream之类,他们下边一般还开启了各种其他的系统资源,比如文件,比如输入输出设备(键盘/屏幕等),等等。而这些设备第一是不能自动关闭(因为谁知道你程序要用它到什么时候啊),另一个系统内数量有限(比如键盘/屏幕同一时间只有一个)。最后,文件和数据库连接之类的东西还存在读写锁定的问题。这些都导致用户必须手动处理这些资源的开启和关闭。

这年头自动挡汽车都那么好了还不是有那么多人喜欢手挡,一样的。

流不关资源占着内存,你一个小的程序感觉不出来,要是好多流都不关,就会导致死机,内存泄流!建议培养良好的编码意识,一个小的程序也要吧流关了。

二、TryWithResources

这个不妨直接来看Oracle公司所提供的JavaDoc好了,毕竟可以这么说,这只是从Java SE 7开始提供的一个语法糖。 https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

官方描述:

The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.

解释一下:

使用try语句来声明一个或多个资源,这里的资源都是在程序执行完毕之后必须要被关闭的对象。TryWithResources声明确保了每一个资源最终都会在程序运行的最后被关闭。但我们要求,每一个资源对象必须实现java.lang.AutoCloseable包括实现了java.io.Closeable的对象都可以被作为资源对象。

如果在Java SE 7 之前,我们关闭一个流对象,需要如下的写法:

代码语言:javascript
复制
static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    }catch(//...){
        //...
    }finally {
        if (br != null) br.close();
    }
}

将close()方法置于finally语句块中是一个常见的做法。

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

代码语言:javascript
复制
static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }catch(//...){
        //.....
    }
}

try-with-resource 结构的用法即是,FileInputStream 类型变量就在try关键字后面的括号中声明,而finally{}的处理实际上是一样的。

一个还看不出此语法糖的优势,比如有多个流要进行关闭,在传统方法中,一个流关闭就应该对应一个try-catch语句,例子如下:

代码语言:javascript
复制
	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){
	    }

我们假设一个bw流出现了异常,那么直接被捕获了异常,那么后面两个流的close方法没能成功被调用,那么就会导致流没有被关闭,所以要写成以下写法:

代码语言:javascript
复制
finally {
	try{
		if(osw!= null){
		  osw.close();
		}
		}catch(Exception e){
		}
		
		try{
		if(fos!= null){
		  fos.close();
		}
		}catch(Exception e){
		}
}

每一次关闭流我们都单独进行一次try,而且需要写在finally中保证异常了也要执行,不要嫌弃代码繁琐因为这是必须的内容。

然而在使用了try-with-resources(注意:resources使用的是复数,说明可以一次声明多个资源)之后,代码简单多了:

代码语言:javascript
复制
try(FileOutputStream fos = new FileOutputStream("d:\\a.txt");
	OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
	BufferedWriter bw = new BufferedWriter(osw))
}catch (Exception e){
	    }

节约了很多不少额外的相似代码,这也是Java语法糖带给开发者的便利之处,这也是我们应当在Java中使用的流关闭方式。在实际开发中,还是尽量使用新特性吧!

三、包装流的关闭

引用于:Java IO包装流如何关闭? 问题:

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

问题 1 的解释:

代码语言:javascript
复制
  FileInputStream is = new FileInputStream(".");   
  BufferedInputStream bis = new BufferedInputStream(is);  
  bis.close();

从设计模式上看: java.io.BufferedInputStream是java.io.InputStream的装饰类。 BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。

因此,可以只调用外层流的close方法关闭其装饰的内层流,验证例子:(我对上述应用博文做了一些改进):

主要思路是:继承后重写close方法,提供一个额外的判断布尔值,来告诉我们内层流对象的close方法是否因为外层流对象调用close方法而调用:

代码语言:javascript
复制
import java.io.*;

/**
 * @author Fisherman
 */
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)方式关闭流,可以逐个关闭流(可能大家比较习惯吧):

代码语言:javascript
复制
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();

    }
}
代码语言:javascript
复制
报出异常:
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)

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

代码语言:javascript
复制
 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异常;

四、如何正确方式关闭流

使用try-with-resources语句,或者对每个流对象建立一套try-with语句块来进行流的关闭。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、在Java中为何要关闭流
  • 二、TryWithResources
  • 三、包装流的关闭
  • 四、如何正确方式关闭流
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com