前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java中是否所有的stream流都需要主动关闭

java中是否所有的stream流都需要主动关闭

作者头像
翎野君
发布2024-04-25 14:57:46
570
发布2024-04-25 14:57:46
举报
文章被收录于专栏:翎野君翎野君

流的概念

在输出数据时,内存中的特定数据排成一个序列,依次输出到文件中,这个数据序列就像流水一样源源不断地“流”到文件中,因此该数据序列称为输出流。同样,把文件中的数据输入到内存中时,这个数据序列就像流水一样“流”到内存中,因此把该数据序列称为输入流。

输入流与输出流

为什么要按照流的方式来读取和保存数据呢?

因为流可以保证原始数据的先后顺序不会被打乱,在很多情况下,这都是符合实际需求的。比如读一篇文章,肯定是希望从头到尾的读取文章,而不希望打乱文章的章节顺序。

无论是输入流还是输出流,如果数据序列中最小的数据单元是字节,那么称这种流为字节流。

数据单元是字节的字节流如果数据序列中最小的数据单元是字符,那么称这种流为字符流。

InputStream为什么设计成不能重复读呢?

“在InputStream读取的时候,会有一个pos指针,他指示每次读取之后下一次要读取的起始位置,当读到最后一个字符的时候,pos指针不会重置。” 说的也有道理,就是说InputStream的读取是单向的。但是并不是所有的InputStream实现类都是这样的实现方式。

代码语言:javascript
复制
//BufferedInputStream代码片段:  
 public synchronized int read() throws IOException {  
        if (pos >= count) {  
            fill();  
            if (pos >= count)  
                return -1;  
        }  
        return getBufIfOpen()[pos++] & 0xff;  
    }  
  
//FileInputStream代码片段:  
public native int read() throws IOException;

我们知道: Java 的List内部是使用数组实现的,遍历的时候也有一个pos指针。但是没有说List遍历一个第二次遍历就没有了。第二次遍历是创建新的Iterator,所以pos也回到了数组起始位置。对于某些InputStream当然可以也这么做。例如:ByteArrayInputStream ByteArrayInputStream就是将一个Java的byte数组保存到对象里,然后读取的时候遍历该byte数组。

代码语言:javascript
复制
public ByteArrayInputStream(byte buf[]) {  
        this.buf = buf;  
        this.pos = 0;  
        this.count = buf.length;  
}  
  
public synchronized int read() {  
        return (pos < count) ? (buf[pos++] & 0xff) : -1;  
}

就ByteArrayInputStream而言,要实现重复读取是很简单的,但是为什么没有。我想是为了遵循InputStream的统一标准。 在InputStream的read方法的注释上明确说明:

代码语言:javascript
复制
/** 
     * Reads the next byte of data from the input stream. The value byte is 
     * returned as an <code>int</code> in the range <code>0</code> to 
     * <code>255</code>. If no byte is available because the end of the stream 
     * has been reached, the value <code>-1</code> is returned. This method 
     * blocks until input data is available, the end of the stream is detected, 
     * or an exception is thrown. 
     * 
     * <p> A subclass must provide an implementation of this method. 
     * 
     * @return     the next byte of data, or <code>-1</code> if the end of the 
     *             stream is reached. 
     * @exception  IOException  if an I/O error occurs. 
     */  
    public abstract int read() throws IOException;  

当流到达末尾后,返回-1.

InputStream顾名思义就是一个单向的字节流,跟水流一样,要想再次使用就自己再去源头取一下。

你看看上面那个图就明白了,InputStream是中间的管道,并不是左右两边的桶要想喝水了,就在把水管架在水源与杯子之间,让水流到杯子里(注意:这个动作完成了之后水管里面就没有水了)。

这样看来,InputStream其实是一个数据通道,只负责数据的流通,并不负责数据的处理和存储等其他工作范畴。 前面讲过,其实有的InputStream实现类是可以实现数据的处理工作的。但是没有这么做,这就是规范和标准的重要性。

流在传输过程中的缓冲区概念

先上个例子:

代码语言:javascript
复制
public class FlushTest {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("F:\\Hello1.txt"); //大文件
FileWriter fileWriter = new FileWriter("F:\\Hello2.txt");
int readerCount = 0;
//一次读取1024个字符
char[] chars = new char[1024];
while (-1 != (readerCount = fileReader.read(chars))) {
fileWriter.write(chars, 0, readerCount);
}
}
}

这里并没有调用close()方法。close()方法包含flush()方法 ,即close会自动flush结果:

可以看到,复制的文件变小了。明显,数据有丢失,丢失的就是缓冲区“残余”的数据。在计算机层面,Java对磁盘进行操作,IO是有缓存的,并不是真正意义上的一边读一边写,底层的落盘(数据真正写到磁盘)另有方法。所以,最后会有一部分数据在内存中,如果不调用flush()方法,数据会随着查询结束而消失,这就是为什么数据丢失使得文件变小了。

BufferedOutputStream、BufferedFileWriter 同理再举个例子:

代码语言:javascript
复制
class FlushTest2{
public static void main(String[] args) throws IOException {
FileWriter fileWriter = new FileWriter("F:\\Hello3.txt");
fileWriter.write("今天打工你不狠,明天地位就不稳\n" +
"今天打工不勤快,明天社会就淘汰");
}
}

