##0.概述
通道(channel)是java nio 的第二个主要创新。它们既不是一个扩展也不是一项增强,而是全新、极好的java I/O 示例,提供与I/O服务直接连接,Channel类提供了维持平台独立性所需要的抽象过程。多数情况下,通道与操作系统的文件描述(fd)或者文件句柄有着一对一的关系。
##1.通道的基础
###1.1Channel 接口
public interface Channel extends Closeable {
/**
* Tells whether or not this channel is open.
*
* @return <tt>true</tt> if, and only if, this channel is open
*/
public boolean isOpen();
/**
* Closes this channel.
*
* <p> After a channel is closed, any further attempt to invoke I/O
* operations upon it will cause a {@link ClosedChannelException} to be
* thrown.
*
* <p> If this channel is already closed then invoking this method has no
* effect.
*
* <p> 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. </p>
*
* @throws IOException If an I/O error occurs
*/
public void close() throws IOException;
}
与缓冲区不同,通道的API主要由接口指定不同的操作系统的实现会有着根本的差异
,所以通道API仅仅描述了可以做什么。下面来看下FileChannel类 为例继承和实现哪些接口 ,FileChannel具体有哪些接口可以查看JDK文档或者FileChannel实现。
1.可以从顶层的Channel接口看到,对所有的通道来说是只有两种共同操作:检查一个通道是否打开(IsOpen())和关闭一个打开的通道(close())。
2.InterruptibleChannel是一个标记接口,当被通道使用时可以标示该通道是可以中断的。
3.从Channel引申出的其它接口都是面向字节的子接口,包括WritableByteChannel和ReadableByteChannel
4.看了JDK源码不难发现出现子接口覆盖父类的接口情况,子接口可以覆盖父接口中的方法的意义
###1.2打开通道
通道主要有两种类型:文件通道和套接字通道。FileChannel对象只能通过一个打开的RandomAccessFile、FileInputStream、FileOutPutStream 对象上调用Channel方法来获取。Socket通道对象可以直接由Socket工厂方法进行创建。
下面给出FileChannel通道的使用Demo
public class FileNio {
public static void main(String[] args) throws Exception {
FileInputStream inputStream = new FileInputStream("test.txt");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
FileChannel fileChannel = inputStream.getChannel();
while (fileChannel.read(byteBuffer) != -1) {
byteBuffer.flip();
byte[] b = byteBuffer.array();
System.out.println(new String(b, 0, byteBuffer.remaining()));
byteBuffer.clear();
}
inputStream.close();
fileChannel.close();
}
}
###1.3使用通道
1.通道可以是单向(unidirectional)或者双向的(bidirectional)。所谓单向的只可以读或者只可以写,双向的既可以读也可以写。
2.ByteChannel继承了ReadableByteChannel, WritableByteChannel,所有其实双向的。由于接口本身没有定义新的API方法,它是一种用来聚集它自己以一个新名称继承的多个接口的便捷接口。
3.值得说明的是一个文件可以在不同的权限打开,从FileInputStream对象的getChannel( )方法获取的FileChannel对象是只读的,不过从接口声明的角度来看却是双向的,因为FileChannel实现ByteChannel接口。在这样一个通道上调用write( )方法将抛出未经检查的NonWritableChannelException异常,因为FileInputStream对象总是以read-only的权限打开文件。
package java.nio.channels;
import java.io.IOException;
/**
* A channel that can read and write bytes. This interface simply unifies
* {@link ReadableByteChannel} and {@link WritableByteChannel}; it does not
* specify any new operations.
*
* @author Mark Reinhold
* @author JSR-51 Expert Group
* @since 1.4
*/
public interface ByteChannel
extends ReadableByteChannel, WritableByteChannel
{
}
文件copyDemo
public class FileCopyDemo {
public static void main(String[] args) throws IOException {
String srcFile = "/Users/hsc/src.mp4";
String destFile = "/Users/hsc/dest.mp4";
FileChannel srcChannel = null;
FileChannel destChannel = null;
FileInputStream input = null;
FileOutputStream output = null;
try {
input = new FileInputStream(srcFile);
output = new FileOutputStream(destFile);
srcChannel = input.getChannel();
destChannel = output.getChannel();
copy(srcChannel, destChannel);
} catch (Exception ex) {
System.out.println(ex);
} finally {
if (srcChannel != null) {
srcChannel.close();
}
if (destChannel != null) {
destChannel.close();
}
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
}
static void copy(FileChannel src, FileChannel dest) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (src.read(byteBuffer) != -1) {
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
dest.write(byteBuffer);
}
byteBuffer.clear();
}
}
}
###1.4关闭通道
与缓冲区不同,通道不能被重复使用。一个打开的通道即代表一个特定I/O服务的特定连接并封装该连接的状态。当该通道关闭时候,关联的文件描述符或者句柄也会被关闭以及相应的流也会被关闭(如FileInputStream)。可以通过isOpen( )方法来测试通道的开放状态。如果返回true值,那么该通道可以使用。如果返回false值,那么该通道已关闭,不能再被使用。尝试进行任何需要通道处于开放状态作为前提的操作,如读、写等都会导致ClosedChannelException异常。
调用通道的close( )方法时,可能会导致在通道关闭底层I/O服务的过程中线程暂时阻塞,哪怕该通道处于非阻塞模式。通道关闭时的阻塞行为(如果有的话)是高度取决于操作系统或者文件系统的。在一个通道上多次调用close( )方法是没有坏处的,但是如果第一个线程在close( )方法中阻塞,那么在它完成关闭通道之前,任何其他调用close( )方法都会阻塞。后续在该已关闭的通道上调用close( )不会产生任何操作,只会立即返回。
##2.Scatter/Gather
通道提供了一种被称为Scatter/Gather的重要功能(有时候也被称为矢量IO),它是指在多个缓冲区上实现一个简单的I/O操作。对于一个write操作而言,数据是从几个缓冲区按顺序抽取(称为gather)并沿着通道发送的。该gather过程的效果就好比全部缓冲区的内容被连结起来,并在发送数据前存放到一个大的缓冲区中(数组中)。对于read操作而言,从通道读取的数据会按顺序被散布(称为scatter)到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。
public class ScatterDemo
{
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("test.txt");
ByteBuffer header = ByteBuffer.allocate (10);
ByteBuffer body = ByteBuffer.allocate (50);
ByteBuffer []byteBuffers={header,body};
FileChannel fileChannel=inputStream.getChannel();
while (fileChannel.read(byteBuffers)!=-1)
{
printBuffer(header);
printBuffer(body);
}
fileChannel.close();
}
private static void printBuffer(ByteBuffer byteBuffer)
{
byteBuffer.flip();
while (byteBuffer.hasRemaining())
{
byte[] b = byteBuffer.array();
System.out.println(new String(b, 0, byteBuffer.remaining()));
byteBuffer.position(byteBuffer.remaining());
}
byteBuffer.clear();
}
}
public class GatherDemo {
public static void main(String[] args) throws IOException {
FileOutputStream outputStream=new FileOutputStream("test.txt");
FileChannel fileChannel=outputStream.getChannel();
ByteBuffer header=ByteBuffer.allocate(10);
ByteBuffer body=ByteBuffer.allocate(20);
header.put("header".getBytes());
body.put("body".getBytes());
//要写数据放到一个字节缓冲区数组中
ByteBuffer [] buffers={header,body};
header.flip();
body.flip();
fileChannel.write(buffers);
fileChannel.close();
}
}
参考文献[1]https://book.douban.com/subject/1433583/