通道 -------------------->>>>>>>>>>>>>>>>>>>>>>>>>\
“辉煌!绝对的辉煌!”
—— Wile E. Coyote (超级天才)
通道(Channel)是 java.nio 的第二个主要创新。它们既不是一个扩展也不是一项增强,而是全新、极好的 Java I/O 示例,提供与 I/O 服务的直接连接。Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。
多数情况下,通道与操作系统的文件描述符(File Descriptor )和文件句柄(File Handle )有着一对一的关系。虽然通道比文件描述符更广义,但您将经常使用到的多数通道都是连接到开放的文件描述符的。Channel类提供维持平台独立性所需的抽象过程,不过仍然会模拟现代操作系统本身的I/O 性能。
通道是一种途径,借助该途径,可以用最小的总开销来访问操作系统本身的 I/O 服务。缓冲区则是通道内部用来发送和接收数据的端点。
channel类的继承关系要比 buffer 类复杂一些。Channel类相互之间的关系更复杂,并且部分 channel类依赖于在 java.nio.channels.spi 子包中定义的类。
package java.nio.channels;
public interface Channel
{
public boolean isOpen( );
public void close( ) throws IOException;
}
与缓冲区不同,
通道 API 主要由接口指定。
不同的操作系统上通道实现(Channel Implementation)会有根本性的差异,所以通道 API 仅仅描述了可以做什么。因此很自然地,
通道实现经常使用操作系统的本地代码。通道接口允许您以一种受控且可移植的方式来访问底层的 I/O服务。
从Channel接口引申出的其他接口都是面向字节的子接口,包括 Writable ByteChannel 和 ReadableByteChannel 。这也正好支持了我们之前所学的: 通道只能在字节缓冲区上操作。层次结构表明其他数据类型的通道也可以从 Channel接口引申而来。这是一种很好的类设计,不过非字节实现是不可能的,因为 操作系统都是以字节的形式实现底层 I/O 接口的。
类层次结构中有两个类位于一个不同的包:java.nio.channels.spi 。这两个类是 AbstractInterruptibleChannel 和 AbstractSelectableChannel ,它们分别为可中断的(interruptible )和可选择的(selectable )的通道实现提供所需的常用方法。尽管描述通道行为的接口都是在 java.nio.channels包中定义的,不过具体的通道实现却都是从 java.nio.channels.spi 中的类引申来的。
作为通道的一个使用者,您可以放心地忽视 SPI 包中包含的中间类。这种有点费解的继承层次只会让那些使用新通道的用户感兴趣。SPI 包允许新通道实现以一种受控且模块化的方式被植入到Java 虚拟机上。这意味着可以使用专为某种操作系统、文件系统或应用程序而优化的通道来使性能最大化。
打开通道 :通道是访问 I/O 服务的导管。I/O 可以分为广义的两大类别:File I/O和Stream I/O。那么相应地有两种类型的通道也就不足为怪了,它们是文件(file)通道和套接字(socket )通道。您会发现有一个 FileChannel 类和三个 socket 通道类:SocketChannel 、ServerSocketChannel 和 DatagramChannel。
通道可以以多种方式创建。Socket 通道有可以直接创建新 socket 通道的工厂方法。但是一个FileChannel 对象却只能通过在一个打开的 RandomAccessFile,FileInputStream 或 FileOutputStream对象上调用 getChannel( )方法来获取。您不能直接创建一个 FileChannel 对象。
SocketChannel sc = SocketChannel.open();
sc.connect (new InetSocketAddress ("somehost", someport));
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind (new InetSocketAddress (somelocalport));
DatagramChannel dc = DatagramChannel.open();
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel();
public final FileChannel getChannel()
-
Returns the unique
FileChannel
object associated with this file.The
position
of the returned channel will always be equal to this object's file-pointer offset as returned by the
getFilePointer
method. Changing this object's file-pointer offset, whether explicitly or by reading or writing bytes, will change the position of the channel, and vice versa. Changing the file's length via this object will change the length seen via the file channel, and vice versa.
使用通道 :
java.nio.channels
Interface ByteChannel
-
All Superinterfaces:
- Channel, Closeable, ReadableByteChannel, WritableByteChannel
-
All Known Implementing Classes:
- DatagramChannel, FileChannel, SocketChannel
public interface ByteChannel extends ReadableByteChannel, WritableByteChannel
A channel that can read and write bytes. This interface simply unifies ReadableByteChannel
and WritableByteChannel
; it does not specify any new operations.
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
{
}
通道可以是单向(unidirecti onal )或者双向的(bidirectional)。一个 channel类可能实现定义read( )方法的 ReadableByteChannel 接口,而另一个 channel类也许实现 WritableByteChannel 接口以提供write( )方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。
所以ByteChannel接口是双向的!!!
您将发现每一个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);
java.nio.channels
Class Channels
java.lang.Object java.nio.channels.Channels
public final class Channels extends Object
Utility methods for channels and streams.
This class defines static methods that support the interoperation of the stream classes of thejava.io
package with the channel classes of this package.
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 o f 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才能使用非阻塞模式。
socket 通道类从 SelectableChannel 引申而来。从 SelectableChannel 引申而来的类可以和支持有条件的选择(readiness selection )的选择器(Selectors )一起使用。将非阻塞I/O 和选择器组合起来可以使您的程序利用多路复用 I/O (multiplexed I/O)。
关闭通道:与缓冲区不同,通道不能被重复使用。一个打开的通道即代表与一个特定 I/O 服务的特定连接并封装该连接的状态。当通道关闭时,那个连接会丢失,然后通道将不再连接任何东西。
调用通道的close( ) 方法时,可能会导致在通道关闭底层I/O服务的过程中线程暂时阻塞,哪怕该通道处于非阻塞模式。通道关闭时的阻塞行为(如果有的话)是高度取决于操作系统或者文件系统的。在一个通道上多次调用close( ) 方法是没有坏处的,但是如果第一个线程在close( ) 方法中阻塞,那么在它完成关闭通道之前,任何其他调用close( ) 方法都会阻塞。后续在该已关闭的通道上调用close( ) 不会产生任何操作,只会立即返回。
void close() throws IOException
-
Closes this channel.
After a channel is closed, any further attempt to invoke I/O operations upon it will cause a
ClosedChannelException
to be thrown.If this channel is already closed then invoking this method has no effect.
This method may be invoked at any time. If some other thread has already invoked it, however, then another invocation will block until the first invocation is complete, after which it will return without effect.
此外,假如一个线程的 interrupt statu s 被设置并且该线程试图访问一个通道,那么这个通道将立即被关闭,同时将抛出相同的 ClosedByInterruptException异常。线程的 interrupt status 在线程的interrupt( ) 方法被调用时会被设置。我们可以使用 isInterrupted( )来测试某个线程当前的 interrupt status。当前线程的 interrupt status 可以通过调用静态的 Thread.interrupted( )方法清除。
public static boolean interrupted()
-
Tests whether the current thread has been interrupted. The
interrupted status of the thread is cleared by this method. In other words, if this method were to be called twice in succession, the second call would return false (unless the current thread were interrupted again, after the first call had cleared its interrupted status and before the second call had examined it).
A thread interruption ignored because a thread was not alive at the time of the interrupt will be reflected by this method returning false.
可中断的通道也是可以异步关闭的。实现 InterruptibleChannel 接口的通道可以在任何时候被关闭,即使有另一个被阻塞的线程在等待该通道上的一个 I/O 操作完成。当一个通道被关闭时,休眠在该通道上的所有线程都将被唤醒并接收到一个 AsynchronousCloseException 异常。接着通道就被关闭并将不再可用。
java.nio.channels
Interface InterruptibleChannel
-
All Known Implementing Classes:
- AbstractInterruptibleChannel, AbstractSelectableChannel, DatagramChannel, FileChannel, Pipe.SinkChannel, Pipe.SourceChannel, SelectableChannel, ServerSocketChannel, SocketChannel
Scatter/Gather :通道提供了一种被称为 Scatter/Gather 的重要新功能(有时也被称为矢量 I/O)。Scatter/Gather是一个简单却强大的概念,它是指在多个缓冲区上实现一个简单的 I/O 操作。对于一个 write操作而言,数据是从几个缓冲区按顺序抽取(称为 gather)并沿着通道发送的。缓冲区本身并不需要具备这种 gather 的能力(通常它们也没有此能力)。该 gather 过程的效果就好比全部缓冲区的内容被连结起来,并在发送数据前存放到一个大的缓冲区中。对于 read 操作而言,从通道读取的数据会按顺序被散布(称为scatter )到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。
大多数现代操作系统都支持本地矢量 I/O(native vectored I/O )。当您在一个通道上请求一个Scatter/Gather 操作时,该请求会被翻译为适当的本地调用来直接填充或抽取缓冲区。这是一个很大的进步,因为减少或避免了缓冲区拷贝和系统调用。Scatter/Gather 应该使用直接的 ByteBuffers 以从本地I/O 获取最大性能优势。
java.nio.channels
Interface ScatteringByteChannel
-
All Superinterfaces:
- Channel, Closeable, ReadableByteChannel
-
All Known Implementing Classes:
- DatagramChannel, FileChannel, Pipe.SourceChannel, SocketChannel
public interface ScatteringByteChannel extends ReadableByteChannel
A channel that can read bytes into a sequence of buffers.
A scattering read operation reads, in a single invocation, a sequence of bytes into one or more of a given sequence of buffers. Scattering reads are often useful when implementing network protocols or file formats that, for example, group data into segments consisting of one or more fixed-length headers followed by a variable-length body. Similargathering write operations are defined in the GatheringByteChannel
interface.
java.nio.channels Interface GatheringByteChannel
-
All Superinterfaces:
- Channel, Closeable, WritableByteChannel
-
All Known Implementing Classes:
- DatagramChannel, FileChannel, Pipe.SinkChannel, SocketChannel
public interface GatheringByteChannel extends WritableByteChannel
A channel that can write bytes from a sequence of buffers.
A gathering write operation writes, in a single invocation, a sequence of bytes from one or more of a given sequence of buffers. Gathering writes are often useful when implementing network protocols or file formats that, for example, group data into segments consisting of one or more fixed-length headers followed by a variable-length body. Similarscattering read operations are defined in the ScatteringByteChannel
interface.
public interface ScatteringByteChannel
extends ReadableByteChannel
{
public long read (ByteBuffer [] dsts)
throws IOException;
public long read (ByteBuffer [] dsts, int offset, int length)
throws IOException;
}
public interface GatheringByteChannel
extends WritableByteChannel
{
public long write(ByteBuffer[] srcs)
throws IOException;
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException;
}
我们假定 channel连接到一个有 48 字节数据等待读取的 socket 上:
ByteBuffer header = ByteBuffer.allocateDirect (10);
ByteBuffer body = ByteBuffer.allocateDirect (80);
ByteBuffer [] buffers = { header, body };
int bytesRead = channel.read (buffers);
一旦read( ) 方法返回,bytesRead 就被赋予值 48 ,header 缓冲区将包含前 10个从通道读取的字节而 body 缓冲区则包含接下来的 38个字节。通道会自动地将数据 scatter 到这两个缓冲区中。缓冲区已经被填充了(尽管此例中 body 缓冲区还有空间填充更多数据),那么将需要被 flip以便其中数据可以被抽取。在类似这样的例子中,我们可能并不会费劲去 flip 这个header 缓冲区而是以绝对 get的方式随机访问它以检查各种 header 字段;不过 body缓冲区会被 flip 并传递到另一个通道的 write( ) 方法上,然后在通道上发送出去。例如:
switch (header.getShort(0)) {
case TYPE_PING:
break;
case TYPE_FILE:
body.flip();
fileChannel.write (body);
break;
default:
logUnknownPacket (header.getShort(0), header.getLong(2), body);
break;
}
同样,很简单地,我们可以用一个 gather 操作将多个缓冲区的数据组合并发送出去。使用相同的缓冲区,我们可以像下面这样汇总数据并在一个 socket 通道上发送包:
body.clear();
body.put("FOO".getBytes()).flip(); // "FOO" as bytes
header.clear();
header.putShort (TYPE_FILE).putLong (body.limit()).flip();
long bytesWritten = channel.write(buffers);
下图描述了一个 scatter 读操作。从通道传输来的数据被 scatter 到所列缓冲区,依次填充每个缓冲区(从缓冲区的 position处开始到 limit 处结束)。这里显示的 position和limit 值是读操作开始之前的。
举个例子,假设我们有一个五元素的 fiveBuffers 阵列,它已经被初始化并引用了五个缓冲区,下面的代码将会写第二个、第三个和第四个缓冲区的内容:
int bytesRead = channel.write (fiveBuffers, 1, 3);
使用得当的话,Scatter/Gather 会是一个极其强大的工具。
它允许您委托操作系统来完成辛苦活:将读取到的数据分开存放到多个存储桶(bucket )或者将不同的数据区块合并成一个整体。这是一个巨大的成就,因为操作系统已经被高度优化来完成此类工作了。它节省了您来回移动数据的工作,也就避免了缓冲区拷贝和减少了您需要编写、调试的代码数量。既然您基本上通过提供
数据容器引用来组合数据,那么
按照不同的组合构建多个缓冲区阵列引用,各种数据区块就可以以不同的方式来组合了。下例很好地诠释了这一点:
package com.ronsoft.books.nio.channels;
import java.nio.ByteBuffer;
import java.nio.channels.GatheringByteChannel;
import java.io.FileOutputStream;
import java.util.Random;
import java.util.List;
import java.util.LinkedList;
/**
* Demonstrate gathering write using many buffers.
*
* @author Ron Hitchens (ron@ronsoft.com)
*/
public class Marketing {
private static final String DEMOGRAPHIC = "blahblah.txt";
// "Leverage frictionless methodologies"
public static void main(String[] argv) throws Exception {
int reps = 10;
if (argv.length > 0) {
reps = Integer.parseInt(argv[0]);
}
FileOutputStream fos = new FileOutputStream(DEMOGRAPHIC);
GatheringByteChannel gatherChannel = fos.getChannel();
// Generate some brilliant marcom, er, repurposed content
ByteBuffer[] bs = utterBS(reps);
// Deliver the message to the waiting market
while (gatherChannel.write(bs) > 0) {
// Empty body
// Loop until write( ) returns zero
}
System.out.println("Mindshare paradigms synergized to " + DEMOGRAPHIC);
fos.close();
}
// ------------------------------------------------
// These are just representative; add your own
private static String[] col1 = { "Aggregate", "Enable", "Leverage",
"Facilitate", "Synergize", "Repurpose", "Strategize", "Reinvent",
"Harness" };
private static String[] col2 = { "cross-platform", "best-of-breed",
"frictionless", "ubiquitous", "extensible", "compelling",
"mission -critical", "collaborative", "integrated" };
private static String[] col3 = { "methodologies", "infomediaries",
"platforms", "schemas", "mindshare", "paradigms",
"functionalities", "web services", "infrastructures" };
private static String newline = System.getProperty("line.separator");
// The Marcom -atic 9000
private static ByteBuffer[] utterBS(int howMany) throws Exception {
List<ByteBuffer> list = new LinkedList<ByteBuffer>();
for (int i = 0; i < howMany; i++) {
list.add(pickRandom(col1, " "));
list.add(pickRandom(col2, " "));
list.add(pickRandom(col3, newline));
}
ByteBuffer[] bufs = new ByteBuffer[list.size()];
list.toArray(bufs);
return (bufs);
}
// The communications director
private static Random rand = new Random();
// Pick one, make a buffer to hold it and the suffix, load it with
// the byte equivalent of the strings (will not work properly for
// non-Latin characters), then flip the loaded buffer so it's ready
// to be drained
private static ByteBuffer pickRandom(String[] strings, String suffix)
throws Exception {
String string = strings[rand.nextInt(strings.length)];
int total = string.length() + suffix.length();
ByteBuffer buf = ByteBuffer.allocate(total);
buf.put(string.getBytes("US-ASCII"));
buf.put(suffix.getBytes("US-ASCII"));
buf.flip();
return (buf);
}
}
下面是实现 Marketing 类的输出。虽然这种输出没什么意义,但是 gather 写操作却能让我们非常高效地把它生成出来。
Aggregate compelling methodologies
Harness collaborative platforms
Aggregate integrated schemas
Aggregate frictionless platforms
Enable integrated platforms
Leverage cross-platform functionalities
Harness extensible paradigms
Synergize compelling infomediaries
Repurpose cross-platform mindshare
Facilitate cross-platform infomediaries