黑马程序员_O‘Reilly java nio学习笔记之通道_通道基础&& Scatter/Gather

来源:互联网 发布:js null.type 编辑:程序博客网 时间:2024/05/05 03:10

---------------------- android培训、java培训、期待与您交流! ----------------------



1.通道基础

1.1   打开通道

    通道是访问I/O 服务的导管。I/O 可以分为广义的两大类别:File I/O 和 Stream I/O。那么相应地有两种类型的通道,它们是文件(file)通道和套接字(socket)通道:有一个FileChannel类和三个 socket通道类:SocketChannel、ServerSocketChannel和  DatagramChannel。 

通道可以以多种方式创建。Socket通道有可以直接创建新socket通道的工厂方法。但是一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或  FileOutputStream对象上调用getChannel( )方法来获取。您不能直接创建一个FileChannel对象。例:

SocketChannel sc = SocketChannel.open( ); 

sc.connect (new InetSocketAddress ("somehost", someport)); 

ServerSocketChannel ssc = ServerSocketChannel.open( ); 

ssc.socket( ).bind (new InetSocketAddress (somelocalport)); 

DatagramChannel dc = DatagramChannel.open( ); 

RandomAccessFile raf = new RandomAccessFile ("somefile", "r"); 

FileChannel fc = raf.getChannel( ); 

后面会详细讨论通道。

1.2  使用通道 

通道可以是单向(unidirectional)或者双向的(bidirectional)。一个 channel类可能实现定义read()方法的ReadableByteChannel接口,而另一个channel类也许实现WritableByteChannel接口以提供write()方法。这两个接口都实现了Channel接口。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。 ByteChannel接口继承了以上两个接口,但是它本身并没有定义方法,只有从父接口继承的read和write方法。

通道会连接一个特定I/O 服务且通道实例(channel instance)的性能受它所连接的I/O 服务的特征限制,记住这很重要。一个连接到只读文件的Channel实例不能进行写操作,即使该实例所属的类可能有write()方法。 

 以下例子运用通道和缓冲区的知识实现从控制台读取数据,然后原样输出到控制台的功能:

public class ChannelCopy {

public static void main(String[] argv) throws IOException {

ReadableByteChannel source = Channels.newChannel(System.in);

WritableByteChannel dest = Channels.newChannel(System.out);

channelCopy1(source, dest);                           

// alternatively, call channelCopy2 (source, dest);

source.close();

dest.close();

}

private static void channelCopy1(ReadableByteChannel src,

WritableByteChannel dest) throws IOException {

ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);

while (src.read(buffer) != -1) {

//首先将限制设置为当前位置,然后将位置设置为 0

buffer.flip();

dest.write(buffer);

//丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。 

buffer.compact();

}

buffer.flip();

while (buffer.hasRemaining()) {

dest.write(buffer);

}

}

private static void channelCopy2(ReadableByteChannel src,

WritableByteChannel dest) throws IOException {

ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);

while (src.read(buffer) != -1) {

buffer.flip();

while (buffer.hasRemaining()) {

dest.write(buffer);

}

//将位置设置为 0,将限制设置为容量,并丢弃标记

buffer.clear();

}

}

}

通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行。非阻塞模式的通道永远不会让调用的线程休眠。请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如 sockets和 pipes才能使用非阻塞模式。 

1.3  关闭通道

     可以通过close方法通道。关闭某个通道后,试图对其调用 I/O 操作就会导致 

ClosedChannelException 被抛出。 如果此通道已经关闭,则调用此方法无效。 可在任意时间调用此方法。但是如果其他某个线程已调用此方法,那么在第一个调用完成前另一个调用将被阻塞,之后该方法将返回,不受任何影响。 

可中断的通道也是可以异步关闭的。实现InterruptibleChannel接口的通道可以在任何时候被关闭,即使有另一个被阻塞的线程在等待该通道上的一个I/O 操作完成。当一个通道被关闭时,休眠在该通道上的所有线程都将被唤醒并接收到一个AsynchronousCloseException异常。接着通道就被关闭并将不再可用。 

2. Scatter/Gather 

通道提供了一种被称为Scatter/Gather的重要新功能(有时也被称为矢量I/O),它是指在多个缓冲区上实现一个简单的I/O 操作。对于一个 write操作而言,数据是从几个缓冲区按顺序抽取(称为gather)并沿着通道发送的。缓冲区本身并不需要具备这种gather的能力,该gather过程的效果就好比全部缓冲区的内容被连结起来,并在发送数据前存放到一个大的缓冲区中。对于read操作而言,从通道读取的数据会按顺序被散布(称为scatter)到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。关于此功能的接口为ScatteringByteChannel 和GatheringByteChannel,他们分别继承了ReadableByteChannel和WritableByteChannel接口,并分别定义了自己的readwrite方法。DatagramChannel, FileChannel, SocketChannel 三个类都分别实现了以上两个接口的方法。

下面的代码中,我们假定channel 连接到一个有48字节数据等待读取的socket上: 

ByteBuffer header = ByteBuffer.allocateDirect (10); 

ByteBuffer body = ByteBuffer.allocateDirect (80); 

ByteBuffer [] buffers = { header, body }; 

int bytesRead = channel.read (buffers); 

一旦 read( )方法返回,bytesRead 就被赋予值48header 缓冲区将包含前10个从通道读取的字节而body 缓冲区则包含接下来的38个字节。通道会自动地将数据scatter到这两个缓冲区中。缓冲区已经被填充了(尽管此例中body缓冲区还有空间填充更多数据),那么将需要被flip以便其中数据可以被抽取。同理:

            long bytesWritten = channel.write (buffers); 

可以将buffers数组中的数据写入到通道中。

offset length参数版本的read( )  write( )方法使得我们可以使用缓冲区阵列的子集缓冲区。这里的offset值指哪个缓冲区将开始被使用,而不是指数据的offset。这里的 length参数指示要使用的缓冲区数量。举个例子,假设我们有一个五元素的fiveBuffers阵列,它已经被初始化并引用了五个缓冲区,下面的代码将会写第二个、第三个和第四个缓冲区的内容: 

            Int bytesRead = channel.write (fiveBuffers, 1, 3); 

使用得当的话,Scatter/Gather会是一个极其强大的工具。它允许您委托操作系统来完成辛苦活:将读取到的数据分开存放到多个存储桶(bucket)或者将不同的数据区块合并成一个整体。这是一个巨大的成就,因为操作系统已经被高度优化来完成此类工作了,它节省了您来回移动数据的工作,也就避免了缓冲区拷贝和减少了您需要编写、调试的代码数量。



---------------------- android培训、java培训、期待与您交流! ----------------------

详细请查看:http://edu.csdn.net/heima