您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
Ready 发布于2013年08月17日 10:07

原创 Java nio入门教程详解(十五)

2360 次浏览 读完需要≈ 14 分钟

内容目录

3.1.2 使用通道

我们在第二章的学习中已经知道了,通道将数据传输给ByteBuffer对象或者从ByteBuffer对象获取数据进行传输。 将图 3-2 中大部分零乱内容移除可以得到图3-3所示的UML类图。子接口API代码如下:

public interface ReadableByteChannel extends Channel
{
    public int read (ByteBuffer dst) throws IOException;
}

public interface WritableByteChannel extends Channel
{
    public int write (ByteBuffer src) throws IOException;
}

public interface ByteChannel extends ReadableByteChannel, WritableByteChannel
{
}

图 3-3 ByteChannel 接口

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

图 3-3 显示了一个ByteChannel接口,该接口引申出了ReadableByteChannelWritableByteChannel两个接口。ByteChannel接口本身并不定义新的API方法,它是一种用来聚集它自己以一个新名称继承的多个接口的便捷接口。根据定义,实现ByteChannel接口的通道会同时实现ReadableByteChannelWritableByteChannel两个接口,所以此类通道是双向的。这是简化类定义的语法糖(syntactic sugar),它使得用操作器(operator)实例来测试通道对象变得更加简单。

这是一种好的类设计技巧,如果您在写您自己的Channel实现的话,您可以适当地实现这些接口。不过对于使用java.nio.channels包中标准通道类的程序员来说,这些接口并没有太大的吸引力。假如您快速回顾一下图 3-2 或者向前跳跃到关于file和socket通道的章节,您将发现每一个file或socket通道都实现全部三个接口。从类定义的角度而言,这意味着全部file和socket通道对象都是双向的。这对于sockets不是问题,因为它们一直都是双向的,不过对于files却是个问题了。

我们知道,一个文件可以在不同的时候以不同的权限打开。从FileInputStream对象的getChannel()方法获取的FileChannel对象是只读的,不过从接口声明的角度来看却是双向的,因为FileChannel实现ByteChannel接口。在这样一个通道上调用write()方法将抛出未经检查的NonWritableChannelException异常,因为FileInputStream对象总是以read-only的权限打开文件。

通道会连接一个特定I/O服务且通道实例(channel instance)的性能受它所连接的 I/O 服务的特征限制,记住这很重要。一个连接到只读文件的Channel实例不能进行写操作,即使该实例所属的类可能有write()方法。基于此,程序员需要知道通道是如何打开的,避免试图尝试一个底层 I/O服务不允许的操作。

// A ByteBuffer named buffer contains data to be written
FileInputStream input = new FileInputStream (fileName);
FileChannel channel = input.getChannel( );
// This will compile but will throw an IOException
// because the underlying file is read-only
channel.write (buffer);

根据底层文件句柄的访问模式,通道实例可能不允许使用read()write()方法。

ByteChannelread()write()方法使用ByteBuffer对象作为参数。两种方法均返回已传输的字节数,可能比缓冲区的字节数少甚至可能为零。缓冲区的位置也会发生与已传输字节相同数量的前移。如果只进行了部分传输,缓冲区可以被重新提交给通道并从上次中断的地方继续传输。该过程重复进行直到缓冲区的hasRemaining()方法返回false值。例 3-1 表示了如何从一个通道复制数据到另一个通道。

package com.ronsoft.books.nio.channels;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.Channels;
import java.io.IOException;
/**
* Test copying between channels.
* @author Ron Hitchens(ron@ronsoft.com)
*/
public class ChannelCopy{
    /**
    * This code copies data from stdin to stdout. Like the 'cat'
    * command, but without any useful options.
    */
    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();
    }
    /**
    * Channel copy method 1. This method copies data from the src
    * channel and writes it to the dest channel until EOF on src.
    * This implementation makes use of compact( ) on the temp buffer
    * to pack down the data if the buffer wasn't fully drained. This
    * may result in data copying, but minimizes system calls. It also
    * requires a cleanup loop to make sure all the data gets sent.
    */
    private static void channelCopy1 (ReadableByteChannel src, WritableByteChannel dest) throws IOException{
        ByteBuffer buffer = ByteBuffer.allocateDirect (16 * 1024);
        while (src.read(buffer) != -1) {
            // Prepare the buffer to be drained
            buffer.flip();
            // Write to the channel; may block
            dest.write(buffer);
            // If partial transfer, shift remainder down
            // If buffer is empty, same as doing clear()
            buffer.compact();
        }
        // EOF will leave buffer in fill state
        buffer.flip();
        // Make sure that the buffer is fully drained
        while (buffer.hasRemaining()) {
            dest.write(buffer);
        }
    }
    /**
    * Channel copy method 2. This method performs the same copy, but
    * assures the temp buffer is empty before reading more data. This
    * never requires data copying but may result in more systems calls.
    * No post-loop cleanup is needed because the buffer will be empty
    * when the loop is exited.
    */
    private static void channelCopy2 (ReadableByteChannel src, WritableByteChannel dest) throws IOException{
        ByteBuffer buffer = ByteBuffer.allocateDirect (16 * 1024);
        while (src.read (buffer) != -1) {
            // Prepare the buffer to be drained
            buffer.flip();
            // Make sure that the buffer was fully drained
            while (buffer.hasRemaining()) {
                dest.write(buffer);
            }
            // Make the buffer empty, ready for filling
            buffer.clear();
        }
    }
}

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

从图 3-2 可以看出,socket通道类从SelectableChannel引申而来。从SelectableChannel引申而来的类可以和支持有条件的选择(readiness selectio)的选择器(Selectors)一起使用。将非阻塞I/O 和选择器组合起来可以使您的程序利用多路复用 I/O(multiplexed I/O)。选择和多路复用将在 第四章中予以讨论。关于怎样将sockets置于非阻塞模式的细节会在3.5节中涉及。

Java nio入门教程详解(十六)

  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

0 条评论

撰写评论