前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性能,但是仅仅是因为性能吗,除此之外是否还有别的原因,或者说既然NIO性能好,那为什么现在我们还在使用IO。本节我们就来详细对比一下两者的特性以及两者之间的不一致对我们编码所带来的影响。

  同样,本文会主要围绕下面几个方面来总结:

  Java NIO和IO的主要区别

  NIO和IO的不同对代码设计带来的变化

  两种IO的各自适用场景

  总结

1. Java NIO和IO的主要区别

  两者之间的不同主要体现在如下三个方面:

  • Java IO是面向流(Stream)的,而Java NIO是面向缓冲区(Buffer)的;
  • IO模型的不同,Java IO是属于阻塞式IO(Blocking IO),而Java NIO是属于非阻塞式IO(Non Blocking IO);
  • Java NIO中还引入了Selector的概念,可以实现多路复用;

  在接下来的部分,我们逐个讨论这三个不同。

1.1 面向流与面向缓冲区

  Java NIO和IO之间第一个不同点是IO是面向流(Stream)的而NIO是面向缓冲区(Buffer)的。

  Java IO是面向流的,这意味着是一次性从流中读取一批数据,这些数据并不会缓存在任何地方,并且对于在流中的数据是不支持在数据中前后移动。如果需要在这些数据中移动(为什么要移动,可以多次读取),则还是需要将这部分数据先缓存在缓冲区中。

  而Java NIO采用的是面向缓冲区的方式,有些不同,数据会先读取到缓冲区中以供稍后处理。在buffer中是可以方便地前移和后移,这使得在处理数据时可以有更大的灵活性。但是呢需要检查buffer是否包含需要的所有数据以便能够将其完整地处理,并且需要确保在通过channel往buffer读数据的时候不能够覆盖还未处理的数据。

1.2 IO模型的区别

  Java IO中使用的流是属于阻塞式的,意味着当线程调用其read()或write()方法时线程会阻塞,直到完成了数据的读写,在读写的过程中线程是什么都做不了的。

  Java NIO提供了一种非阻塞模式,使得线程向channel请求读数据时,只会获取已经就绪的数据,并不会阻塞以等待所有数据都准备好(IO就是这样做),这样在数据准备的阶段线程就能够去处理别的事情。对于非阻塞式写数据是一样的。线程往channel中写数据时,并不会阻塞以等待数据写完,而是可以处理别的事情,等到数据已经写好了,线程再处理这部分事情。

  当线程在进行IO调用并且不会进入阻塞的情况下,这部分的空余时间就可以花在和其他channel进行IO交互上。也就是说,这样单个线程就能够管理多个channel的输入和输出了。

1.3 Selector

  Java NIO中的Selector允许单个线程监控多个channel,可以将多个channel注册到一个Selector中,然后可以"select"出已经准备好数据的channel,或者准备好写入的channel。这个selector机制使得单个线程同时管理多个channel变得更容易。

2. NIO和IO的不同对代码设计带来的变化

  选择使用NIO还是IO作为开发工具包会在如下几个方面影响应用设计:

  • API是调用NIO类库还是IO类库;
  • 数据的处理方式;
  • 用来处理数据的线程的数量;

2.1 API的调用

  采用NIO的API调用方式和IO是不一样的,与直接从InputStream中读取字节数据不同,在NIO中,数据必须要先被读到buffer中,然后再从那里进行后续的处理。

2.2 数据的处理方式

  采用NIO的设计还是IO的设计,数据的处理方式也是不一样的。

  在IO设计中是从InputStream或Reader中逐字节读取数据。在下面例子中,我们通过一个处理基于文本的简单例子来说明两种设计的区别:

Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890

  采用IO的方式,这些数据流会像下面这样处理:

