JAVA几种IO工作机制及特点(二)

1.什么是IO?

1.1 什么是流?

 IO在本质上是单个字节的移动,而流可以说是字节移动的载体和方式,它不停的向目标处移动数据,我们要做的就是根据流的方向从流中读取数据或者向流中写入数据。最简单的Java流的例子就是下载电影,肯定不是等电影全部下载在内存中再保存到磁盘上,本质上是下载一个字节就保存一个字节。

  一个流,必有源和目标,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是Internet上的某个URL。流的方向是重要的,根据流的方向,流可分为两类:输入流和输出流。我们从输入流读取数据,向输出流写入数据。

     Java传统的io是基于流的io即BIO(同步阻塞IO),从jdk1.4开始提供基于块的io,即nio即同步非阻塞IO,到jdk1.7之后出现aio 异步非阻塞IO。

 

1.2 IO分类

Java对io的支持主要集中在io包下,显然可以分为下面两类:

  1. 基于字节操作的io接口:InputStream 和 OutputStream

  2. 基于字符操作的io接口:Writer 和 Reader

不管磁盘还是网络传输,最小的存储单位都是字节,但是程序中操作的数据大多都是字符形式的,所以Java也提供了字符型的流。流并不等于io,还有很重要的一点,数据的传输方式,也就是数据写到哪里的问题,主要是以下两种:

  1. 基于磁盘操作的io接口:File

  2. 基于网络操作的io接口:Socket

 

到jdk1.4以后 出现socketChannel 管道,支持 select poll epoll等通讯方式。 

 

1.3 磁盘IO和网络IO工作机制

1.3.1 磁盘IO-file

io中数据写到何处也是重要的一点,其中最主要的就是将数据持久化到磁盘。数据在磁盘上最小的描述就是文件,上层应用对磁盘的读和写都是针对文件而言的在java中,以File类来表示文件,如:

 

File file = new File("D:/test.txt");

 

  但是严格来说,File并不表示一个真实的存在于磁盘上的文件。就像上面代码的文件其实并不存在,File做的只是根据你所提供的文件描述符,返回某一路径的虚拟对象,它并不关心文件或路径是否存在,可能存在,也可能是捏造的。就好象一张名片,名片的背后代表的是人。为什么要这么设计?

以FileInputStream读取文件为例,过程是这样的:当传入一个文件路径时,会根据这个路径创建File对象,作为这个文件的一个“名片”。当我们试图通过FileInputStream对象去操作文件的时候,将会真正创建一个关联真实存在的磁盘文件的文件描述符FileDescriptor,通过FileInputStream构造方法可以看出:

 

fd = new FileDescriptor(); 

 

  如果说File是文件的名片,那么FileDescriptor就是真正指向了一个打开的文件,可以操作磁盘文件。例如FileDescriptor.sync()方法可以将缓存中的数据强制刷新到磁盘文件中。如果我们需要读取的是字符,还需要通过StreamDecoder类将字节解码成字符。至于如何从物理磁盘上读取数据,那就是操作系统做的事情了。 

图 7. 从磁盘读取文件

1.3.2 网络io-socket

大部分情况下我们使用的都是基于TCP/IP协议的流Socket,因为它是一种稳定的通信协议。Socket就像一个插座,计算机通过Socket就能和网络或者其他计算机上进行通讯;当有数据通讯的需求时,只需要建立一个Socket“插座”,通过网卡与其他计算机相连获取数据。

  Socket位于传输层和应用层之间,向应用层统一提供编程接口,应用层不必知道传输层的协议细节。Java中对Socket的支持主要是以下两种:

  (1)基于TCP的Socket:提供给应用层可靠的流式数据服务,使用TCP的Socket应用程序协议:BGP,HTTP,FTP,TELNET等。优点:基于数据传输的可靠性。

  (2)基于UDP的Socket:适用于数据传输可靠性要求不高的场合。基于UDP的Socket应用程序协议:RIP,SNMP,L2TP等。

面向连接的套接字的系统调用时序图:

socket基本流程

无连接协议的套接字调用时序图
socket基本流程

 

1.4 为什么要使用NIO?

现在互联网提供的在线服务都是走网络I/O,那么对于网络I/O,传统的阻塞式I/O,一个线程对应一个连接,采用线程池的模式在大部分场景下简单高效。当有大量的连接的时候,我们可以为每一个连接建立一个线程来操作。但是这种做法带来的缺陷也是显而易见的:

  • 硬件能够支持大量的并发。

  • 并发的数量始终有一个上限。

  • 各个线程之间的优先级不好控制。

  • 各个Client之间的交互与同步困难。

我们也可以使用一个线程来处理所有的请求,使用不阻塞的IO,轮询查询所有的Client。这种做法同样也有缺陷:无法迅速响应Client端,同时会消耗大量轮询查询的时间。所以,我们需要一种poll的模式来处理这种情况,从大量的网络连接中找出来真正需要服务的Client。这正是NIO诞生的原因:提供一种Poll的模式,在所有的Client中找到需要服务的Client,现在支持epoll。JDK中,有一个非常有意思的库:NIO(New I/O)。这个库中有3个重要的类,分别是java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

1.Channel代表一个可以被用于Poll操作的对象(可以是文件流/网络流),Channel能够被注册到一Selector中。

2.通过调用Selector的select方法可以从所有的Channel中找到需要服务的实例(Accept,read ..)。

3.Buffer对象提供读写数据的缓存。对于我们熟悉的Stream对象,Buffer提供更好的性能以及更好的编程透明性(人为控制缓存的大小以及具体的操作)。

 

 

2.BIO NIO AIO是什么,有什么特点?

这里主要讨论的是网络IO:

1.首先需要提到BIO中socket,这里面的阻塞点 accept 等待连接 ,read/write数据.

 

 

 

2.在说说NIO是中socketChannel   accept不会阻塞,而是交给一个线程去接收所有连接,将所有连接放到一个缓存队列上;最终会通过selector(可以看做是监听器)监听所有客户端的连接数据请求,会调用doselect()方法(阻塞点) 通知分发到(dispatch)到真正操作数据读写的业务线程池里,进行数据处理。

看看dubbo rpc的io处理机制:

 

3.阻塞/非阻塞,同步/异步 ?

3.1 阻塞(blocking)与非阻塞(non-blocking)IO

 

  IO的阻塞、非阻塞主要表现在一个IO操作过程中,如果有些操作很慢,比如读操作时需要准备数据,那么当前IO进程是否等待操作完成,还是得知暂时不能操作后先去做别的事情?一直等待下去,什么事也不做直到完成,这就是阻塞。抽空做些别的事情,这是非阻塞。

  非阻塞IO会在发出IO请求后立即得到回应,即使数据包没有准备好,也会返回一个错误标识,使得操作进程不会阻塞在那里。操作进程会通过多次请求的方式直到数据准备好,返回成功的标识。

 

3.2同步(synchronous)与异步(asynchronous)IO

  先来看看正式点的定义,POSIX标准将IO模型分为了两种:同步IO和异步IO,Richard Stevens在《Unix网络编程卷》中也总结道:

 

A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;An asynchronous I/O operation does not cause the requesting process to be blocked;

 

  可以看出,判断同步和异步的标准在于:一个IO操作直到完成,是否导致程序进程的阻塞。如果阻塞就是同步的,没有阻塞就是异步的。这里的IO操作指的是真实的IO操作,也就是数据从内核拷贝到系统进程(读)的过程。

 

 

 

参考:

1.细说JAVA IO

2.IO阻塞与非阻塞

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值