IO是什么:IO是将数据从源头通过流的方式,输出到目的地。
(非常经典的IO类归纳图片,来自snailclimb)
按操作单位分:字节流和字符流
字节流:以字节为单位,每次次读入或读出是8位数据。可以读任何类型数据。字符流:以字符为单位,每次次读入或读出是16位数据。其只能读取字符类型数据。按流向分:输出流和输入流
输出流:从内存读出到文件。只能进行写操作。输入流:从文件读入到内存。只能进行读操按操作角色分:节点流和处理流
节点流:直接与数据源相连,读入或读出。处理流:在节点流的基础上实现的,是为了更加方便读写文件而出现的字符流和字节流,我们该如何选择?
字节流是直接操作磁盘,当不需要对文件中的内容进行操作时,可以使用字节流直接存取文件。字符流是经缓冲区(内存)的,在对文件中内容进行处理的时候使用较为高效BufferReader的理解
BufferReader是 字符读入处理流 ,该类将读入的内容放在缓冲区,可以使用readLine来读取内存中文件的一行内容。Buffer(缓冲区) NIO 直接将数据读取或写入到 ByteBuffer 等对象中,而在 BIO 中,虽然也有 Buffer 的一些对象,但本质上还是通过流到缓冲区中。 Channel(通道) NIO 通过 Channel 进行读或者写操作,不像 BIO 读写需要使用不同的对象。 Channel 与 Buffer 的配合使用使得 NIO 可以异步的完成读写操作。 Selector(选择器) Selector 的出现,是为了让单个线程可以操作多个 Channel ,达到减少线程切换的操作,进而来提高相关的资源消耗。
当线程N使用 NIO 向缓冲区读写数据时,线程N还可以继续做其他工作,当 Channel 读取或写入数据到 Buffer 后线程N再去处理数据;当线程B使用 BIO 读写数据时,只有读写操作结束后,才可以做其他操作。请注意,此处描述的是数据,FileChannel,即文件的读写是不可以设置为非阻塞模式;SocketChannel,即网络编程是可以设置为非阻塞模式的。
关于 NIO 的更多解释与用例 Blocking IO / Non-blocking IO 操作文件的代码用例
(目前没有什么大型开源项目在使用,有兴趣再去了解)
FD: 对一个文件的读写操作会调用内核提供的系统命令,返回一个fd (file descriptor,文件描述符)。而对一个 socket 的读写也会有相应的描述符,称为 socket fd (socket 描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。
在内核将数据准备好之前,系统调用会一直等待套接字(Socket)的数据传输完成,默认的是阻塞方式,如下图所示流程。 对应Java 中 BIO 模型中 Socket 和 ServerSocket,当服务端或客户端调用 socket.getInputStream().read() 方法时,最终会调用底层操作系统的 recvfrom方法,OS 会判断来自网络的数据报是否准备好,当数据报准备好了之后,OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.getInputStream().read() 就会解除阻塞,并得到网络数据的结果。
BIO中的阻塞,就是阻塞在2个地方:
OS 等待数据报通过网络发送过来,如果建立连接后数据一直没过来,就会白白浪费线程的资源;将数据从内核空间拷贝到用户空间。在这2个阶段,我们的线程会一直被阻塞,啥事情都不干。
在内核将数据准备好之前,系统调用会一直等待套接字(Socket)的数据传输完成,默认的是阻塞方式,如下图所示流程。
应用程序轮询内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮询对于CPU来说是较大的浪费。 虽然 非阻塞IO 比 阻塞IO 少了一段阻塞的过程,但事实上 非阻塞IO 这种方式也是低效的,因为我们不得不使用轮询方法一直问 OS 是否有文件数据准备好。
Linux 提供 select/poll,进程通过将一个或多个 fd 传递给 select 或 poll系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪。Linux 还提供了一个 epoll系统调用,epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即执行回调函数,通知进程进行后续的数据拷贝工作。 Java中的 NIO 使用的就是 IO 复用模型,使用 SocketChannel 和 ServerSocketChannel 完成相关数据请求和接收。
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动IO 由内核通知我们何时可以开始一个 IO 操作;异步IO模型 由内核通知我们 IO操作何时已经完成。
从这五种 IO模型的结构 也可以看出,阻塞程度:阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的。
更多的介绍参考
