java 中需要手动关闭的流

55 篇文章 0 订阅
36 篇文章 1 订阅
Java程序中关闭流至关重要,因为流不关闭可能导致内存泄漏、资源耗尽甚至系统性能下降。GC虽能回收内存,但无法处理其他系统资源如文件、网络连接等。使用try-with-resources语句可以确保流在使用完毕后被正确关闭,避免出现端口占用、文件句柄泄露等问题。在处理装饰模式下的流时,关闭最外层流即可关闭所有内层流。正确的关闭顺序是从外到内,遵循依赖关系。
摘要由CSDN通过智能技术生成

在Java中为何要关闭流

GC运行的时间点不确定的(因为是一条单独存在的线程),所以很多时候你不能直接控制什么时候发生GC

这个带来的问题有两点

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

另外,GC只能回收内存。至于各种stream之类,他们接下来一般还开启了各种其他的系统资源,比如文件,比如输入输出设备(键盘/屏幕等),等等。

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

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

不主动close并且未触发gc条件,那么连接句柄一直被占用着,如果此时连接使用的是连接池方式,将造成连接池中的连接不能及时回收,一但占满就会造成后续获取连接进行操作的阻塞或失败,如果io占用着文件句柄未及时释放,也会造成文件无法被删除的情况

如果堆状态一直无法到达gc条件就麻烦大了【比如我用一个程序仅仅是使用tcp连接做一些数据的同步操作,若此时是用了连接池,一旦用光连接池中的连接,且用完后都没有主动释放,那么整个程序将阻塞住,因为阻塞造成没有后续对象创建无法触发内存gc条件,此时程序将彻底假死】,所以在finally中及时调用close是非常必要的

示例

HttpClient的http请求就要每次关闭response

端口占用,一般发生在网络连接上,比如HttpClient的http请求就要每次关闭response,比如连接数据库的连接每次也要close(),不然该端口一直被进程占用,别的程序想用该端口,就会报错端口已被占用,或者系统的端口被占用完而报错。

文件句柄占用

当用io流读写文件时,该进程会打开该文件,即占用该文件的句柄,如果最后不释放,别的进程要想写该文件,就会报错该文件已被别的进程占用,相信大家都熟悉这个错误。又或者每打开一个文件后都不释放资源,到一定程度,程序会报错too many open files

其他

Reader, Writer, InputStreamReader, OutputStreamWeiter, FileReader, FileWriter,

BufferedReader, BufferedWriter, FileInputStream, FileOutputStream ……

zipOutputStream
FFmpegFrameGrabber
WebSocketSession
Connection
Channel
MPushWrapper
ResponseBody.body()
java.sql:
Connection, Statement, ResultSet ……
先rs.close()再stmt.close()最后conn.close()

写法

try-with-resource

使用try语句声明一个或多个资源,这里的资源都是在程序执行完毕之后必须要被关闭的对象

TryWithResources声明确保了每一个资源最终都会在程序运行的最后被关闭。但我们要求,

每一个资源对象实现java.lang.AutoCloseable包括实现了java.io.Closeable的对象都可以被作为资源对象。

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

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    }catch(//...){
        //...
    }finally {close()方法置于finally语句块中是一个常见的做法
        if (br != null) br.close();
    }
}

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

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语句,例子如下

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方法没能成功被调用,那么就会导致流没有被关闭,所以要写成以下写法:

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使用的是复数,说明可以一次声明多个资源)之后,代码简单多了:

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

包装流的关闭

问题

JAVA的IO流使用了装饰模式,关闭最外面的流的时候会自动调用被包装的流的close()方吗?

如果按顺序关闭流,是从内层流到外层流关闭还是从外层到内存关闭?

  FileInputStream is = new FileInputStream(".");   
  BufferedInputStream bis = new BufferedInputStream(is);  
  bis.close();

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

因此,可以只调用外层流close方法关闭其装饰的内层流

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

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)方式关闭流,可以逐个关闭流

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异常;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值