提起java的io,我们都会提到传统io,nio。也会想到时下比较热门的netty这个io框架。

那传统io,是如何演变成nio的呢?它们之间有什么异同呢?

接下来我们将从传统io谈起,系统完整的解答io的相关问题。

一:传统io

只要学过io的人都知道,传统io处理读取的时候通常是需要创建多个线程来单独处理的。那为什么需要创建多个线程呢?既然想要搞清楚为什么使用多线程,那么我们先来看看在使用单线程的时候是个什么样的情况:

public class OioServer {@SuppressWarnings("resource")public static void main(String[] args) throws Exception {//创建socket服务,监听10101端口ServerSocket server=new ServerSocket(10101);System.out.println("服务器启动!");while(true){//获取一个套接字(阻塞)final Socket socket = server.accept();System.out.println("来个一个新客户端!");//业务处理handler(socket);          }}/*** 读取数据* @param socket* @throws Exception*/public static void handler(Socket socket){try {byte[] bytes = new byte[1024];InputStream inputStream = socket.getInputStream();while(true){//读取数据(阻塞)int read = inputStream.read(bytes);if(read != -1){System.out.println(new String(bytes, 0, read));}else{break;}}} catch (Exception e) {e.printStackTrace();}finally{try {System.out.println("socket关闭");socket.close();} catch (IOException e) {e.printStackTrace();}}}
}

首先需要清楚,这段代码中有两个阻塞的点:

  • server.accept(),该方法用于获取一个socket,在未接收到客户端连接的时候它会一直阻塞
  • inputStream.read(bytes),当客户端连接成功后,程序阻塞等待读取数据发送的数据

然后我们来看看这段代码的执行顺序

  • 当该代码运行后(即通信的服务端开启),程序在server.accept()处阻塞等待客户端连接
  • 通过dos命令,输入telnet 127.0.0.1 9999连接服务端,这时程序返回一个socket并等待客户端发送数据
  • 客户端进行数据读取,并处理数据。

在单线程的io中,在长连接的时候客户端的socket独占线程,这造成其他的客户端连接到该服务端,它也无法获取到socket并进行数据读取。

传统io单线程处理的弊端:一个服务端无法为多个客户端提供服务。

那如何处理这种状况呢?这个时候就引入了多线程,既然在数据读取的同时不能让其他客户端获取到socket,那么就让处理读写的操作独立出来让另一个线程去处理不就完了。看下面一段代码:

public class OioServer {@SuppressWarnings("resource")public static void main(String[] args) throws Exception {ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();//创建socket服务,监听9999端口ServerSocket server=new ServerSocket(9999);System.out.println("服务器启动!");while(true){//获取一个套接字(阻塞)final Socket socket = server.accept();System.out.println("来个一个新客户端!");newCachedThreadPool.execute(new Runnable() {@Overridepublic void run() {//业务处理handler(socket);}});}}/*** 读取数据* @param socket* @throws Exception*/public static void handler(Socket socket){try {byte[] bytes = new byte[1024];InputStream inputStream = socket.getInputStream();while(true){//读取数据(阻塞)int read = inputStream.read(bytes);if(read != -1){System.out.println(new String(bytes, 0, read));}else{break;}}} catch (Exception e) {e.printStackTrace();}finally{try {System.out.println("socket关闭");socket.close();} catch (IOException e) {e.printStackTrace();}}}
}

可以看到,加入多线程后可以避免在单线程的时候不能为多个客户端提供服务的弊端。但试想一下,如果来一个客户端就新建一个线程为它服务,那线程多了你的电脑会不会爆炸掉?所以这种多线程的io也不是特别靠谱。当然如果是短连接(读写操作耗时短,处理完后socket关闭)的时候,这种处理还是可以的,对于长连接就不行了。

由此,对传统io进行改进,就得到了nio

二:Nio

nio被称为非阻塞的io。那它和传统io有什么区别呢?

先上两张图:

传统io:如果将整个服务端程序看成是一家餐厅的话,serverSocket就是大门,而客人是客户端,服务员就是处理读写操作的线程。传统的处理方式是,当大门(serverSocket),来一个客人(client),餐厅(sever)就为他分配一个服务员(thread),这一个服务员为一名客人服务,这样明显会造成人力浪费,是一种非常不可取的方式。

于是我们可能会想,如果一个服务员能够为多个客人服务就好了,所以接下来看nio是如何处理的:

还是刚才那个餐厅和服务员的比喻。nio新加入了几个概念,一个是channel(通道)相当于socket,一个是selector(多路复用器)。当大门(serverSocketChannel)检测到有新的客人(client)上门的时候,餐厅不再是直接给他派遣一个服务员,而是让他在服务员那里去登记一下(就像我们点餐一样,点好了拿着点餐的牌号,然后等着服务员根据订单给你上菜就好了),然后由一个服务员根据你登记的需要服务的事项(在nio中成为状态)为所有客人服务。这样一个服务端就可以为多个客户端提供服务了,大大的节约了资源。

上面是比喻的说法,接下来看看nio的具体实现:

public class NIOServer {// 通道管理器private Selector selector;/*** 获得一个ServerSocket通道,并对该通道做一些初始化的工作* * @param port*            绑定的端口号* @throws IOException*/public void initServer(int port) throws IOException {// 获得一个ServerSocket通道ServerSocketChannel serverChannel = ServerSocketChannel.open();// 设置通道为非阻塞serverChannel.configureBlocking(false);// 将该通道对应的ServerSocket绑定到port端口serverChannel.socket().bind(new InetSocketAddress(port));// 获得一个通道管理器this.selector = Selector.open();// 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,// 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。serverChannel.register(selector, SelectionKey.OP_ACCEPT);}/*** 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理* * @throws IOException*/public void listen() throws IOException {System.out.println("服务端启动成功!");// 轮询访问selectorwhile (true) {// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞selector.select();// 获得selector中选中的项的迭代器,选中的项为注册的事件Iterator<?> ite = this.selector.selectedKeys().iterator();while (ite.hasNext()) {SelectionKey key = (SelectionKey) ite.next();// 删除已选的key,以防重复处理ite.remove();handler(key);}}}/*** 处理请求* * @param key* @throws IOException*/public void handler(SelectionKey key) throws IOException {// 客户端请求连接事件if (key.isAcceptable()) {handlerAccept(key);// 获得了可读的事件} else if (key.isReadable()) {handelerRead(key);}}/*** 处理连接请求* * @param key* @throws IOException*/public void handlerAccept(SelectionKey key) throws IOException {ServerSocketChannel server = (ServerSocketChannel) key.channel();// 获得和客户端连接的通道SocketChannel channel = server.accept();// 设置成非阻塞channel.configureBlocking(false);// 在这里可以给客户端发送信息哦System.out.println("新的客户端连接");// 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。channel.register(this.selector, SelectionKey.OP_READ);}/*** 处理读的事件* * @param key* @throws IOException*/public void handelerRead(SelectionKey key) throws IOException {// 服务器可读取消息:得到事件发生的Socket通道SocketChannel channel = (SocketChannel) key.channel();// 创建读取的缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);int read = channel.read(buffer);if(read > 0){byte[] data = buffer.array();String msg = new String(data).trim();try {System.out.println("开始接收数据……");Thread.sleep(10000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("服务端收到信息:" + msg);//回写数据ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());channel.write(outBuffer);// 将消息回送给客户端}else{System.out.println("客户端关闭");key.cancel();}}/*** 启动服务端测试* * @throws IOException*/public static void main(String[] args) throws IOException {NIOServer server = new NIOServer();server.initServer(8000);server.listen();}}

这段代码有点长,但是仔细梳理下可以概括成一下几个步骤