InputStream input = ... ; // get the InputStream from the client socket
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();

  注意在这里处理状态是通过程序执行了多少就能够确定的。换句话说,当第一行reader.readLine()返回之后,可以确定已经读了一整行。因为readLine()会阻塞直到整行数据读完。而且我们能够确切地知道所读取的这第一行是包含名字的。类似,第二次调用readLine()返回之后我们确切地知道所读取的内容包含年龄。

  可以知道,上面的程序只有当有新的数据是可读时才会进行处理,在每一步都知道数据是什么。一旦执行读写的线程已经读取了一些数据之后,是不能够再返回到前面的数据(因为流的方式只能读取一次,很好理解,像水一样,流完了就流完了,除非你把它装到容器里面)。上面程序中所遵循的原则如下图所示:

  而NIO的实现则看起来有些不同,如下:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);

  注意第二行是从channel读取数据到buffer中,当read()方法返回时我们是不知道是否所有需要的数据有没有全部读到buffer中,我们知道的只是buffer中可能包含一部分数据,这会使得整个过程的处理有点麻烦。

  假设,在第一次调用read()之后,所有读到buffer中的数据只有半行,比如,"Name:An"。这时可以处理数据吗,显然是不可以的(因为还没有读完),需要等到至少一行数据被读到buffer中。

  那么我们又如何来知道buffer中包含足够可以处理的数据呢?唯一的办法只有检查buffer中的数据了。所以结果就是我们需要通过多次检查buffer中的数据来判断数据是否已经全部读进buffer了。这样就很低效,而且容易导致程序设计混乱。比如:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}

  bufferFull()方法会跟踪有多少数据被读到buffer中了,并且返回true或者false,取决于buffer是否已满。换言之,如果buffer中的数据已经可供处理,那就代表它已经满了。

  bufferFull()方法会扫描整个buffer,要保证扫描并不会影响整个buffer的状态,不然可能导致后面要读入buffer中的数据不能读到正确地位置。这并非不可能,所以对于设计者来说这是一个需要关注的地方。

  如果buffer已满,那其中的数据就可供处理。如果没满,那可能需要部分地处理那些数据(如果需要的话),只是在大部分场景下是不需要的。

  下图描述了这种 is-data-in-buffer-ready的循环:

3. 两种IO的各自适用场景

  NIO使得通过单个或少量线程来管理多个channel(网络连接或者文件)成为可能,但是代价是传递数据会比从阻塞的流中读数据更复杂。我们学习一项新的技术时,既要看到其优点也要看到其缺点。

  如果需要同时管理数以千计的连接,而且每个连接只会发送少量的数据,比如聊天服务器,用NIO的方式来实现这个服务器则比较合适。类似的,如果需要长时间保持一些和别的电脑的连接,比如在一个P2P网络中,用单个线程来管理所有的对外连接也有优势。如下图描述了这种单个线程,多个连接的设计模型:

  如果只有少量的连接,但是每个连接又都占用大量的带宽,短时间之内发送大量数据,这时后也许传统的IO模型会更适用,因为专一,所以在特定场景下可以更高效。如下图描述了一个基于传统IO模型设计的服务器模型:

4. 总结

  在前面总结了很多IO和NIO的相关知识之后,本文总结了Java中两种IO类库的区别即各自的优缺点:

  • 传统Java IO是面向流,从流中读取数据或者写入到流中,而Java NIO是面向缓冲区的,通过channel和buffer的搭配使用来读取或者写入数据;
  • 面向流只能一次读取数据;面向缓冲区可以多次读取数据;
  • 面向流的方式处理数据过程相对简单,易于实现;而Java NIO中面向buffer的方式一般是非阻塞的方式,所以在数据的操作上会更复杂,从而会增加代码的复杂程度;
  • Java NIO提供了Selector的概念,可以通过少量线程处理多个连接,可以有效处理并发;而Java IO则专注于单个线程阻塞式读写,对于少量连接但是每个连接都占用大量宽带的场景更适用;

  技术没有好坏,只有合适与否!

