Java NIO 通道
Channel 接口:
package java.nio.channels;
public interface Channel
{
public boolean isOpen();
public void close() throws IOException;
}
由于取决于底层平台的各种因素,“通道”实现在操作系统之间存在根本性差异,因此通道 API(或者接口)只是描述了可以做什么。
Channel实现通常使用本机代码来执行实际工作。
通过这种方式,通道接口允许我们以可控且可移植的方式访问低级 I/O 服务。
正如你在顶层 Channel接口中看到的,所有通道只有两个通用操作:检查一个通道是否打开(isOpen())和关闭一个打开的通道(close())。
Java 使用通道
正如我们在缓冲区教程中已经了解到的,通道将数据传输到 ByteBuffer 对象和从 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),则它是双向的并且可以双向传输数据。
如果我们查看Channel类,我们会发现每个文件和套接字通道都实现了所有这三个接口。
就类定义而言,这意味着所有文件和套接字通道对象都是双向的。
这对套接字来说不是问题,因为它们总是双向的,但对于文件来说却是个问题。
从 FileInputStream 对象的 getChannel() 方法获得的 FileChannel 对象是只读的,但在接口声明方面是双向的,因为 FileChannel 实现了 ByteChannel 。
在这样的通道上调用 write()将抛出未经检查的 NonWritableChannelException,因为 FileInputStream总是以只读权限打开文件。
所以请记住,当一个通道连接到一个特定的 I/O 服务时,一个通道实例的能力将受到它所连接的服务的特性的限制。
连接到只读文件的 Channel 实例无法写入,即使该通道实例所属的类可能具有 write()方法。
程序员应该知道通道是如何打开的,而不是尝试底层 I/O 服务不允许的操作。
示例 3:我们无法使用任何通道写入只读文件
FileInputStream input = new FileInputStream ("readOnlyFile.txt");
FileChannel channel = input.getChannel();
// This will compile but will throw an IOException
// because the underlying file is read-only
channel.write (buffer);
ByteChannel的 read()和 write()方法将 ByteBuffer对象作为参数。
每个都返回传输的字节数,它可以小于缓冲区中的字节数,甚至为零。
缓冲区的位置将被推进相同的数量。
如果执行了部分传输,则可以将缓冲区重新提交到通道,以从中断处继续传输数据。
重复直到缓冲区的 hasRemaining()方法返回 false。
在下面的示例中,我们将数据从一个通道复制到另一个通道(或者从一个文件复制到另一个文件)。
示例 4:在 Java 中将数据从一个通道复制到另一个通道
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 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();
}
}
}
通道可以在阻塞或者非阻塞模式下运行。
非阻塞模式下的通道永远不会让调用线程进入睡眠状态。
请求的操作或者立即完成,或者返回一个结果,表明什么都没做。
只有面向流的通道,例如套接字和管道,才能处于非阻塞模式。
Java 关闭通道
要关闭通道,请使用它的 close()方法。
与缓冲区不同,通道在关闭后不能被重用。
开放通道代表到特定 I/O 服务的特定连接,并封装了该连接的状态。
当通道关闭时,该连接将丢失,并且通道不再连接到任何东西。
在一个通道上多次调用 close()是无害的。
在关闭的通道上对 close()的后续调用不执行任何操作并立即返回。
可以想象,套接字通道可能需要大量时间才能关闭,具体取决于系统的网络实现。
一些网络协议栈可能会在输出耗尽时阻止关闭。
可以使用 isOpen()方法测试通道的打开状态。
如果返回 true,则可以使用该通道。
如果为 false,则通道已关闭,无法再使用。
尝试读取、写入或者执行任何其他需要通道处于打开状态的操作将导致 ClosedChannelException。
通道是 java.nio 除了缓冲区之外,第2个补充。
通道提供到 I/O 服务的直接连接。
通道是一种在字节缓冲区和通道另一端的实体(通常是文件或者套接字)之间有效传输数据的介质。
通常,通道与操作系统文件描述符是一对一的关系。Channel类提供了维持平台独立性所需的抽象,但仍然对现代操作系统的本机 I/O 功能进行建模。
通道是网关,通过它可以以最小的开销访问操作系统的本地 I/O 服务,缓冲区是通道用于发送和接收数据的内部端点。
Java 打开通道
正如我们已经知道的,I/O 分为两大类:
- 文件输入/输出
- 流输入/输出
因此,有两种类型的通道: file 和 socket 。
FileChannel类和 SocketChannel类用于处理这两个类。
FileChannel对象只能通过在打开的 RandomAccessFile、FileInputStream或者 FileOutputStream对象上调用 getChannel()方法来获取。
我们不能直接创建 FileChannel对象。
示例 1:如何获取 FileChannel
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel();
与FileChannel相反,套接字通道具有直接创建新套接字通道的工厂方法。
示例 2:如何创建 SocketChannel
//How to open SocketChannel
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("somehost", someport));
//How to open ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind (new InetSocketAddress (somelocalport));
//How to open DatagramChannel
DatagramChannel dc = DatagramChannel.open();
上述方法返回相应的套接字通道对象。
它们不是新通道的来源,因为 RandomAccessFile.getChannel() 是。
如果套接字已经存在,它们将返回与套接字关联的通道;他们从不创建新通道。
