时间过得真快,一晃就大四上学期了,马上面临着要找工作的压力和挑战,虽然我不怕失败,但心里还是忐忑不安,最近闲来无事,就把一直没有时间看的NIO顺便了解了解下。
本次NIO系列都是基于IBM的Developerworks上的一篇pdf文档,我也就是拾人牙慧,如有值得商榷的地方,欢迎大家拍砖。
本次主要讨论一下Channels和Buffers。
Overview:
Channels和Buffers是构成NIO的主要部件,他们被广泛的使用在每一个IO操作中。Channels类似于原IO包中的流的概念。所有流动的数据(不管是流进还是流入)都必须经过Channel。而Buffer则在本质上是一个容器,所有被送到channel中的数据首先必须放在buffer中,同样的,所有从channel中读入的数据也应先放在buffer中。
what is a buffer?
buffer是一个存装数据的实体,可以被写入或者只能从中读取数据。这些附加的属性使得buffer与原IO包中有明显的差异。在原来以流为主的IO中,你直接从流中读入数据,或者将数据写入流中。但在NIO中,所有的数据都被buffer持有,当数据被读入时,它将被直接读入到一个buffer中,当数据被写出时,也将写入到buffer中,所以在NIO中获得数据,都是从buffer中获取的。一个buffer本质上是一个数组,通常是一个字节数组,当然也有其他类型的数组。但且不仅仅是一个数组,它提供了有组织的获得数据的方式,同时也保证了系统的读/写进程。
Kind of buffers
在大多数应用条件下,一个buffer就是一个bytebuffer,bytebuffer提供了get和set操作底层的字节数组。和刚刚说的一样,buffer还可以是CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer等等,每一个buffer类都实现了buffer这个接口,除了bytebuffer外,其他的buffer都有着相同的操作方法,当然操作的数据类型是不一样的。因为bytebuffer被大多数的标准IO操作所使用,所以有一些独特的共享数据的方法。
what is the channel?
一个channel就是一个可以读入数据和写入数据的构件,相比于原来的IO,就相当于stream。正如前面提到的,所有的数据都将经过buffer,你不可能直接将数据写入channel,相反你不得不将数据写入buffer。同样的,你不能直接从channel中读入数据,你必须将一个channel和一个buffer关联起来,然后从buffer中读入数据。
Kinds of channels
channels是双向的,这点不同于以往的stream。stream必须是inputstream或者是outputstream的子类,是单向的,但channel既可以读,或者写,或者两件兼之。因为channel是双向的,所以能比stream更形象的描述操作系统的模型。在当今比较流行的unix上,底层操作系统的管道也是双向的。
From theory to practice : Reading and writing in NIO
overview
读和写是IO的基本操作,从channel中读取数据是简单的,我们只需要创建一个buffer然后让channel从这个buffer中读出数据即可。写同样是简单的,我们创建一个buffer,然后往里填充数据,再让channel从中写出。(注意这里的读出和写入,原文是这样的:Reading from a channel is simple: we simply create a buffer and then ask a channel to read data into it. Writing is
also fairly simply: we create a buffer, fill it with data, and then ask a channel to write from it.)在这一节中,我们接下来将用java语言来编写一些读或者写的实例,并复习一下NIO的组件(buffer,channel和一些相关的方法)。
Reading from a file
我们将从文件中读入数据这个场景作为我们的第一个实例。如果我们使用原始的IO,我们可以创建一个FileInoutStream来读取,在NIO中,事情变得有点复杂,我们必须先从FileInputStream上创建一个channel,然后用这个channel读取数据。
不管什么时候你从NIO中使用一个读操作,你将不得不从channel中读入数据,但却不是直接的从channel中读入。由于所有的数据都存在于buffer中,你得使用channel从buffer中读取。
所以从一个文件中读取数据需要以下几步:1.从FileInputStream中得到一个channel。2.创建一个buffer。3.使用channel从buffer中读取数据。
package com.nio.example; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class UseChannelReadFromFileTest { public static void main(String[] args) throws Exception { /** 首先创建一个FileInputStream 和channel */ FileInputStream fin = new FileInputStream("./src/ex.txt"); FileChannel channel = fin.getChannel(); /** 然后创建一个buffer */ ByteBuffer buffer = ByteBuffer.allocate(1024); /** 使用channel将数据读入到buffer中 */ int len = 0; while (-1 != (len = channel.read(buffer))) { System.out.print(translate2Char(buffer.array(), len)); buffer.clear(); } channel.close(); fin.close(); } /** 将字节数组转换成字符数组,以便还原文件内容 */ private static char[] translate2Char(byte[] array, int len) { char[] seq = new char[len]; for (int i = 0; i < len; i++) { seq[i] = (char) array[i]; } return seq; } }
我们可以看到bytebuffer中还有很多值得探究的地方,不要着急,后面我们会细细道来。
Writing to a file
和读文件差不多,我们首先也要从一个FileOutputStream中获得一个channel。然后下一步就是创建一个buffer然后放入数据,buffer.flip()和buffer.put()的调用我们将在后面解释。
ByteBuffer buffer = ByteBuffer.allocate(1024); for( int i = 0 ; i < message.length; ++i ){ buffer.put(message[i]); } buffer.flip();
最后,我们写入buffer。
fc.write( buffer );
注意我们这里并不需要告诉channel我们有多少数据期望写入,buffer内部有一种机制保证了这种正确性。
完整实例如下:
package com.nio.example; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class UseChannelWriteToFileTest { public static void main(String[] args) throws Exception { /** 首先从fileoutputstream中获得channel */ FileOutputStream fout = new FileOutputStream("./src/wt.txt"); FileChannel channel = fout.getChannel(); /** 准备一个装有数据的缓冲区 */ ByteBuffer buffer = ByteBuffer.allocate(1024); String message = "Change project.properties compass-version to the actual version number (it should probably already be set)"; /** 填充数据 */ fileData2Buffer(message,buffer); buffer.flip(); /** 将数据写回文件 */ channel.write(buffer); channel.close(); fout.close(); } /** 将message中的数据填充入buffer中 */ private static void fileData2Buffer(String message, ByteBuffer buffer) { int len = message.getBytes().length; for( int i = 0 ; i < len; ++i ){ buffer.put(message.getBytes()[i]); } } }
Reading and writing together
下面我们看看将读和写结合在一起会发生什么。我们这个实例的名字叫做copyFile.java,它将一个文件的数据拷贝到另一个文件。copyFile有三个基本的操作:1.首先创建一个buffer,2.从源文件中读入数据并存入buffer中,3.然后将buffer写回到目标文件中。程序就这样往复循环,读,写,读,写。。。。。。直到源文件被读完。
这个copyFile程序将让你看到我们如何检查操作的状态,就如我们使用clear()和flip()方法去重置buffer让其准备读入或者新的数据或者写入到另外的一个channel中。
因为是对buffer中的数据进行操作,所以内层循环相当simple,如下:
fcin.read( buffer ); fcout.write( buffer );
第一行将数据从channel中读入到buffer中,第二行将buffer中的数据写入到channel中。
下一步呢,我们将检查是否已经拷贝成功,我们可以观察read()方法的返回值,如果返回--1,则文件读取完毕。
int r = fcin.read( buffer ); if ( r == -1 ) { break; }
重置buffer:
我们在将数据读入input channel之前调用claer()方法,同样的,我们在将数据写入output channel之前调用flip()方法,如下:
buffer.clear(); int r = fcin.read( buffer ); if( -1 == r ) { break; } buffer.flip(); fout.write( buffer );
clear()方法重置了buffer,让其可以将数据读入其中,flip()方法准备buffer中新读入的数据写入到一个output channel中。完整实例如下:
package com.nio.example; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class CopyFile { public static void main(String[] args) throws Exception { /** 首先获得input channel和output channel */ FileInputStream fin = new FileInputStream("./src/ex.txt"); FileChannel inChannel = fin.getChannel(); FileOutputStream fout = new FileOutputStream("./src/ex_backup.txt"); FileChannel outChannel = fout.getChannel(); /** 然后开辟一个缓冲区 */ ByteBuffer buffer = ByteBuffer.allocate(1024); /** 然后从文件中读入数据到buffer中 */ while (-1 != inChannel.read(buffer)) { buffer.flip(); outChannel.write(buffer); buffer.clear(); } outChannel.close(); fout.close(); inChannel.close(); fin.close(); } }好了,第一篇就写到这里,我也是写作新手,有什么不对的,希望大家海涵。