前面的一些文章中我总结了一些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. 深度分析WM_PAINT和WM_ERASEBKGND消息

    做windows开发这么久了,一直以来对WM_PAINT和WM_ERASEBKGND消息总是感觉理解的不准确,每次要自绘一个窗口都因为知其然不知其所以然,偶然发现一篇文章,详细透彻地分了这个两个消息的 ...

  2. IDisposeable 最佳实现

    public class MyClass : IDisposable { #region 变量声明 // 指向外部非托管资源 private IntPtr handle; // 此类使用的其它托管资源 ...

  3. 起调UWP的几种方法

    原文:起调UWP的几种方法 由于种种原因吧,我需要使用一个WPF程序起调一个UWP程序,下面总结一下,给自己个备份. 启动UWP程序的关键是协议启动 给我们的UWP应用添加一个协议,like this ...

  4. SpringBoot简易搭建

    1.建立maven工程 2.打开pom文件, 将以下配置拷贝过去 <parent> <groupId>org.springframework.boot</groupId& ...

  5. ML:吴恩达 机器学习 课程笔记(Week3~4)

    Logistic Regression Regularization Neural Networks: Representation

  6. Elevated privileges for Delphi applications

    BY CRAIG CHAPMAN · PUBLISHED 2015-06-08 · UPDATED 2015-06-08   One of my customers recently asked th ...

  7. IntelliJ IDEA的jsp中内置对象方法无法被解析的解决办法

    主要原因是因为缺乏依赖 可以通过添加依赖的方式 导入servlet-api.jar,jsp-api.jar,tomcat-api.jar 这三个jar即可 这三个jar在tomcat的lib目录下有 ...

  8. Qt加载百度离线地图

    1.下载百度地图离线API 1.3 下载链接:http://download.csdn.NET/detail/caoshangpa/9476608,网上虽然出现了2.0版本离线API,但是经试用,存在 ...

  9. FMX+Win32,窗口无法保持原样,应该是个bug

    从FMX发布开始,一直有这问题,大家看看是不是一个bug,应该如何修复? 新建一个FMX Application,运行后,点击窗口标题栏右上角的“最大化”按钮,此时窗口是最大化的.在windows最底 ...

  10. 记一次排查tomcat耗费CPU过高的经历

    有一个新项目,在测试环境部署后,发现tomcat进程耗费的CPU非常高,排查过程如下: 日志搜集 先通过top,查找耗费CPU最高的线程 top -Hp pid 将线程ID转为16进制 printf ...