  • 初始化操作:该阶段会获取serverSocketChannel,并将它与selector绑定并赋予SelectionKey.OP_ACCEPT状态(即阻塞状态)
  • 通过selector.select()进行事件检测,当事件到达的时候它返回,而没有事件到达的时候它会阻塞。
  • 根据select()方法检测到的事件,获取对应事件的就绪的channel,并根据状态采取相应的操作

这里selector中的状态分为:

  • SelectionKey.OP_CONNECT 连接状态
  • SelectionKey.OP_ACCEPT 阻塞状态
  • SelectionKey.OP_READ 可读
  • SelectionKey.OP_WRITE 可写

注意:nio中最重要的方法就是selector.select(),该方法是一个阻塞的方法。它会检测事件和类型,它会将对应事件类型的就绪的channel随着selectedKey返回。比如说客户端要进行read操作,select()方法检测到该事件,它就会将selector中注册的并且read就绪的channel返回。而如果是一个刚连接的client,select()方法会返回ACCEPT状态的serverChannel进行新channel的注册。所以说在selector中的channel是在不断增长的。

看了nio的代码你可能有一个想法:感觉nio和单线程的传统io好像差不多

现在我们来理一理其中的区别,并且解释为什么同样是单线程nio能够做到传统io不能做到的事情。

看以下代码:

public static void handler(Socket socket){try {byte[] bytes = new byte[1024];InputStream inputStream = socket.getInputStream();while(true){//读取数据(阻塞)int read = inputStream.read(bytes);if(read != -1){System.out.println(new String(bytes, 0, read));}else{break;}}} catch (Exception e) {e.printStackTrace();}}

这是一段读取客户端传入数据的代码片段。可以看见传统单线程io中,我们如果要保持长时间读写,需要使用while(true)循环,线程被单一客户端所独占,这造其他客户端不能连接。而nio采用的是一种对象池的思想,这个时候线程是被selector独占的,这样就可以通过不断循环selector来处理多个客户端的连接。一个是被单一客户端独占线程,而另一个是被一个类似对象池的多路复用器独占线程,这就是为什么同样是单线程,nio能够处理多个客户端连接的原因。

总结:

  • 传统io处理多个客户端时需要创建多个线程,而nio可以用单个线程处理多个客户端(至于nio如何用多个线程处理客户端请求,这个放在另一篇中讲,其实netty就是nio的多线程版)
  • 传统io是阻塞的,nio是非阻塞的。可能有人会有疑问,selecetor.select()明明是一个阻塞的方法怎么nio是非阻塞的了。这里说的阻塞和非阻塞其实是指的socket或channel读取数据的方法是否阻塞。socket的读取方法是阻塞的,而channel的read方法是非阻塞的。
  • 传统io和nio都是同步的,即都是按顺序执行的。

以上便是我对io的理解,希望能够帮助到你\(^o^)/~

传统io和NIO详细比较相关推荐

  1. Netty入门--传统IO与NIO详解

    文章目录 IO模型 传统阻塞的IO模型--BIO Client端案例 Server端案例 NIO(Java non-blocking IO)非阻塞IO NIO的三大组件 Channel Selecto ...

  2. Java网络编程进化史:从IO到NIO再到Netty

    一.Netty入门 1.传统IO与NIO NIO即New IO,这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多. ...

  3. 分别基于IO、NIO、Netty的Java网络程序

    分别基于IO.NIO.Netty的Java网络程序 IDE:IntelliJ IDEA 文章目录 分别基于IO.NIO.Netty的Java网络程序 一.Java NIO 1.1 NIO与传统IO对比 ...

  4. Java传统的io和nio区别_Java中IO和NIO的本质和区别

    简介 终于要写到java中最最让人激动的部分了IO和NIO.IO的全称是input output,是java程序跟外部世界交流的桥梁,IO指的是java.io包中的所有类,他们是从java1.0开始就 ...

  5. java输入输出流_金九银十准备季:Java异常+Java IO与NIO面试题(含答案)

    写在前面:2020年面试必备的Java后端进阶面试题总结了一份复习指南在Github上,内容详细,图文并茂,有需要学习的朋友可以Star一下! GitHub地址:abel-max/Java-Study ...

  6. Java IO BIO NIO

    Java IO BIO NIO 一.Java I/O概述 1.1 什么是流 1.2 流的分类 1.3 字符流 1.3.1 Reader 1.3.2 Writer 1.4 字节流 1.4.1 Input ...

  7. Java之IO,BIO,NIO,AIO

    2019独角兽企业重金招聘Python工程师标准>>> 参考文献一 IO基础知识回顾 java的核心库java.io提供了全面的IO接口.包括:文件读写.标准设备输出等.Java中I ...

  8. io读取一个文件再写入socket技术_JAVA中IO与NIO面试题

    BIO.NIO有什么区别? BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低. NIO:New IO 同步非阻塞 IO,是传统 IO ...

  9. IO: BIO ? NIO ? AIO?

    IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSock ...

  10. 【学习笔记】JAVA IO与NIO(new IO)的对比与不同IO模型的理解

    JAVA IO 分类: 几种IO 模型 1. 阻塞 IO 模型 2. 非阻塞 IO 模型 JAVA NIO 多路复用 IO 模型(即Java中的NIO) JAVA IO 思维导图: 分类: 按照流的方 ...

最新文章

  1. 通用数据库连接执行类(SQL)
  2. 最牛逼的核心框架,没有之一!
  3. 微型计算机48MHz辐射超,2012职称计算机考试题理论题库
  4. 使用Duilib做桌面应用总结
  5. 什么是java OOM?如何分析及解决oom问题?
  6. js获取网页面的高度和宽度
  7. 二层冗余网络引起的问题
  8. ZZULIOJ 1068:二进制数
  9. http 阮一峰_互联网协议入门(二)
  10. 简单自制拖拽布局思路vue-Cil
  11. 机器学习教程之语义分割入门教程
  12. cfar matlab,雷达无线电系列(二)经典CFAR算法图文解析与实现(matlab)
  13. 股票行情图的绘制,分时图和闪电图
  14. 使用OpenSSL库函数测试AES-CCM加密算法
  15. 传奇修改map地图教程_传奇地图事件触发脚本教程
  16. 电瓶车.20180809
  17. 《RFID原理及应用》期末复习总结(6)
  18. python作业网站_怒刷python作业-WEB资讯专栏-DMOZ中文网站分类目录-免费收录各类优秀网站的中文网站目录....
  19. N本名著浓缩成的N句话
  20. 读书笔记之解忧杂货店

热门文章

  1. ubuntu freeswitch安装
  2. 进制之间的转换(史上最全自己纯手工总结)
  3. g729语音编码 matlab,Case7 FreeSwitch配置开启转码功能及安装G729语音编码
  4. 互联网人:最熟悉的陌生人
  5. deepspeech 1 (百度 2014 论文解读)
  6. PDF转Word软件
  7. SystemTap笔记02 stap的编译运行
  8. Linux系统配置ftps服务(显式)
  9. Windows下使用SSH命令登录Linux服务器
  10. win10 安装 ssh后,命令行中仍无法运行ssh命令,‘ssh‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件