Java NIO学习系列四:NIO和IO对比的更多相关文章

  1. Java NIO学习笔记四 NIO选择器

    Java NIO选择器 A Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备就绪,例如读取或写入.这样一个线程可以管理多个通道,从而管理多个网络连接. 为 ...

  2. 零拷贝详解 Java NIO学习笔记四(零拷贝详解)

    转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...

  3. Java命令学习系列(二)——Jstack

    Java命令学习系列(二)——Jstack 2015-04-18 分类:Java 阅读(512) 评论(0) jstack是java虚拟机自带的一种堆栈跟踪工具. 功能 jstack用于生成java虚 ...

  4. Java Web学习系列——Maven Web项目中集成使用Spring

    参考Java Web学习系列——创建基于Maven的Web项目一文,创建一个名为LockMIS的Maven Web项目. 添加依赖Jar包 推荐在http://mvnrepository.com/.h ...

  5. Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问

    本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...

  6. 转:深入Java集合学习系列:HashSet的实现原理

    0.参考文献 深入Java集合学习系列:HashSet的实现原理 1.HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特 ...

  7. Python学习系列(九)(IO与异常处理)

    Python学习系列(九)(IO与异常处理) Python学习系列(八)( 面向对象基础) 一,存储器 1,Python提供一个标准的模块,称为pickle,使用它既可以在一个文件中存储任何Pytho ...

  8. Java多线程学习(四)等待/通知(wait/notify)机制

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  9. Java命令学习系列(7):Javap(转)

    原文出处: Hollis(@Hollis_Chuang) javap是jdk自带的一个工具,可以对代码反编译,也可以查看java编译器生成的字节码. 一般情况下,很少有人使用javap对class文件 ...

随机推荐

  1. linux命令行模式下实现代理上网 专题

    有些公司的局域网环境,例如我们公司的只允许使用代理上网,图形界面的很好解决就设置一下浏览器的代理就好了,但是linux纯命令行的界面就....下面简单几步就可以实现了! 一.命令行界面的一般代理设置方 ...

  2. xadmin下设置“use_bootswatch = True”无效解决方法

    from xadmin import viewsimport xadmin class BaseSetting(object): enable_themes=True use_bootswatch=T ...

  3. mysql主从配置及其读写分离

    mysql主从配置意思就是一个主mysql服务器,一个从mysql服务器,一共要用到两台服务器.主服务器新增一个账号专门让从服务器来访问同步工作,主从配置完成后,主服务器主要就是新增和update操作 ...

  4. python 安装 win 下的exe结尾的文件操作

    1.首先下载相关的模块 2.把下载的相关模块放到python 安装目录下 3.cmd 切换到python的安装目录下 例如:{PIL-1.1.7.win32-py2.7.exe} 4.执行 pip i ...

  5. HTTP协议入门(一)- 版本

    当我们在浏览器的地址栏输入URL后,信息会被发送到WEB服务器,服务器得到响应,将数据传输回来,展示到WEB页面上,这其中的传输方法就是HTTP协议. 一.HTTP 0.9 发布于1991年,是首个H ...

  6. 使用WinDug工具调试c#程序或c++程序的dmp崩溃文件,调试内存泄漏

    1.调试c#程序内存泄漏步骤 设置symbol符号路径: SRV*c:\mysymbol* http://msdl.microsoft.com/download/symbols;d:/你的pdb文件路 ...

  7. Java中动态代理技术生成的类与原始类的区别 (good)

    用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑. 平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类重新生成了一遍,在切面上加上了后 ...

  8. PowerDesigner 在通过jdbc连接数据库时 Could not Initialize JavaVM!

    最近用到PowerDesigner的逆向工程,从数据库中逆向生成模型,本想使用odbc连接的,但是需要安装驱动,mysql的还好弄,oracle对我来讲实在是有些麻烦,看到能用jdbc连接,就想试试, ...

  9. 给你的 GitHub Repository 加上 sponsor 按钮

    「本文微信公众号 AndroidTraveler 首发」 背景 其实之前 GitHub 就已经说过要给开源的开发者提供赞助支持. 当你进入 GitHub 主页时,你会在右边发现一个 Tips. 点击进 ...

  10. Python基础,day3

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 1.函数基本语法及特性 如何不重复代码,其实很 ...