Java Nio 十四、Java NIO vs. IO

最后更新时间:2014-06-23

当学习Java NIO和IO的API的时候,一个问题很快的就会出现中我们的脑中:

我什么时候应该使用IO,什么时候应该使用NIO?

在这篇文章中我将会尝试着写出中NIO和IO之间不同的地方,他们的使用场景,以及他们怎么影响你的代码设计。

Java NIO和IO的主要不同

下面的表格总结了Java NIO和IO的主要不同。针对这个表格中的不同点我将会给予更加详细的说明。

IO NIO
基于流的 基于缓冲区的
堵塞IO 非堵塞IO
  Selectors(选择器)

基于流的相对基于缓冲区的

在Java NIO和IO之间第一个最大的不同就是,IO是基于流式的,而NIO是基于缓冲区的。这样是意味着什么呢?

基于流式的Java IO意味着你每次是从一个流中读取一个或者更多的字节。处理这个读到的字节取决于你。他们不能在任何地方被缓存。并且,在流中的数据你不能向前或者返回。如果你需要去向前或者返回去读取一个流中的数据,你将会首先需要把它缓存中buffer中。

Java NIO基于缓冲区的方式稍微有些不同。数据读进一个缓冲区中,然后后面会被处理。你可以按照你需要的在缓冲区中向前或者返回。这个中处理期间给予更大的灵活性。然而,你也需要去检查这个缓冲区中是否包含你需要处理的所有数据。而且,你需要确认什么时候可以读取更多的数据进入到缓冲区,你不能覆盖掉还没有处理的缓冲区的数据。

堵塞相对非堵塞IO

Java IO的各种流是堵塞的。那就意味着,当一个线程调用read()方法或者write()方法的时候,那个线程将会被堵塞直到这里有数据可以读,或者有数据可以被写。在此期间,这个线程不能做其他任何事情。

Java NIO的非堵塞模式可以让一个线程从一个通道中发送读数据请求,并且只是得到当前可用的,或者如果当前没有数据可用,那就什么都没有。而不是保持锁定直到有数据可读,这个线程可以继续去做其他的事情。

对于非堵塞写也是相同的。一个线程可以发送一个写数据到通道的请求,但是不会等到它完全被写。这个线程然后继续的同时去做其他事情。

当非堵塞IO调用的时候,线程花费他们空闲的时间在什么地方?通常是同时在其他的通道中执行IO。那就是,一个单独的线程可以管理多个通道的输入和输出。

选择器(Selectors)

Java NIO的选择器允许一个单独的线程去监控多个通道的输入。你可以用一个选择器注册多个通道,然后使用一个单线程去“选择”对于进程有可用输入的通道,或者“选择”准备好写的通道。这个选择器原理使得单个线程去管理多个通道变得简单了。

NIO和IO是怎样影响应用设计

不管你选择NIO还是IO作为你的工具包可能都会影响你应用设计的各个方面:

  1. 对于NIO或者IO类的API调用。
  2. 数据的处理。
  3. 处理数据的线程数量

API调用

当然使用NIO对于使用IO的API调用看起来是不同的。这没有什么惊讶的。Java NIO不是从例如InputStream中一个字节一个字节的读数据,这个数据必须首先被读取到缓冲区中,然后从缓冲区中被处理。

数据的处理

当使用一个纯粹的NIO设计相对于IO设计的时候,数据的处理也会受到影响的。

在一个IO设计中,你从InputStream或者一个Reader中逐字节的读取数据。想想你正在处理基于文本数据的行的流。例如:

Name: Anna
Age: 25
Email: [email protected]
Phone: 1234567890

这个文本流可以像这样被处理:

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);

注意第二行,是从通道中读取字节到ByteBuffer中。当那个方法调用返回的时候,你不知道是否你需要的所有数据在这个缓冲区中。所有你知道的只是这个缓冲区包含了一些字节。这样就会使得处理有点困难。

想象下,如果第一个read(buffer)调用之后,所有的读到缓冲区的数据是一行的一半。例如,“Name: An”。你能处理那个数据吗?真的不能。你需要等待直到一个完整的数据行读取到了缓冲区中,在有意义的处理这个数据的任何部分之前。

以至于为了能够有意义的去处理数据,你怎么知道这个缓冲区包含了足够的数据呢。好吧,你不知道。发现的唯一方式就是,去看这个缓冲区的数据。这个结果就是,在你知道是否所有的数据在这里之前,你可能不得不检查几次这个缓冲区中的数据。这样不仅是低效的,而且在代码设计方面变的混乱的。例如:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

while(! bufferFull(bytesRead) ) {
    bytesRead = inChannel.read(buffer);
}

这个bufferFull()的方法不得不跟踪有多少数据读到缓冲区了,并且根据缓冲区是否满了返回true或者false。换句话说,如果缓冲区准备好处理了,它就是认为满了。

这个bufferFull()方法扫描这个缓冲区,但是在这个bufferFull()方法被调用之前一定要处在缓冲区相同的位置。如果不是的话,下一个读取到buffe的数据可能就不会读到正确的位置上。这个不是不可能的事情,但是这个仍然是需要注意的另外一个问题。

如果缓冲区满了,它就可以被处理了。如果不满,如果那个在你的特别的场景中有意义的,你可能不管什么样的数据在这里,能够部分的处理数据。在大多数场景下是不可以的。

这个过程如下图所示:

总结

NIO允许你去管理多个通道(网络连接或者文件)使用一个单独的线程或者多个线程,但是这个代价就是解析数据可能稍微复杂一些相对从一个堵塞的流中读取数据而言。

