时间过得真快,一晃就大四上学期了,马上面临着要找工作的压力和挑战,虽然我不怕失败,但心里还是忐忑不安,最近闲来无事,就把一直没有时间看的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();
}
}
好了,第一篇就写到这里,我也是写作新手,有什么不对的,希望大家海涵。