程序与运行时数据在内存中驻留,由CPU负责执行,涉及到数据交换的地方,如磁盘、网络等,就需要IO接口。IO中涉及到输入流 Input Stream 与输出流 Output Stream的概念,用来表达数据从一端,到达另一端的过程。
Input Stream 与 Output Stream 可以以内存作为参照标准,加载到内存的,是输入流,而从内存中输出到别的地方,如磁盘、网络的,则称为输出流。比如File存于磁盘中,程序获取File数据用来进行其它操作,这是将数据读入内存中的过程,所以为输入流,反之,程序将各种信息保存入File中,是将数据读出内存的过程,所以为输出流;再比如,网络操作,请求从客户端来到服务端,也就是数据从客户端到达了服务端,那么对于客户端,是输出流,对服务端,是输入流,响应则相反。如图:
IO原理
用户态
:对于操作系统而言,JVM只是一个用户进程(应用程序),处于用户态空间中,而处于用户态空间的进程是不能直接操作底层的硬件(磁盘/网卡)。系统调用
:区别于用户进程调用,系统调用时操作系统级别的api,比如java IO的读取数据过程(使用缓冲区),用户程序发起读操作,导致“ syscall read ”系统调用,就会把数据搬入到 一个buffer中;用户发起写操作,导致 “syscall write ”系统调用,将会把一个 buffer 中的数据 搬出去(发送到网络中 or 写入到磁盘文件)内核态
:用户态的进程要访问磁盘/网卡(也就是操作IO),必须通过系统调用,从用户态切换到内核态(中断、trap),才能完成。局部性原理
:操作系统在访问磁盘时,由于局部性原理,操作系统不会每次只读取一个字节(代价太大),而是借助硬件直接存储器存取(DMA)一次性读取一片(一个或者若干个磁盘块)数据。因此,就需要有一个“中间缓冲区”--即内核缓冲区。先把数据从磁盘读到内核缓冲区中,然后再把数据从内核缓冲区搬到用户缓冲区。
- 从进程看,分用户态和内核态 应用程序属于用户态,系统调用则内核态
- 从内存看,分用户内存和内核内存 用户内存 = 应用内存 = JVM内存 = User space 内核内存 = 系统内存 = Kernel space 引用一句话:
操作系统与Java基于流的I/O模型有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在的协助下完成的。I/O类喜欢操作小块数据——单个字节、几行文本。结果,操作系统送来整缓冲区的数据,java.io的流数据类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。操作系统喜欢整卡车地运来数据,java.io类则喜欢一铲子一铲子地加工数据。 —— 引自《JAVA NIO》
java旧的io方式,就是一个字节一个字节读取数据,这会引起频繁的用户态和系统态切换,系统开销特别大,效率极低。而基于缓冲的读写,减少这种不必要的切换,可以显著提高IO效率。NIO里ByteBuffer的设计,也是为了去贴近操作系统读取一片数据的习惯。
IO模型https://www.cnblogs.com/dolphin0520/p/3916526.html 一共有5种io模型,java nio使用的时多路复用模型。
- 阻塞io:用户进程阻塞,知道io就绪且io处理(读写)完成,阻塞才解除。
- 非阻塞IO:用户进程不阻塞,需要不断轮询io是否就绪,很占用cpu资源,所以一般不用
- 多路复用IO:用户进程阻塞,也是轮询io是否就绪,但是和非阻塞IO不一样的是,轮询io的是系统进程而非应用进程,实际使用了代理(select/poll/epoll),可以避免cpu空转,所以效率较高,而且1个进程可以管理多个socket,java nio使用的就是多路复用模型。
- 信号驱动式 I/O:(有点类似回调)用户进程不阻塞,而是安装一个信号处理函数,当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。
- 异步io:用户进程不阻塞,发起io请求(读写)后就可以做自己的事了,io就绪等待和io操作交给操作系统去完成,完成后调用用户进程处理函数,所以整个过程都是异步的。
非阻塞是用户进程去轮询io是否就绪,所以用户进程是不阻塞的。 而多路复用,用户进程是阻塞的,轮询io是否就绪是系统进程(内核)在进行。 多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
io是面向流,nio是面向缓冲,所以io性能差,这是错误的实际上,io做文件读写的速度并不比nio差,因为io也可以使用buffer缓冲区来提高性能,使用buffer来读写数据的io和nio的bytebuffer多大区别。人们常说的旧io性能低下,其实指的是不使用buffer情况下,io使用一个字节一个字节的读写的方式。 因为在 JDK 1.4 中原来的 I/O 包和 NIO 已经很好地集成了。 java.io.* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些类包含以块的形式读写数据的方法,这使得即使在更面向流的系统中,处理速度也会更快。
BIO,AIO,NIO理解:https://baijiahao.baidu.com/s?id=1666632547627347318&wfr=spider&for=pc
1)BIO:BIO通信(一请求一应答)模型图如下:
BIO通信模型的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。过程就是服务端接收到客户端的连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,然后销毁线程。
所以试想下这里有很大的问题,如果一个连接创建好了但是什么事都不做,会造成不必要的开销的。我们都知道,jvm中线程的创建和销毁成本很高。在并发量很大的情况下会导致创建的线程数急剧增长,可能会导致线程堆栈溢出、创建新线程失败等问题,服务就挂了。
2)NIO:NIO通信模型图:
这里需要了解3个概念:缓冲区、通道、多路复用器。
缓冲区:即Buffer,Buffer是一个对象,它包含一些要写入或者要读出的数据,在NIO库中,所有数据读写操作都是用缓冲区处理的。具体一些实现就不说了,通常它是一个字节数组(ByteBuffer)。
通道:即Channel,通过它读取和写入数据,就像电话线一样。这里大家是不是有想到流(Stream),区别在于Stream是单向,而Channel是双向的。主要实现呢有以下:FileChannel、DatagramChannel、SocketChannel,通过英文名是不是可以看出个大概意思:文件IO、UDP和TCP。
多路复用器:即Selector,说白了是用来管理Channel的,Channel注册在Selector中,如果某个Channel上面有TCP连接接入,会被Selector轮询出来,通过相关处理进行后续的I/O操作。
适用场景:
BIO:适用于连接数目比较小,并且一次发送大量数据的场景;
NIO:适用于连接数目多,连接比较短,常用于聊天服务器开发工作(榜上有名的Netty是很大的实践);
AIO:适用于连接数目多,连接比较长。这个目前市面上应用还不是很广泛。
BIO、NIO、AIO 有什么区别? BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
Java NIO、IO
https://lrh1993.gitbooks.io/android_interview_guide/content/java/basis/java-nio.html
轻松理解java中的IO与NIO
https://zhuanlan.zhihu.com/p/27204492
Okio好在哪
https://www.jianshu.com/p/2fff6fe403dd
Java IO、NIO原理https://www.jianshu.com/p/63d1c4476f45