如果你需要同时管理成千上万个打开的连接,这些连接每一个都要发送一些数据,例如一个聊天服务器,用NIO实现这个服务器可能是一个优势。相似的,如果你需要对其他的计算机保持许多的打开的连接,例如中一个P2P网络中,使用一个单独的线程管理你的所有的外部的连接可能是一个优势。这个一个线程,多个连接设计如下面的示意图所示:

如果你有很少的连接伴随着非常高的带宽,每次发送许多数据,可能一个标准的IO实现是更好的选择。如下图所示:

翻译地址:http://tutorials.jenkov.com/java-nio/nio-vs-io.html

时间: 2024-10-22 20:30:02

Java Nio 十四、Java NIO vs. IO的相关文章

java(十四)反射和正则表达式

反射机制: java反射机制是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法: 对于任意一个对象,都能够调用他的任意一个方法和属性: 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. 说简单点:动态获取类中的信息,就是java的反射.可以理解为对类的剖析. 反射技术提高了应用的扩展性,而且应用简单,所以很常用. 想要对一个类文件进行解剖,只要获取该类的字节码文件对象即可.怎么获取呢? 获取字节码对象的方式: package day28;

JAVA中十四种常见开发工具及其特点

1.JDK (Java Development Kit)Java开发工具集 SUN的Java不仅提了一个丰富的语言和运行环境,而且还提了一个免费的Java开发工具集(JDK).开发人员和最终用户可以利用这个工具来开发java程序. JDK简单易学,可以通过任何文本编辑器(如:Windows 记事本.UltrEdit.Editplus.FrontPage以及dreamweaver等)编写Java源文件,然后在DOS状况下利通过javac命令将Java源程序编译成字节码,通过Java命令来执行编译后

Java学习十四

学习内容: 1.Junit 一.Junit实例演示步骤 1.引入jar包 junit包需要引入hamcrest-core包,否则会报错 2.测试如下代码 1 package com.junit.test; 2 3 public class Calculator { 4 private static int result; //静态变量,用于存储运行结果 5 public void add(int n){ 6 result=result+n; 7 } 8 public void substract

java之十四 网络连接

1969年,KenThompson和Dennis Ritchie在MurrayHill,New Jersey的贝尔电话实验室开发了与C语言一致的UNIX.很多年来,UNIX的发展停留在贝尔实验室和一些大学及研究机构,用特意设计的DEC PDP机器运行.到了1978 年,Bill Joy在Cal Berkeley领导了一个项目,给UNIX增添新的特性,例如虚拟内存和全屏显示功能.到了1984年早期,当Bill正准备建立Sun Microsystems,它发明了4.2BSD,即众所周知的Berkel

Java成长第四集--文本处理IO流

Java IO流在实际业务中使用的频率还是蛮高的,一些业务场景比如,文件的上传和导出,文件的读取等基本都是通过操作IO流来实现的,所以IO流是我们现在学习过程中必须要掌握的技能之一,熟练的使用IO流,理解它的操作过程,能够让我们在今后的开发过程中达到事半功倍的效果.话不多说,首先,让我们来看看Java IO流的组织架构图: 所以,我们可以清晰的发现IO流中主要分为了两类,一类是字节流一类是字符流,要知道两者的区别,我们还需要理解一个概念, 流: 在程序中所有的数据都是以流的方式进行传输或保存的,

零基础入门学习java第十四节:Java对象的克隆

今天要介绍一个概念,对象的克隆.本篇有一定难度,请先做好心理准备.看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充. 克隆,自然就是将对象重新复制一份,那为什么要用克隆呢?什么时候需要使用呢?先来看一个小栗子: 简单起见,我们这里用的是Goods类的简单版本. public class Goods { private String title; private double price; public Goods(String aTitle, double aPri

再回首Java第二十四天

Callable和FutureJava1.5开始,Java提供了Callable接口,Callable接口提供了一个call()方法作为线程的执行体,但call()方法run()方法的功能更强大:?call()方法可以有返回值?call()方法可以声明抛出异常因此我们完全可以提供一个Callable对象作为Thread的target,而该线程的执行体就是该Callable对象的call方法.问题是:Callable对象时JDK1.5开始新增的接口,而它并不是Runnable的子接口,所以Call

Java Web(十四) 编写MyBookStore项目的总结

这几天一直没有发博文,原因是在写一个书城的小项目,作为web学习的最后沉淀,接下来就要到框架的学习了.项目最后会分享源码链接.有需要的同学可以拿到自己玩一玩 --WH 一.项目介绍 从网上找的一个培训机构的小项目,名称叫做 书城购物网站 吧,其中就是分前后台,前台用于显示各种类型的书籍商品,可以用于加入购物车,可以购买付款下单,后台就用来对书籍.书类别的增删改,和对订单的处理.一个不是很复杂的项目,但是在没开始编写之前,确实是毫无头绪,无从下手,那是因为没有对该项目有一个大体的认识,不知道其中的

Java学习(十四):JDBC方式连接数据库举例

java定义了JDBC这一标准的接口和类,为程序员操作数据库提供了统一的方式. 下载对应数据库的jar包,添加到工程内. JDBC的操作方式比较单一,由五个流程组成: 1.通过数据库厂商提供的JDBC类库向DriverManager注册数据库驱动 2.使用DriverManager提供的getConnection()方法连接到数据库 3.通过数据库的连接对象的createStatement方法建立SQL语句对象 4.执行SQL语句,并将结果集合返回到ResultSet中 5.使用while循环读