Java NIO学习系列四:NIO和IO对比
前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性能,但是仅仅是因为性能吗,除此之外是否还有别的原因,或者说既然NIO性能好,那为什么现在我们还在使用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对比的更多相关文章
- Java NIO学习笔记四 NIO选择器
Java NIO选择器 A Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备就绪,例如读取或写入.这样一个线程可以管理多个通道,从而管理多个网络连接. 为 ...
- 零拷贝详解 Java NIO学习笔记四(零拷贝详解)
转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...
- Java命令学习系列(二)——Jstack
Java命令学习系列(二)——Jstack 2015-04-18 分类:Java 阅读(512) 评论(0) jstack是java虚拟机自带的一种堆栈跟踪工具. 功能 jstack用于生成java虚 ...
- Java Web学习系列——Maven Web项目中集成使用Spring
参考Java Web学习系列——创建基于Maven的Web项目一文,创建一个名为LockMIS的Maven Web项目. 添加依赖Jar包 推荐在http://mvnrepository.com/.h ...
- Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问
本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...
- 转:深入Java集合学习系列:HashSet的实现原理
0.参考文献 深入Java集合学习系列:HashSet的实现原理 1.HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特 ...
- Python学习系列(九)(IO与异常处理)
Python学习系列(九)(IO与异常处理) Python学习系列(八)( 面向对象基础) 一,存储器 1,Python提供一个标准的模块,称为pickle,使用它既可以在一个文件中存储任何Pytho ...
- Java多线程学习(四)等待/通知(wait/notify)机制
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- Java命令学习系列(7):Javap(转)
原文出处: Hollis(@Hollis_Chuang) javap是jdk自带的一个工具,可以对代码反编译,也可以查看java编译器生成的字节码. 一般情况下,很少有人使用javap对class文件 ...
随机推荐
- Spring 中 CharacterEncodingFilter 失效?
# 问题 Spring 提供了CharcterEncodingFilter,专门解决字符串编码的问题. 诡异的是,在类 AbstractAnnotationConfigDispatcherServle ...
- xadmin下修改左道航的显示不是中文字修改方法
解决方案: 在对应的apps下有一个apps.py文件添加verbose_name=u"想要的字" 在对应的__init__.py 添加 default_app_config= ...
- sqlserver检查sql执行时间
set statistics profile onset statistics io onset statistics time onGO select * from mytable; goset s ...
- 使用BGP的虚拟下一跳实现IGP的路由负载
网络拓扑: XRV1 ============================================================== !hostname XRV1! interface ...
- Win8Metro(C#)数字图像处理--2.16图像浮雕效果
原文:Win8Metro(C#)数字图像处理--2.16图像浮雕效果 [函数名称] 图像浮雕效果函数ReliefProcess(WriteableBitmap src) [函数代码] ...
- 零元学Expression Blend 4 - Chapter 28 ListBox的基本运用与更改预设样式
原文:零元学Expression Blend 4 - Chapter 28 ListBox的基本运用与更改预设样式 本章将先教大家认识ListBox的基本运用与更改预设样式 本章将先教大家认识List ...
- Delphi事件的广播
原文地址:Delphi事件的广播 转作者:MondaySoftware 明天就是五一节了,辛苦了好几个月,借此机会应该尽情放松一番.可是想到Blog好久没有写文章,似乎缺些什么似的.这几个月来在项目中 ...
- 宿主机与虚拟机系统的USB设备切换
有时候我们需要在虚拟机的操作系统中进行一些USB设备的测试,但默认情况下USB设备是在宿主机系统里面的,那这个时候我们就要进行切换才能够达到目的,具体要怎么操作呢?下面讲解一下: 1. Ctrl+ ...
- DirectUI的消息流转
Windows是一个基于消息循环的系统,DirectUI同样遵循这样的消息流转.当界面呈现.用户点击.定时器等各种各样的消息一旦进入windows消息循环队列,系统自动调用该窗口的WndProc过程. ...
- Swagger API文档集中化注册管理
接口文档是前后端开发对接时很重要的一个组件.手动编写接口文档既费时,又存在文档不能随代码及时更新的问题,因此产生了像swagger这样的自动生成接口文档的框架.swagger文档一般是随项目代码生成与 ...