Java——IO流

来源:互联网 发布:ubuntu装hadoop 编辑:程序博客网 时间:2024/05/16 06:59

流的理解

在很多时候,流(Stream)是字节流(Byte Steram)的简称,也就是长长的一串字节,当然,除了字节流,我们还可以有视频流、音频流、数据流。流只有一个特征就是连续,流可以没有头可以没有尾,甚至可能没有绝对的位置(因为无头无尾),但是由于流是连续的,所以有相对位置。

但也有人更倾向于流是类库或执行环境中的数据流概念,而不是编程语言里的概念。这种“流”的典型代表好比Java里的InputStream,OutputStream。而说起编程语言里的Stream,应该说是一种“延迟执行”的序列构造,典型代表是Java 8里 java.util.stream 的那些东西。

参见:https://www.zhihu.com/question/27996269/answer/38960397

流有哪些分类?

可以从不同的角度对流进行分类:

  1. 处理的数据单位不同,可分为:字符流,字节流
  2. 数据流方向不同,可分为:输入流,输出流
  3. 功能不同,可分为:节点流,处理流

1 和 2 都比较好理解,对于根据功能分类的,可以这么理解。

节点流:节点流从一个特定的数据源读写数据。即节点流是直接操作文件,网络等的流,例如FileInputStream和FileOutputStream,他们直接从文件中读取或往文件中写入字节流。

处理流:“连接”在已存在的流(节点流或处理流)之上通过对数据的处理为程序提供更为强大的读写功能。过滤流是使用一个已经存在的输入流或输出流连接创建的,过滤流就是对节点流进行一系列的包装。例如BufferedInputStream和BufferedOutputStream,使用已经存在的节点流来构造,提供带缓冲的读写,提高了读写的效率,以及DataInputStream和DataOutputStream,使用已经存在的节点流来构造,提供了读写Java中的基本数据类型的功能。他们都属于过滤流。

流结构介绍

这里写图片描述

参见文章: http://www.cnblogs.com/shitouer/archive/2012/12/19/2823641.html

为什么使用缓冲区

FileOutPutStream 继承 OutputStream,并不提供 flush() 方法的重写所以无论内容多少write都会将二进制流直接传递给底层操作系统的I/O,flush无效果。而 Buffered 系列的输入输出流函数单从Buffered这个单词就可以看出他们是使用缓冲区的。应用程序每次IO都要和设备进行通信,效率很低,因此缓冲区为了提高效率,当写入设备时,先写入缓冲区,每次等到缓冲区满了时,就将数据一次性整体写入设备,避免了每一个数据都和IO进行一次交互,IO交互消耗太大。

什么时候使用flush()

 对于输出的缓冲流,写出的数据,会先写入到内存中,再使用flush方法将内存中的数据刷到硬盘。所以,在使用字符缓冲流的时候,一定要先flush,然后再close,避免数据丢失。jdk源码中有的类中的close方法会调用flush的,所以也可不需要先flush;但有的时候需要手动的调用flush, 比如 socket 套接字。
 

默认缓冲区大小

8192个字节。
可以去jdk中查看 BufferedReader 类的源代码,里面定义了 private static int defaultCharBufferSize = 8192 即8K。

使用flush()和不使用flush()效果对比参见文章:https://segmentfault.com/a/1190000003804439

为什么关闭流

java本身是带GC的,所以对象在消除引用之后,按正常是能够被回收的,那么为什么会有关闭操作?
这是为了回收系统资源,主要是端口(网络IO),文件句柄(输入输出)等,通常涉及的场景也是操作文件,网络操作、数据库应用等。对于类unix系统,所有东西都是抽象成文件的,所以可以通过lsof来观察。

在Java中如果执行过多的流操作或者开启过多未关闭的Socket,并且没有及时的关闭,就可能会出现too many open files 的错误。这就是因为系统的文件句柄数不够了. 查看文件句柄数 ulimit -n, 修改文件句柄数 ulimit -n 2048

参见文章从流关闭说起:http://mccxj.github.io/blog/20130821_java-hell-stream-close.html

流的关闭顺序

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

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

bw.close();      //最外层包装流//下面三个无顺序osw = null;fos = null;bw = null;

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

也可以使用 Try-with-resources

 try(FileInputStream input = new FileInputStream("file.txt"); BufferedInputStream bufferedInput = new BufferedInputStream(input)        ) {            int data = bufferedInput.read();            while(data != -1){                System.out.print((char) data);        data = bufferedInput.read();            }        }

上面的例子在try关键字后的括号里创建了两个资源——FileInputStream 和BufferedInputStream。当程序运行离开try语句块时,这两个资源都会被自动关闭。
这些资源将按照他们被创建顺序的逆序来关闭。首先BufferedInputStream 会被关闭,然后FileInputStream会被关闭。

参考文章
Java IO包装流如何关闭? : http://www.cnblogs.com/qqzy168/p/3670915.html (不完善)
Java IO流关闭问题的深入研究: http://blog.csdn.net/maxwell_nc/article/details/49151005 (对上面有补充)

IO流使用的设计模式

IO流主要使用了装饰模式

阻塞非阻塞与同步异步

在IO中经常听到这些名词,这里引用个不错的解释。

  • 同步与异步

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。

而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如Node.js

举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

  • 阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

参见文章:https://www.zhihu.com/question/19732473

1 0