前面的一些文章中我总结了一些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. WPF获取控件内部的ScrollViewer,并控制ScrollViewer操作

    //获取内部  ScrollViewer方法 public static T FindVisualChild<T>(DependencyObject obj) where T : Depe ...

  2. git服务器创建,冲突解决,远程仓库获取指定文件

    1.git服务器创建 在公司多人协作开发的情况下,不能简单地使用github,因为github是互联网公开的,这种情况公司的代码的保密性就会丧失了.这种情况下,需要创建git服务器. 登录服务器,使用 ...

  3. SQL之连接查询

    这几天忙着笔试.面试,遇到了好几个关于数据库的试题,当然只是简单的多表查询.我第一时间都是选择select...from...where...group by...having...这个结构去写的.但 ...

  4. A Summaryof JDBC

    Die Sonne gewinnen! I think it's easy to understand what is JDBC used for. Programer's program shoul ...

  5. UWP StorageFile StorageFolder StorageFileHelper

    //获取表示指定文件系统路径中的文件夹的 StorageFolder. StorageFolder folder1 = await StorageFolder.GetFolderFromPathAsy ...

  6. .net的数据类型说明

    C#提供称为简单类型的预定义结构类型集,简单类型通过保留字标识, 而这些保留字只是System命名空间中预定义结构类型的别名. 保留字与预定义结构类型的对应如下: 保留字 预定义结构类型 sbyte ...

  7. 简单封装 Delphi 的 DirectX类

    var CreatorRenderer  : TCreatorRenderer; Form1: TForm1; 窗体代码: {$R *.dfm} procedure TForm1.FormCreate ...

  8. Delphi下IOC 模式的实现(反转模式,即Callback模式)

    IOC英文为 Inversion of Control,即反转模式,这里有著名的好莱坞理论:你呆着别动,到时我会找你.Ioc模式是解决调用者和被调用者之间关系的模式,可以有效降低软件的耦合度,并适合团 ...

  9. 文件夹管理工具(MVC+zTree+layer)

    文件夹管理工具(MVC+zTree+layer)(附源码)   写在前 之前写了一篇关于 文件夹与文件的操作的文章  操作文件方法简单总结(File,Directory,StreamReader,St ...

  10. Qt5 中对 C++11 一些新特性的封装

    在 Qt5 中,提供更多 C++11 的特性支持,接下来我们将进行详细的说明. slots (槽) 的 Lambda 表达式 Lambda表达式 是 C++11 中的一个新语法,允许定义匿名函数.匿名 ...