一:什么是java的I/O
I/O中的i为input即输入的意思,O为output输出的意思,所以io为java中数据的输入和输出。这里的数据即包括网络上的数据(socket)也包括本地的文件数据。IO使用流的概念来进行数据的输入和输出也就是对数据的读(read)和写(write),生活中的流如水流、电流等,这里的流指的是数据流,数据流是单向的,或者是输入流(read)或者是输出流(write),它只能单向的传输数据。
从网络上或者是从文件中读取数据其底层原理是一样的。我们知道计算机的内存空间可以分为系统的空间和用户的空间, 系统空间是操作系统操作和调用各种设备,用户空间是供用户的进程来使用的。java进行流的读写是不是直接从网卡或者磁盘中读取的而是通知系统空间中操作系统的方法,由操作系统的方法来和磁盘或者网卡进行交互读取数据。内核空间中有内核缓冲区,用户空间中有用户缓冲区,使用流读取数据的大概流程是用户进程调用内核系统方法,内核系统从磁盘或网卡上将数据读入内核缓冲区,然后再将数据从内核缓冲区复制到用户缓冲区,写数据正好相反,如下图所示:
在现实生活中IO就特别像我们去小区超市取快递,小区超市的柜台就相当于内核缓冲区,货架上的商品就相当于存放文件的磁盘,存放快递的货架就相当于网卡,我们去买东西或者取快递就相当于一个用户线程发起的任务。当我们去该超市买东西就相当于从磁盘上读取文件,超市老板将商品从货架上取下来放到柜台上就相当于内核将文件数据放到柜台上,付完款后将商品拿回家就相当于把数据从内核缓冲区复制到用户缓冲区,当我们去超市取快递就相当于使用socket从网络上获取数据,超市老板把快递放到柜台上就相当于内核从网卡中读取数据到内核缓冲区,当我们把快递取回家就相当于把数据从内核缓冲区复制到用户缓冲区,在这个过程中超市老板就相当于内核方法,当我们买东西或者取快递的时候统一由老板负责去取,而不是我们直接去取。当我们往超市寄存东西或者发快递的时候和取的过程相反。
二:四种主要的I/O模型简介
(1)同步阻塞IO(Blocking IO)
该模型读取数据的大致流程是用户进程调用内核方法,内核从网卡或磁盘上读取数据并复制 到内核缓存区,再此期间用户线程一直等待内核准备数据,当内核准备数据完成后用户线程再从内核缓冲区将数据复制到用户缓冲区,这个读取过程是一直处于阻塞状态,用户线程只能等待不能去做其他的事情,在阻塞期间是不占用cpu的,所以cpu可以去干其他的事情。java中使用流进行文件的读取和默认的socket读取网络信息都是这种模型的体现。
同样拿我们上面讲到的超市快递的例子来比喻这种模型的工作方式,当我们去取快递或者买东西的时候只能由我们亲自去超市取,并且如果快递没有到或者在等待老板取商品的过程中我们不能去干别的事只能在那等知道快递邮到或者商品取到才能回来干别的事情,这个等待就是我们所说的阻塞。
(2)同步非阻塞IO(Non-blocking IO)
这里所讲的NIO并不是java中的NIO,这里将的NIO只是一种IO模型,而java中的NIO是对(3)多路复用技术的使用的总称。
该模型读取数据的大致流程是用户进程发起读取的任务,如果系统内核还没有准备好(未从磁盘上读取数据完毕或者网卡中还没有返回的数据可供使用)就返回个状态码然后再重复的去读取,当系统内核将数据准备完成后返回数据。这种模型需要用户不停的去内核中询问直到有数据,再没有数据返回的情况下用户线程可以去做处理其他任务这就是非阻塞的实现。
同样拿我们上面讲到的超市快递的例子来比喻这种模型的工作方式,我们需要亲自的去超市中询问商品有没有准备好或者快递有没有到,如果没有准备好或者没有到就回来去做其他的事情,然后不停的去超市询问,知道快递到达或者商品准备好并取回。
(3)IO多路复用(IO Multiplexing)
该模型读取数据的大致流程是系统内核监视多个用户的读取连接,当某个连接在系统内核中有可读或可写的数据时就会将该连接返回给用户进程,用户的进程就可以通过该连接来读写数据。多路复用技术是需要操作系统支持的,windos系统用的是select并且实现的比较完善,linux系统使用的是expoll来进行实现的。
同样拿我们上面讲到的超市快递的例子来比喻这种模型的工作方式,系统内核监视各个连接就好比是超市老板顾了个员工,该员工不停的监视有那个用户的快递到了,如果到了就通知用户自己去取。
(4)异步IO(Asynchronous IO)
这种模式的工作方式和BIO模式相反,用户进程去调用系统内核告知系统内核需要调用哪个io,接下来用户线程去处理其他任务,由系统内核去去监听该io就绪好去主动通知用户进程程,用户进程收到通知后去再去处理。这种方式完全实现了异步.windows系统已经做了良好的实现,但高并发的服务器很少使用windows系统,linux系统对此的支持不够完善,所以这种模型实际的应用并不广泛。
同样拿我们上面讲到的超市快递的例子来比喻这种模型的工作方式,这就好比我们自己不需要亲自去取快递,而是打电话给超市老板当有我的快递时直接将快递送到我门口,当超市老板把快递送到我门口后再通知我,我再去开门取快递。
四种模型的比较:
1,是否阻塞:模型1是绝对阻塞的。模型2和模型3做到了部分异步,当用户线程可以去读数据的时候仍然是阻塞的。模型4完全实现了异步不阻塞。
2、对cpu的占用情况:模型1一次读取会占用cpu。模型2需要线程自己不停的去询问系统内核是否就绪,每次询问都会占用cpu,因此这种模型会很占用cpu,在实际的应用中很少使用的。模型3有一个单独的线程去调cup询问内核的使用情况,可以监控许多的读取线程,同样占用cpu,但相比模型2尤其是在高并发的情况下很值得。模型4在每次调用告知系统内核的时候回占用一次,其他情况下不占用,是非常理想的一种方式。
3、并发情况:模型1和2在高并发的情况下不仅会占用大量的线程同时对cpu的开销特别大,在高并发的情况下效率非常低。模型3是由一个线程来监视成千上万个连接,适合高并发的场景。模型4虽然支持高并发,但是需要操作系统很好的支持。
4、使用情况:在低并发的情况下使用模型1最为理想,因为这种实现简单,易于维护。在高并发的情况使用模型3,现在大部分的服务器都是使用的这种模型。模型2和4的使用很少。
关于java中对io的具体实现请看下一篇《java语言中的io简介》