`
JimmyWen
  • 浏览: 13340 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

Java NIO入门

 
阅读更多

 

链接地址: http://www.2cto.com/kf/201109/103843.html

I/O 介绍
I/O
即输入输出,指的是计算机和世界其他部分的接口,或者是单个程序同计算机其他部分的接口。I/O 是计算机系统中的重要元素,并且大量的I/O 实际上已经内建到操作系统中了。单独的程序通常都有很多I/O 方面的工作要做。

JAVA 中,老的I/O 是以/Stream” 为基础概念,即所有的I/O 都一个个单个字节的流动。在字节流中,每次一个字节,依次通过一个叫做 Stream 的对象。Stream I/O 联系着计算机和外面的世界。Stream I/O 也用在计算机内部,比如把一个对象转换成bytes 或者把bytes 反序列化成对象。

NIO 具有和原来的老I/O 相同作用和目的,但是NIO 却使用了一个不同的概念--block I/O (块I/O )。在本文后面会讲到,NIO 会比老的基于流的I/O 更高效。

为什么使用NIO
创建NIO 的初衷是为了让JAVA 开发人员在不直接使用底层本地代码的情况下实现高速的I/O 操作。NIO 的高效归结与它把那些耗时的I/O 操作(比如读数据进入buffer ,或者将buffer 中的数据写入外部设备)都推给了操作系统,因此获得了很高的性能。

Streams VS blocks (流和块的比较)
I/ONIO 之间最重要的区别在于他们不同的数据打包与传输方式。就像之前提到的,老I/O 的方式处理数据,而NIO 的方式处理数据。

面向流的IO 系统每次处理一个字节,输入流(input stream )每生产一个字节,输出流(output stream )就消费一个字节。这种工作模式下,非常容易给流数据创建过滤器(filters ),而且也很容易将多个过滤器串起来,每个过滤器针对流过自 己的字节做相应处理。另一方面,在这种工作模式下面向流的IO 通常很慢。

面向块的IO 系统以块为单位处理数据。每个操作都会生产/ 消费一 数据,以块为单位处理数据会比以字节(流)为单位处理数据快很多。但是面向块的IO 系统同时也损失了一些优雅而简单的操作方式。

Channels and buffers (通道和缓冲)
 

概述
Channel
BufferNIO 中最核心的对象,他们用在几乎每一个NIO 的操作上。

Channel 模拟了老IO 包中的流的概念。所有去任何地方(或者来自任何地方)的数据都必须通过Channel 对象。Buffer 本质上说是一个容器对 象。任何发送到Channel 的数据都必须先放进Buffer ,类似的,任何从Channel 中读出的数据都先读进Buffer

在本节中你将学会在NIO 中使用ChannelBuffer

什么是Buffer
Buffer
就是一个装载数据的容器对象,数据从Buffer 中读出,或者把数据写入Buffer 中。在NIO 中添加了Buffer 对象,这是NIO 和老 IO 最重要的区别。在面向流的I/O 中,你可以把数据直接写入Stream 对象,或者直接把数据从Stream 中读出来,而不需要任何容器。

Buffer 本质上就是一个数组(array )。通常它是一个字节数组,或者其他种类的可用数组。但是Buffer 除了是一个数组之外,它还提供了结构化的访问数据的方法,并且还用来跟踪系统的读/ 写过程。

Buffer 的种类
最通常使用的BufferByteBufferByteBuffer 允许get/set (也就是读取/ 写入字节)操作底层的字节数组。

ByteBuffer 不是NIO 中唯一的buffer ,实际上对每种JAVA 原生类型都对应一个相应的Buffer
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer

每种类型的Buffer 类都是Buffer 接口的一个实例。除了ByteBuffer 之外,每个Buffer 都有完全一样的操作,区别仅仅在于他们操作的 数据类型。因为ByteBuffer 用于绝大多数标准的I/O 操作,它除了具有其他类型Buffer 共有的操作之外,还有一些个性的操作。

什么是Channel
Channel
是这样一个对象:你可以从Channel 中读取数据,并且你也可以从Channel 中写入数据。NIO 与老的IO 相比而言,Channel 就如同Stream

就像之前提到的,所有被NIO 处理的数据都必须通过Buffer 对象,而不能直接将任何字节写入Channel ;而是必须先将数据写入Buffer Buffer 中可以包含少到一个字节,多到任何字节数目。相似的,也不能直接从Channel 中读取任何字节,必须首先通过Channel 将数据读入 Buffer ,然后再从Buffer 中获取数据。

Channel 的种类
Channel
Stream 的区别在于:Channel 是双向的,而Stream 只能是单向的(Stream 必须是InputStream 或者 OutpugStream 的一个子类,即要么是输入流,要么是输出流,不能即输入又输出),而Channel 再被打开之后,即可以读,也可以写,或者同时 进行读写操作。

因为Channel 是双向的,因此它比Stream 更好的反应了底层操作系统IO 的实质。特别是在Linux 系统中,底层操作系统都是双向的。

Java NIO 入门(三)从理论到实践:使用NIO 读写
 

概述
读和写是最基础的IO 处理。从Channel 中读是非常简单的,我们只要创建一个Buffer ,然后要求ChannelBuffer 中读数据。写也很简 单,也需要创建一个Buffer ,把要写的数据填充到Buffer 中,然后要求ChannelBuffer 中的数据写出去。

在这节中,我们学习使用JAVA 程序来读写数据,顺便浏览一下NIO 的主要组件(BufferChannel 以及相关的方法)并且看看他们在读写数据时如何交互,在后面的章节中我们将依次查看每个组件的细节。

从文件中读数据
在第一个练习中,我们首先从文件中读取一些数据。如果使用老的IO ,只需要简单的创建FileInputStream ,然后从中读取数据。在NIO 中,事 情变得有些不同了,首先需要从FileInputStream 中获取Channel 对象,然后使用这个Channel 去读文件。

NIO 系统中任何时刻执行一个读操作时,都要从Channel 中读,但不是直接从Channel 中读。由于所有的数据都需要通过Buffer 承载,所以需首先从Channel 中把数据读进Buffer

因此,从文件中读数据一共有三步:
FileInputStream 中获取Channel
创建Buffer
Channel 中把数据读入Buffer

下面我们仔细看看具体时如何工作的。

三步简单的工作
第一步获取Channel 。从FileInputStream 中获取Channel
Java
代码 
FileInputStream fin = new FileInputStream( "readandshow.txt" );  
FileChannel fc = fin.getChannel(); 

第二步创建Buffer
Java
代码 
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); 

最后一步,将文件内容通过Channel 读入Buffer
Java
代码 
fc.read( buffer ); 

你一定发现,在这里,并没有告诉Channel 把多少内容读进Buffer 。在每个Buffer 中都有一套完整的内部计数系统来跟踪已经读了多少数据了,Buffer 中还剩多少空间。关于Buffer 的计数系统在随后的“Buffer 内部原理 中讲解。

写文件
使用NIO 写文件和读文件非常类似。我们还是从FileOutputStream 中获取Channel
Java
代码 
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); 
FileChannel fc = fout.getChannel(); 

下一步还是创建Buffer 并且把数据放到Buffer 中。在这种情况下数据将从一个叫做message 的数组中取出,这个数组中包含了字符串ASCII 码的字节。(The buffer.flip() buffer.put() 在后面讲解)
Java
代码 
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); 
for (int i=0; i<message.length; ++i) {  
  buffer.put( message[i] ); 
}  
buffer.flip(); 

最后一步就是把Buffer 写出去。
Java
代码 
fc.write( buffer ); 


再次注意,我们没有必要告诉Channel 总共要写多少数据,Buffer 的内部计数系统会跟踪已经写了多少数据了,还剩多少空间可以使用。

边读边写
下面看看当我们把读和写结合起来会发生什么。这个练习程序的名称叫CopyFile.java ,用来从一个文件拷贝全部数据到另一个文件。Copy.java 包含了三个基本的操作:
首先创建Buffer
然后从源文件把数据读入Buffer
最后把Buffer 写入目的文件

程序重复的进行读写、读写,直到文件读完。

CopyFile 程序能够看到如何检查程序的状态,以及如何使用clear()flip() 方法重置Buffer ,如何读数据从一个Channel 到另一个Channel

运行CopyFile 例子
因为Buffer 跟踪了自己的内部数据,因此CopyFile 程序的内部循环非常简单,如下:
Java
代码 
fcin.read( buffer );  
fcout.write( buffer ); 

首先数据从输入Channel 中读入Buffer ,然后把数据写出到输出Channel 中。因为输入和输出分别对应于两个文件,因此是两个Channel

检查读文件的状态
下一步,当我们在拷贝时需要检查读文件的状态。当没有数据可读时即执行完毕,这个状态可以通过read() 方法返回-1 来判断,如下:
Java
代码 
int r = fcin.read( buffer ); 
if (r==-1) {  
  break; 


重置Buffer
最后,在从Channel 将数据读入Buffer 前调用clear() 方法,相似的,在将Buffer 写入输出Channel 之前调用flip() 方法。
Java
代码 
buffer.clear();  
int r = fcin.read( buffer ); 
 
if (r==-1) {  
  break; 

 
buffer.flip();  
fcout.write( buffer ); 


clear()
方法重置了Buffer ,使其准备好能够将读入的数据放入Buffer 中。flip() 方法使得Buffer 准备好把Buffer 中的数据写入Channel

Java NIO 入门(四) Buffer 内部原理

概述
在这节中,我们将关注NIOBuffer 中两个重要的组件:状态变量和访问方法。
状态变量对于前面提到的 内部计数系统 而言相当重要,每次进行完读写之后,Buffer 的状态都随之改变。通过记录和跟踪这些改变,Buffer 才可以把Buffer 内部的资源管理好。

当你从Channel 中读数据时,数据首先放到了Buffer 中。在某些情况下,你可以直接把这个Buffer 写入另一个Channel 中,但是通常情况 下,你可能想看看数据内容,这个想法可以通过方法get() 实现。相似的,当你想要把原始数据放进Buffer 中,你可以使用put() 方法。

在这节中,我们将学习NIO 中的状态变量和访问方法。每个组件都会涉及到,并且有机会查看具体的使用。然而NIO 的内部计数系统起初看起来可能有些复杂,你很快会看到内部计数系统实际上为你做了哪些事情。

状态变量
总共有三个值可以被用来表示在给定的任何时刻Buffer 的状态,他们分别是:
position
limit
capacity

这三个变量跟踪了Buffer 的状态和Buffer 所包含的数据。
接下来我们将逐一检查每个细节,并且也看看为什么这样的设计适合典型的读/ 写(输入/ 输出)处理。比如仅仅这个例子,我们假设从一个Channel 拷贝数据到另一个Channel

Position
回忆一下,Buffer 实际上也就是个array 。当你从Channel 中读数据时,你把从Channel 中读出来的数据放进底层 arrayposition 变量用来跟踪截止目前为止已经写了多少数据。更精确的讲,它指示如果下次写Buffer 时数据应该进入array 的哪个位 置。因此如果已经从Channel 中读出了3 个字节,Bufferposition 会被置为3 ,指向array 中第四个位置。
相似的,如果正在向Channel 中写入数据,你需要从Buffer 中获取要写的数据,此时position 持续的跟踪你已经从Buffer 中读取了多少 数据。更精确的说,position 指示了下一次从Buffer 中读取数据时将读入array 的哪个元素。因此如果你已经向Channel 中写了5 byteBufferposition 被置为5 ,指向array 中第六个元素。

Limit
在从Buffer 中向Channel 中写数据时,limit 变量指示了还剩多少数据可以读取,在从Channel 中读取数据到Buffer 中时,limit 变量指示了还剩多少空间可供存放数据。
position
正常情况下小于或者等于limit

Capacity
Buffer
Capacity 指示Buffer 最多能够存储的数据。实际上,它指示了底层array 的容量,或者至少是底层array 允许使用的空间数量。
Limit
永远不会大于capacity

以实例来观察这三个变量
我们从一个新建的Buffer 开始。由于是例子的缘故,我们假设Buffer 有一个8 字节大小的Capacity 。此时Buffer 的状态如下所示:
 http://www.2cto.com/uploadfile/2011/0913/20110913045025598.png

回忆之前所讲的,limit 不会大于capacity ,在这个例子中,limitcapacity 都会被设为8 。我们通过在array 尾部用箭头指示的方式表示。
 http://www.2cto.com/uploadfile/2011/0913/20110913045026682.png

此时position 设置为0 。如果我们从Channel 读了一些数据进入Buffer ,下一个字节将会被存入位置为0 的地方。如果我们从Buffer 中写数据进入ChannelBuffer 中下一个被读的字节将从位置0 取得。position 的设置如下图所示:

 http://www.2cto.com/uploadfile/2011/0913/20110913045026390.png

因为capacity 一旦设置好就不会改变了,之后的讨论我们将暂时忽略capacity

第一次读操作
现在我们准备好了在我们新建的Buffer 上开始读/ 写操作了。我们开始从Channel 中读一些数据进入Buffer ,第一次读3 个字节。读之前position0 ,读完后position0 增长到3 ,如下所示:
 http://www.2cto.com/uploadfile/2011/0913/20110913045026636.png

第二次读操作
第二次读操作,我们将再读2 个字节从ChannelBuffer 中,这两个字节存储的位置从之前的position3 开始,读完后position 增加了2 变为5 。如下图:

 http://www.2cto.com/uploadfile/2011/0913/20110913045027783.png

Flip 操作
到目前位置,我们结束了从Channel 中读的操作,现在开始将数据写入输出Channel 中。在做写操作之前,我们必须调用一次flip() 方法,这个方法做了两件重要的事情:
1.
limit 设置到当前的position 处。
2.
设置position0
上幅图展示了在执行flip 之前的Buffer ,下面这幅图展示了执行了flip() 之后的Buffer

 http://www.2cto.com/uploadfile/2011/0913/20110913045028594.png

现在我们已经准备好了把数据从Buffer 中写到Channel 中。Position 已经设置为0 ,意思是说我们将获得的下一个字节为位置为0 的字节,limit 已经设置到了之前的position 处,意思是此时Buffer 中包含有之前读入Buffer 中的所有字节。

第一次写操作
在我们第一次的写操作中,我们从Buffer 中取出4 个字节,然后写入output channel 中。这个操作使得position0 增加到4 ,而limit 没有变化,如下所示:

 http://www.2cto.com/uploadfile/2011/0913/20110913045029127.png

第二次写操作
目前为止,只剩一个字节可写了。当我们调用flip() 时,limit 被设置成5 ,并且position 不能超越limit 。因此最后的写操作从 Buffer 取出一个字节并写入output Channel 中。这次写操作会将position 增加到5 ,而limit 不变。如下所示:

 http://www.2cto.com/uploadfile/2011/0913/20110913045030836.png

Clear 操作
在我们的最后一步是调用Clear 方法。这个方法会重置Buffer 以准备接收新数据。Clear 做了2 件重要的事情:
1.
设置limit0 以匹配capacity
2.
设置position0
下面的图展示了当调用完flip() 之后的Buffer 的状态:

 http://www.2cto.com/uploadfile/2011/0913/20110913045031797.png

一个能工作的Buffer
下面的代码总结了使用Buffer 从一个Channel 拷贝数据到另一个Channel
Java
代码 
while(trie) { 
  buffer.clear(); 
  int r = fcin.read( buffer ); 
 
  if (r==-1) { 
    break; 
  } 
 
  buffer.flip(); 
  fcout.write( buffer ); 

read()write() 方法极大的简化了程序,由于Buffer 处理了所有的细节。clear()flip() 方法用来切换Buffer 的读和写。

 

分享到:
评论

相关推荐

    java nio 入门

    Java NIO(New Input/Output)是Java标准库中的一部分,自Java 1.4版本引入,为开发者提供了更高效的数据处理方式。相比于传统的IO模型,NIO具有非阻塞和选择器等特性,适用于高并发、大数据量的场景。在本文中,...

    java nio入门学习,两个pdf

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java标准库提供的一种替代传统I/O模型的新技术。在Java 1.4版本中引入,NIO提供了一种全新的I/O编程方式,使得Java开发者能够更高效地处理I/O操作...

    javaNIO入门(良好排版格式).pdf

    javaNIO入门(良好排版格式).pdf

    Java NIO入门的源码

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种I/O模型,旨在提供一种更高效、更具控制力的I/O操作方式。与传统的-blocking I/O(阻塞I/O)相比,NIO的关键在于它...

    java NIO入门(中英+代码)

    Java NIO,全称为Non-Blocking Input/Output(非阻塞输入/输出),是Java从1.4版本开始引入的一种I/O模型,旨在提供一种更高效、更具控制力的I/O操作方式。与传统的 Blocking I/O(同步阻塞I/O)相比,NIO的核心特点...

    java-NIO-入门教程.docx

    "Java NIO 入门教程" Java NIO(New I/O)是 Java 语言中的一种新的输入/输出机制,自 JDK 1.4 开始引入。不同于传统的面向流的 I/O,NIO 采用面向块的 I/O 方式,提供了高速的 I/O 操作。NIO 库的主要特点是使用...

    Java Nio selector例程

    java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...

    NIO 入门.chm,NIO 入门.chm

    **NIO(New Input/Output)是Java编程语言中用于替代标准I/O(BIO,Blocking I/O)的一组API,它提供了非阻塞式的I/O操作方式,极大地提升了Java在处理I/O密集型应用时的性能。NIO在Java 1.4版本中被引入,之后在...

    java_NIO_入门(良好排版格式).pdf

    ### Java NIO 入门详解 #### 一、NIO 的背景与意义 **NIO (New Input/Output)** 是 Java 在 JDK 1.4 中引入的一个全新的输入输出库,旨在改进原有的 IO 库(主要位于 `java.io.*` 包中)的性能和功能。传统的 Java...

    IBM Java文档库 NIO 入门

    《IBM Java文档库 NIO 入门》这篇教程主要针对的是Java 1.4引入的New Input/Output (NIO)库,这是一个重要的更新,旨在提高Java程序的I/O性能,特别是面向块的I/O操作。NIO弥补了传统I/O(基于java.io.*包)的不足,...

    Java NIO 简单入门

    Java NIO(Non-blocking Input/Output)是一种在Java中处理I/O操作的新方式,相比于传统的BIO(Blocking I/O),NIO提供了更高效、更具扩展性的I/O模型。NIO的核心在于非阻塞,它允许Java程序在等待数据就绪时进行...

    Java视频教程 Java游戏服务器端开发 Netty NIO AIO Mina视频教程

    [第4节] JavaNIO流-通道1.flv [第5节] Java NIO流-通道2.flv [第6节] Java NIO流-socket通道操作.flv [第7节] Java NIO流-文件通道操作.flv [第8节] Java NIO流-选择器 .flv [第9节] Java NIO流-选择器操作.flv...

    java NIO socket聊天室

    可以作为NIO socket入门的例子,Reactor模式,重点理解key.attach, jar文件里包含了源代码 1,运行server.bat启动服务器,可以打开编辑,修改端口号 2,运行client.bat启动客户端,可以打开编辑,ip,和端口号 3...

    Java NIO总结

    Java NIO的总结, 对于新人入门理解很好, 使用Markdown编写

    NIO入门pdf分享

    《NIO入门》一书是理解Java NIO(New Input/Output)的重要参考资料,NIO在Java编程中扮演着至关重要的角色,特别是在处理高并发、大数据传输等场景下。本PDF文档将引领读者深入理解这一核心概念。 NIO,全称New ...

Global site tag (gtag.js) - Google Analytics