通道是继 buffers 之后 java.nio
的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。
通道是一种在字节缓冲区和通道另一端的实体(通常是文件或套接字)之间高效传输数据的媒介。
通常,通道与操作系统文件描述符具有一对一的关系。 Channel
类提供了保持平台独立性所需的抽象,但仍然模拟现代操作系统的本机 I/O 功能。
通道是网关,通过它可以以最小的开销访问操作系统的本机 I/O 服务,缓冲区是通道用来发送和接收数据的内部端点。
在层次结构的顶部,有频道 界面如下所示:
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 分为两大类:
因此,有两种类型的通道也就不足为奇了:文件 和套接字。 FileChannel
类和SocketChannel
类就是用来处理这两个类的。
FileChannel
对象只能通过在打开的getChannel()
上调用RandomAccessFile
方法获得,FileInputStream
或 FileOutputStream
对象。您不能直接创建 FileChannel
对象。
RandomAccessFile raf = new RandomAccessFile ("somefile", "r"); FileChannel fc = raf.getChannel();
与FileChannel
相反,套接字通道有工厂方法来直接创建新的套接字通道。
//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()
那样是新渠道的来源。如果一个套接字已经存在,它们返回与套接字关联的通道;他们从不创建新渠道。
正如我们已经在缓冲区教程中了解到的那样,通道将数据传入和传出 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
类,您会发现每个文件和套接字通道都实现了所有这三个接口。就类定义而言,这意味着所有文件和套接字通道对象都是双向的。
这对套接字不是问题,因为它们总是双向的,但对文件来说是个问题。从 FileChannel
对象的 getChannel()
方法获得的 FileInputStream
对象是只读的,但在接口方面是双向的声明,因为 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-only channel.write (buffer);
read()
的 write()
和 ByteChannel
方法将 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 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(); } } }
通道可以在阻塞或非阻塞模式下运行。非阻塞模式下的通道永远不会让调用线程进入睡眠状态。请求的操作要么立即完成,要么返回一个结果,表明什么都没做。只有面向流的通道,如套接字和管道,才能置于非阻塞模式。
要关闭频道,请使用它的 close()
方法。与缓冲区不同,通道在关闭后无法重复使用。开放通道表示与特定 I/O 服务的特定连接并封装该连接的状态。当一个通道关闭时,那个连接就丢失了,通道不再连接到任何东西。
在频道上多次调用
close()
是无害的。在关闭的通道上对close()
的后续调用不执行任何操作并立即返回。
可以使用 isOpen()
方法测试通道的打开状态。如果它返回 true,则可以使用该通道。如果为 false,则通道已关闭,无法再使用。
尝试读取、写入或执行任何其他需要通道处于打开状态的操作将导致 ClosedChannelException
。
快乐学习!!
地址:https://www.cundage.com/article/java-nio-2-0-channels.html