I/O 分类
- 按传输数据方式:
字节流:以 byte 为基本单位进行,XXXStream 表示字节流
字符流:以字符为基本单位,字符又根据编码方式不同,一个字符对应不同大小的 byte。XXXReader、xxxWriter 表示字符流相关类
字节流可以处理任何类型的数据,如图片,视频等;字符流只能处理字符类型的数据。
- 按输入输出方向:
输入流(InXXX)、输出流(OutXXX)
Java 中的 I/O 种类
同步与异步
调用者是否主动等待调用的返回结果
同步和异步关注的是消息通信机制,即消息是怎么返回的,是直接返回还是通过回调返回。
同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起,不能执行其它业务。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
你妈妈让你烧水,小时候你比较笨啊,在那里傻等着水开(同步阻塞)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(同步非阻塞)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。
BIO、NIO 和 AIO 的区别
-
BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
-
NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数
据操作方式。什么是NIO?NIO的原理是什么机制? -
AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
Linux 5 种 I/O 模型
Linux IO 模型中,会经历数据准备(等待数据到达并赋值到内核缓冲区),数据处理(从内核拷贝到进程)两个阶段。
- Blocking IO 同步阻塞
进程阻塞在等待数据和处理数据阶段,啥也不干
- NoBlocking IO 同步非阻塞
进程轮询等待数据过程,若轮询时数据还没准备好,可以做其它事,当数据准备好之后,就阻塞在处理数据阶段,直至返回。
当对一个非阻塞 socket 执行读操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。用户进程需要不断地主动进行 read 操作,一旦数据准备好了,就会把数据拷贝到用户内存。
- IO Multiplexing 多路复用
内核每次轮询多个进程的等待数据过程,有一个准备好了,就执行后面的数据处理过程。
这种 IO 方式也称为 event driven IO,通过使用 select/poll/epoll 在单个进程中同时处理多个网络连接的 IO。例如,当用户进程调用了 select,那么整个进程会被 block,通过不断地轮询所负责的所有 socket,当某个 socket 的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从 kerne l拷贝到用户进程。
在IO复用模型中,实际上对于每一个 socket,一般都设置成为 non-blocking,但是,整个用户进程其实是一直被 block 的,先是被 select 函数 block,再是被 socket IO 第二阶段 block。
- Signal-Driven IO 信号驱动
数据准备好时,进程收到信号,然后进程进行下一步的数据处理
- Asynchronous IO
进程在数据准备过程中处理其它事情,当数据等待和数据处理都完成之后,内核会向进程发送通知。
在 linux 异步 IO 中,用户进程发起 read 操作之后,直接返回,去做其它的事。而另一方面,从 kernel 的角度,当它受到一个 asynchronous read 之后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。也就是说两个阶段都不会阻塞线程。它就像是用户进程将整个 IO 操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查 IO 操作的状态,也不需要主动的去拷贝数据。