I/O即输入输出,是计算机与外界世界的一个接口。IO操做的实际主题是操做系统。在java编程中,通常使用流的方式来处理IO,全部的IO都被视做是单个字节的移动,经过stream对象一次移动一个字节。流IO负责把对象转换为字节,而后再转换为对象。html
关于Java IO相关知识请参考个人另外一篇文章:Java IO 详解java
NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的做用和目的,但实现方式不一样,NIO主要用到的是块,因此NIO的效率要比IO高不少。编程
在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另外一套就是网络编程NIO,本篇文章重点介绍标NIO,关于网络编程NIO请见Java NIO详解(二)。api
NIO和IO最大的区别是数据打包和传输方式。IO是以流的方式处理数据,而NIO是以块的方式处理数据。数组
面向流的IO一次一个字节的处理数据,一个输入流产生一个字节,一个输出流就消费一个字节。为流式数据建立过滤器就变得很是容易,连接几个过滤器,以便对数据进行处理很是方便而简单,可是面向流的IO一般处理的很慢。网络
面向块的IO系统以块的形式处理数据。每个操做都在一步中产生或消费一个数据块。按块要比按流快的多,但面向块的IO缺乏了面向流IO所具备的优雅性和简单性。异步
Buffer和Channel是标准NIO中的核心对象(网络NIO中还有个Selector核心对象,具体请参考Java NIO详解(二)),几乎每个IO操做中都会用到它们。ui
Channel是对原IO中流的模拟,任何来源和目的数据都必须经过一个Channel对象。一个Buffer实质上是一个容器对象,发给Channel的全部对象都必须先放到Buffer中;一样的,从Channel中读取的任何数据都要读到Buffer中。this
Buffer是一个对象,它包含一些要写入或读出的数据。在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操做,而必须经过 Buffer 来进行,即 Channel 是经过 Buffer 来读写数据的。操作系统
在NIO中,全部的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,一般是一个字节数据,但也能够是其余类型的数组。但一个缓冲区不只仅是一个数组,重要的是它提供了对数据的结构化访问,并且还能够跟踪系统的读写进程。
使用 Buffer 读写数据通常遵循如下四个步骤:
当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,须要经过 flip() 方法将 Buffer 从写模式切换到读模式。在读模式下,能够读取以前写入到 Buffer 的全部数据。
一旦读完了全部的数据,就须要清空缓冲区,让它能够再次被写入。有两种方式能清空缓冲区:调用 clear() 或 compact() 方法。clear() 方法会清空整个缓冲区。compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
Buffer主要有以下几种:
Channel是一个对象,能够经过它读取和写入数据。能够把它看作IO中的流。可是它和流相比还有一些不一样:
正如上面提到的,全部数据都经过Buffer对象处理,因此,您永远不会将字节直接写入到Channel中,相反,您是将数据写入到Buffer中;一样,您也不会从Channel中读取字节,而是将数据从Channel读入Buffer,再从Buffer获取这个字节。
由于Channel是双向的,因此Channel能够比流更好地反映出底层操做系统的真实状况。特别是在Unix模型中,底层操做系统一般都是双向的。
在Java NIO中Channel主要有以下几种类型:
IO中的读和写,对应的是数据和Stream,NIO中的读和写,则对应的就是通道和缓冲区。NIO中从通道中读取:建立一个缓冲区,而后让通道读取数据到缓冲区。NIO写入数据到通道:建立一个缓冲区,用数据填充它,而后让通道用这些数据来执行写入。
咱们已经知道,在NIO系统中,任什么时候候执行一个读操做,您都是从Channel中读取,而您不是直接从Channel中读取数据,由于全部的数据都必须用Buffer来封装,因此您应该是从Channel读取数据到Buffer。
所以,若是从文件读取数据的话,须要以下三步:
下面咱们看一下具体过程:
第一步:获取通道
FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel();
第二步:建立缓冲区
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
第三步:将数据从通道读到缓冲区
fc.read( buffer );
相似于从文件读数据,
第一步:获取一个通道
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel();
第二步:建立缓冲区,将数据放入缓冲区
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] ); } buffer.flip();
第三步:把缓冲区数据写入通道中
fc.write( buffer );
CopyFile是一个很是好的读写结合的例子,咱们将经过CopyFile这个实力让你们体会NIO的操做过程。CopyFile执行三个基本的操做:建立一个Buffer,而后从源文件读取数据到缓冲区,而后再将缓冲区写入目标文件。
/** * 用java NIO api拷贝文件 * @param src * @param dst * @throws IOException */ public static void copyFileUseNIO(String src,String dst) throws IOException{ //声明源文件和目标文件 FileInputStream fi=new FileInputStream(new File(src)); FileOutputStream fo=new FileOutputStream(new File(dst)); //得到传输通道channel FileChannel inChannel=fi.getChannel(); FileChannel outChannel=fo.getChannel(); //得到容器buffer ByteBuffer buffer=ByteBuffer.allocate(1024); while(true){ //判断是否读完文件 int eof =inChannel.read(buffer); if(eof==-1){ break; } //重设一下buffer的position=0,limit=position buffer.flip(); //开始写 outChannel.write(buffer); //写完要重置buffer,重设position=0,limit=capacity buffer.clear(); } inChannel.close(); outChannel.close(); fi.close(); fo.close(); }
上面程序中有三个地方须要注意
当没有更多的数据时,拷贝就算完成,此时 read() 方法会返回 -1 ,咱们能够根据这个方法判断是否读完。
int r= fcin.read( buffer ); if (r==-1) { break; }
flip、clear这两个方法即是用来设置这些值的。
咱们先看一下flip的源码:
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
在上面的FileCopy程序中,写入数据以前咱们调用了buffer.flip();
方法,这个方法把当前的指针位置position设置成了limit,再将当前指针position指向数据的最开始端,咱们如今能够将数据从缓冲区写入通道了。 position 被设置为 0,这意味着咱们获得的下一个字节是第一个字节。 limit 已被设置为原来的 position,这意味着它包括之前读到的全部字节,而且一个字节也很少。
先看一下clear的源码:
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
在上面的FileCopy程序中,写入数据以后也就是读数据以前,咱们调用了 buffer.clear();
方法,这个方法重设缓冲区以便接收更多的字节。上图显示了在调用 clear() 后缓冲区的状态。
转载请说明出处,原文连接:http://blog.csdn.net/suifeng3051/article/details/48160753