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())。

on  It Road.com

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);

ByteChannelread()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 中的通道

通道是 java.nio 除了缓冲区之外,第2个补充。

通道提供到 I/O 服务的直接连接。

通道是一种在字节缓冲区和通道另一端的实体(通常是文件或者套接字)之间有效传输数据的介质。

通常,通道与操作系统文件描述符是一对一的关系。
Channel类提供了维持平台独立性所需的抽象,但仍然对现代操作系统的本机 I/O 功能进行建模。

通道是网关,通过它可以以最小的开销访问操作系统的本地 I/O 服务,缓冲区是通道用于发送和接收数据的内部端点。

Java 打开通道

正如我们已经知道的,I/O 分为两大类:

  • 文件输入/输出
  • 流输入/输出

因此,有两种类型的通道: file 和 socket 。

FileChannel类和 SocketChannel类用于处理这两个类。

FileChannel对象只能通过在打开的 RandomAccessFileFileInputStream或者 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() 是。
如果套接字已经存在,它们将返回与套接字关联的通道;他们从不创建新通道。

日期:2020-09-17 00:09:37 来源:oir作者:oir