原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-channels/
通道是继缓冲区之后的java.nio的第二项重大创新,我们在我之前的教程中已详细了解到。 通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通常是文件或套接字)之间有效传输数据的介质。 通常,通道与操作系统文件描述符具有一对一的关系。 通道类提供了维持平台独立性所需的抽象,但仍可以对现代操作系统的本机 I/O 能力进行建模。 通道是网关,通过它可以以最小的开销访问操作系统的本机 I/O 服务,而缓冲区是通道用来发送和接收数据的内部端点。
NIO 通道基础
在层次结构的顶部,有Channel接口,如下所示:
package java.nio.channels;public interface Channel{public boolean isOpen();public void close() throws IOException;}
由于依赖底层平台的各种因素,Channel实现在操作系统之间存在根本性的差异,因此通道 API(或接口)仅描述了可以完成的工作。 Channel实现通常使用本机代码执行实际工作。 通过这种方式,通道接口使您能够以受控且可移植的方式访问低级 I/O 服务。
从顶级Channel接口可以看到,所有通道只有两个共同的操作:检查通道是否打开(isOpen())和关闭打开的通道(close())。
打开通道
众所周知,I/O 分为两大类:文件 I/O 和流 I/O 。 因此,通道有两种类型也就不足为奇了:文件和套接字。 FileChannel类和SocketChannel类用于处理这两个类别。
仅可以通过在打开的RandomAccessFile,FileInputStream或FileOutputStream对象上调用getChannel()方法来获得FileChannel对象。 您不能直接创建FileChannel对象。
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");FileChannel fc = raf.getChannel();
与FileChannel相反,套接字通道具有工厂方法来直接创建新的套接字通道。 例如
//How to open SocketChannelSocketChannel sc = SocketChannel.open();sc.connect(new InetSocketAddress("somehost", someport));//How to open ServerSocketChannelServerSocketChannel ssc = ServerSocketChannel.open();ssc.socket().bind (new InetSocketAddress (somelocalport));//How to open DatagramChannelDatagramChannel dc = DatagramChannel.open();
上面的方法返回一个相应的套接字通道对象。 它们不是RandomAccessFile.getChannel()的新通道的来源。 如果已经存在,则它们返回与套接字关联的通道。 他们从不创造新的渠道。
使用通道
正如我们已经学习到缓冲区的教程一样,该教程介绍了通道与ByteBuffer对象之间的数据传输。 大多数读/写操作由从下面的接口实现的方法执行。
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{}
通道可以是单向或双向。 给定的通道类可能实现ReadableByteChannel,它定义了read()方法。 另一个可能会实现WritableByteChannel以提供write()。 实现这些接口中的一个或另一个的类是单向的:它只能在一个方向上传输数据。 如果一个类实现两个接口(或扩展两个接口的ByteChannel),则它是双向的,可以双向传输数据。
如果您查看通道类,则会发现每个文件和套接字通道都实现了这三个接口。 就类定义而言,这意味着所有文件和套接字通道对象都是双向的。 对于套接字来说,这不是问题,因为它们始终是双向的,但是对于文件来说,这是一个问题。 从FileInputStream对象的getChannel()方法获得的FileChannel对象是只读的,但是在接口声明方面是双向的,因为 FileChannel实现了ByteChannel 。 在这样的通道上调用write()会抛出未选中的NonWritableChannelException,因为FileInputStream总是打开具有只读权限的文件。 因此,请记住,当通道连接到特定的 I/O 服务时,通道实例的特性将受到与其连接的服务的特性的限制。 连接到只读文件的Channel实例无法写入,即使该Channel实例所属的类可能具有write()方法。 程序员要知道如何打开通道,而不要尝试执行底层 I/O 服务不允许的操作。
FileInputStream input = new FileInputStream ("readOnlyFile.txt");FileChannel channel = input.getChannel();// This will compile but will throw an IOException// because the underlying file is read-onlychannel.write (buffer);
ByteChannel的read()和write()方法将ByteBuffer对象作为参数。 每个都返回传输的字节数,该字节数可以小于缓冲区中的字节数,甚至为零。 缓冲区的位置将增加相同的量。 如果执行了部分传输,则可以将缓冲区重新提交给通道以继续传输数据,并从该处停止传输。 重复直到缓冲区的hasRemaining()方法返回false。
在下面的示例中,我们将数据从一个通道复制到另一个通道(或从一个文件复制到另一个文件):
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.ReadableByteChannel;import java.nio.channels.WritableByteChannel;public class ChannelCopyExample{public static void main(String args[]) throws IOException{FileInputStream input = new FileInputStream ("testIn.txt");ReadableByteChannel source = input.getChannel();FileOutputStream output = new FileOutputStream ("testOut.txt");WritableByteChannel dest = output.getChannel();copyData(source, dest);source.close();dest.close();}private static void copyData(ReadableByteChannel src, WritableByteChannel dest) throws IOException{ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);while (src.read(buffer) != -1){// Prepare the buffer to be drainedbuffer.flip();// Make sure that the buffer was fully drainedwhile (buffer.hasRemaining()){dest.write(buffer);}// Make the buffer empty, ready for fillingbuffer.clear();}}}
通道可以在阻塞或非阻塞模式下运行。 处于非阻塞模式的通道永远不会使调用线程进入睡眠状态。 所请求的操作要么立即完成,要么返回结果表明未完成任何操作。 只能将面向流的通道(例如套接字和管道)置于非阻塞模式。
关闭通道
要关闭通道,请使用其close()方法。 与缓冲区不同,通道在关闭后不能重新使用。 开放通道表示与特定 I/O 服务的特定连接,并封装了该连接的状态。 关闭通道后,该连接将丢失,并且该通道不再连接任何东西。
在通道上多次调用close()是没有害处的。 随后在关闭通道上对close()的调用无济于事,并立即返回。
可以想到,套接字通道可能需要大量时间才能关闭,具体取决于系统的网络实现。 某些网络协议栈可能会在输出耗尽时阻止关闭。
通道的打开状态可以使用isOpen()方法进行测试。 如果返回true,则可以使用该通道。 如果为false,则该通道已关闭,无法再使用。 尝试读取,写入或执行任何其他需要通道处于打开状态的操作将导致ClosedChannelException。
祝您学习愉快!
