IO,面向流(Stream)
面向流意味着从流中读取一个或多个字节,拿到读取的这些做什么,你说的算,没有任何缓存,
接收或者发送的数据是缓存到操作系统中的,流就像一根水管一样,从操作系统的缓存中读取数据,
而且只能顺序从流中读取数据,
如果要跳过一些字节或者再读取已经读过的字节,必须将流中读取的数据先缓存起来
NIO,面向块(buffer)
面向块,处理方式会有所不同,数据先被读写到buffer中,根据需要可以自己控制读取什么位置的数据,
但由此需要额外做的工作就是检查你需要的数据是否已经全部读取到buffer中,
还要保证当再有更多数据进入buffer中时,buffer中未处理的数据不会被覆盖
IO,阻塞的
当一条线程执行read或者write方法时,会一直阻塞,
直到读取到了一些数据或者要写出的数据已经被全部写出,
在此期间该线程不能做任何其他的事情
NIO,有阻塞模式(与IO基本一致)和非阻塞模式
读:允许一条线程从channel中读取数据,通过返回值判断buffer中是否有数据,
如果没有数据,NIO不会阻塞,(因为不阻塞这条线程就可以去做其他的事情,过一段时间再回来判断下有没有数据)
写:允许一条线程将buffer中的数据写入channel,
它不会等待数据全部写完才会返回,而是调用完write方法就会继续向下执行
Selectors
让一个线程管理多个channel
向一个selector上注册多个channel,调用selector的select()方法,
判断是否有新的连接进来;或者已经在selector上注册时是否有数据进入
NIO和IO对设计的影响
处理数据的方式
比如,在io模式中,需要一个字节一个字节的从InputStream/Reader中读取数据
Name:Anna
Age:25
Phone:18937636253
处理上述文本行的流的代码应该像下面这样
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String name = reader.readLine();
String Age= reader.readLine();
String Phone= reader.readLine();
一旦reader.readLine()方法返回,可以确定整行已被读取,readLine()阻塞,直到一整行都被读取
然而,NIO的实现则复杂很多
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(!bufferFull(bytesRead)){
bytesRead = inChannel.read(buffer);
}
inChannel.read(buffer)读取数据,但不知道是否把需要的所有数据都读取到buffer中了,如何知道buffer中是否有足够的数据可以被处理呢?然而并不知道,唯一的方法就是检查buffer中的数据,可能会进行几次无效的检查,令程序设计比较混乱复杂。
bufferFull方法负责检查有多少数据被读取到了buffer中,扫描buffer但不能改变buffer的内部状态,根据返回值true/false判断数据是否进行处理
总结:
如果需要同时管理成千上万的连接,但是每个连接只发送少量数据(例如聊天服务器),用NIO实现会更好一些,相似的,如果需要保持很多个到其他电脑的连接(例如P2P网络),用一个单独的线程管理所有出口的连接是比较合适的
如果只有少量的连接但是每个连接都占很高的带宽,同时发送很多数据,传统的IO会更适合
nio类似餐厅点餐,客人看菜单选择想吃的菜(读取buffer数据),决定好要点的所有菜(判断buffer中数据已满),此时会有服务员过来下单(处理数据)。在客人看菜单的时候,服务员(线程)还可以为其他桌客人服务(处理其他数据)
io就是一个服务员对应一个客人