不调用flush()方法你会发现,文件是空白的,没有把数据写进来,也是因为数据在内存中而不是落盘到磁盘了。所以为了实时性和安全性,IO在写操作的时候,需要调用flush()或者close()

close() 和flush()的区别:

关close()是闭流对象,但是会先刷新一次缓冲区,关闭之后,流对象不可以继续再使用了,否则报空指针异常。

flush()仅仅是刷新缓冲区,准确的说是"强制写出缓冲区的数据",流对象还可以继续使用。

总结一下:

1、Java的IO有一个 缓冲区 的概念,不是Buffer概念的缓冲区。

2、如果是文件读写完的同时缓冲区刚好装满 , 那么缓冲区会把里面的数据朝目标文件自动进行读或写(这就是为什么总剩下有一点没写完) , 这种时候你不调用close()方法也0不会出现问题

3、如果文件在读写完成时 , 缓冲区没有装满,也没有flush(), 这个时候装在缓冲区的数据就不会自动的朝目标文件进行读或写 , 从而造成缓冲区中的这部分数据丢失 , 所以这个是时候就需要在close()之前先调用flush()方法 , 手动使缓冲区数据读写到目标文件。

举个很形象的例子加深理解:我从黄桶通过水泵把水抽到绿桶,水管就相当于缓冲区,当我看到黄桶水没有了,我立马关了水泵,但发现水管里还有水没有流到绿桶,这些残留的水就相当于内存中丢失的数据。如果此时我再把水泵打开,把水管里的水都抽了一遍,此时水管里面的水又流到了绿桶,这就相当于调用了flush()方法。

java Stream对象如果不关闭会发生什么?

比如FileStream或者说HttpClient 中的HTTPResponse,不关闭会发生什么呢?或者说调用close防范实际上在底层都做了哪些事?

看是什么类了, 不同的类的close里执行的逻辑当然是不一样的. close就是用来做收尾工作的, 如果你学过servlet, 可以认为就是servletdestroy方法. 有一些类会占用特殊资源(比如文件句柄, 线程, 数据库连接等), 而这些资源是有限的/比较消耗性能的, 而且不会自动释放(或者需要很久才能自动释放), 因此需要你在不用的时候及时释放, 避免浪费资源.

比如IO里面的:

  • FileInputStream会占用系统里的一个文件句柄, 每个进程可以打开的文件数量是有限的, 如果一直打开而不关闭, 理论上迟早会遇到无法打开的情况.
  • StringWriter就没有什么. close方法没什么卵用

Closing a StringWriter has no effect. The methods in this class can be called after the stream has been closed without generating an IOException.

ps: FileInputStreamfinalize方法会自动调用close方法. 但是需要等待很长很长时间. 所以最好自己手工调用.

一般而言, 如果是接口里有close方法, 我们调用时是不应该关注close里究竟执行了什么, 不调用是不是有坏处, 而应该是始终调用

你打开文件是会在系统里有一个文件句柄的,这个句柄数量操作系统里是有限的,如果不close,这个句柄所代表的资源就泄露了,就跟悬垂指针一样,如果量大或时间长了之后再打开文件就可能打不开了,超过了系统的限制

有没有不需要关闭的流

曾几何时,作为java程序员要记住的一条准则就是,流用完了一定要在关闭,一定要写在finally里。

代码语言:javascript
复制
finally {
	out.flush();
 	out.close();
}

但是最近发现一个stream是不需要关闭的。它就是ByteArrayOutputStream,当然还有它的妹妹ByteArrayInputStream和表哥StringWriter。道理一样,我们就不讨论亲戚们了。 作为一种OutputStream它也extendsOutputStream,自然也有继承了flush()close()。 但这2个方法的方法体为空。

代码语言:javascript
复制
    /**
     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
     * this class can be called after the stream has been closed without
     * generating an <tt>IOException</tt>.
     */
    public void close() throws IOException {
    }
    
    /***
    * OutputStream的方法,ByteArrayInputStream并未重写
    */
    public void flush() throws IOException {
    }    

究其原因也不难理解。其实ByteArrayInputStream内部实现是一个byte数组,是基于内存中字节数据的访问。并没有占用硬盘,网络等资源。就算是不关闭,用完了垃圾回收器也会回收掉。这点跟普通数组并没有区别。既然是操作内存,就要考虑到内存大小,如果字节流太大,就要考虑内存溢出的情况。

但是,作为一个蛋疼的程序员,习惯性关闭流是一个好习惯,不管三七五十八,先close掉再说,现在close是空方法,保不齐哪天就有了呢?这也是百利无一害的事,就好像保健品,吃了不治病,但是也吃不坏。

  • 结论就是:指向内存的流可以不用关闭,指向硬盘/网络等外部资源的流一定要关闭。
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 流的概念
  • InputStream为什么设计成不能重复读呢?
  • 流在传输过程中的缓冲区概念
    • close() 和flush()的区别:
    • java Stream对象如果不关闭会发生什么?
    • 有没有不需要关闭的流
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    http://www.vxiaotou.com