Java NIO教程 Channel

Channel是一个连接到数据源的通道。程序不能直接用Channel中的数据,必须让Channel与BtyeBuffer交互数据,才能使用Buffer中的数据。

我们用FileChannel作为引子,开始逐步的了解NIO中的重要一环——Channel

FileChannel

有了前面的知识积累,我可以更快速的学习。FileChannel中常用的操作无非那么几种,打开FileChannel、用BtyeBuffer从FileChannel中读数据、用BtyeBuffer向FileChannel中写数据,下面这段代码就展示了这些

/* 
 * 1.Channel是需要关闭的,所以这里用TWR方式确保Channel正确关闭
 * 2.鼓励大家用这种方法打开通道FileChannel.open(Path path, OpenOption... options)
 */
try (FileChannel inChannel 
		= FileChannel.open(Paths.get("src/a.txt"),StandardOpenOption.READ);
	FileChannel outChannel 
		= FileChannel.open(Paths.get("src/b.txt"),StandardOpenOption.WRITE);) {
	ByteBuffer buf = ByteBuffer.allocate(48);
	/*
	 * 1.channel.write()和read()方法是需要移动position和limit指针的
	 * 		所以需要用buffer.flip()等方法,来保证读写正确
	 * 2.channel.read()方法是从通道读取到缓冲区中,读取的字节数量是n (n是buffer中当前剩余的容量),
	 * 		但是读取的数量是取决于通道的当前状态。例如:要读到文件末尾,不够buffer的容量也就是 通道剩余<=n,
	 * 		或者说ServerSocketChannel 当前只能读取准备好的,这很可能<n,所以说加循环,
	 * 		另外read的方法返回当前读取的数量,一个int 可以根据他来设定while
	 * 		如果返回-1,表示到了文件末尾
	 */
	int bytesRead = inChannel.read(buf);
	while (bytesRead != -1) {
		buf.flip();
		/*
		 *注意fileChannel.write()是在while循环中调用的。
		 *因为无法保证write()方法一次能向FileChannel写入多少字节,
		 *因此需要重复调用write()方法,直到Buffer中已经没有尚未写入通道的字节。
		 */
		while (buf.hasRemaining()) {
			outChannel.write(buf);
		}
		buf.clear();
		bytesRead = inChannel.read(buf);
	}
}

其实掌握了Buffer的知识后,学起FileChannel来挺容易的。而且再告诉你一点,就是如果只是将一个数据源通过FileChannel,转移到另一个数据源,还有一种更加简单的方法

try (FileChannel inChannel 
		= FileChannel.open(Paths.get("src/a.txt"),StandardOpenOption.READ);
	FileChannel outChannel 
		= FileChannel.open(Paths.get("src/b.txt"),StandardOpenOption.WRITE);) {
	//第二个参数表示,数据转移的起始位置,第三个参数表示转移的长度
	//channel.size()表示通道的长度
	outChannel.transferFrom(inChannel,0,inChannel.size());
	//以下方式也可
	inChannel.transferTo(0, inChannel.size(), outChannel);
}

这些以外,还有几个常用的方法,在这里要跟大家说一下
fileChannel.position() 返回FileChannel读写的当前位置
fileChannel.position(long newPosition) 设置FileChannel读写的当前位置
fileChannel.truncate(long size) 截取文件的前size个字节
fileChannel.force(boolean metaData) 将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。其中的boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。

下面理论上要介绍SocketChannel ServerSocketChannel等通道了,但是这些网络通道和fileChannel不太一样,因为fileChannel竟然是阻塞的(NIO,你在跟我开玩笑吧!),真的是阻塞的。而SocketChannel ServerSocketChannel等通道才是非阻塞的(fileChannel是充话费送的吧!),所以它们的真正能力要配合Selector才能显示出来,所以等到讲解Selector时,在一起讲。

那这样就结束了吗?当然不可能(这种看右边的滚动条就能发现的事实,就不要故弄玄虚了吧! 咦,我怎么在自己吐槽自己?)下面开始讲讲java7中引进的AsynchronousFileChannel,这回放心,它是异步的。

异步I/O (AIO)

其实利用java7之前的方式,也能做到,但是必须写大量的多线程代码,而且多路读取也十分麻烦。除非程序写的十分强大,否则,自己写的异步I/O的速度只能是 慢~~~~~~~~~

在java7的异步I/O中,主要有两种形式,将来式回调式。这是在java.util.concurrent中的并发工具,不会的话也没关系,在这里应该能大致的看懂。

将来式

这种方式是由主线程发起I/O操作并轮询等待结果。这里用了java.util.concurrent.Future接口,它的能力是不让当前线程阻塞。通过将I/O操作转移到另一线程上,并在完成时返回结果,来达到异步的目的。

try (AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(
		Paths.get("src/a.txt"), StandardOpenOption.READ);) {
	ByteBuffer buffer = ByteBuffer.allocate(1024);
	//read的第二个参数指定了channel的起始位置
	Future<Integer> result = inChannel.read(buffer, 0);
	//一直轮询I/O操作是否完成
	while (!result.isDone()) {
		// 做点别的
	}
	buffer.flip();
	while (buffer.hasRemaining()) {
		System.out.print((char) buffer.get());
	}
}

回调式

这种方式是预先制定好I/O成功或失败时的应对策略,等待I/O操作完成后就自动执行该策略。所以必须得重写两个方法completionHandler.completed()和completionHandler.failed().

try (AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(
		Paths.get("src/a.txt"), StandardOpenOption.READ);) {
	ByteBuffer buffer = ByteBuffer.allocate(1024);
	/*
	 * asynchronousFileChannel.read(ByteBuffer dst,long position,
	 *           A attachment, CompletionHandler<Integer,? super A> handler)
	 * 该函数是回调式中的核心函数 
	 * 1.首先讲最后一个参数,它的第二个泛型类型和第三个参数类型一致为A
	 * 该接口有两个待实现的方法,completed(...)和failed(...) 分别代指完成时和失败时如何操作
	 * completed(Integer result, A attachment)的第一个参数是完成了多少个字节
	 * failed(Throwable exc, A attachment)的第一个参数是引起失败的异常类型 
	 * 2.A 可以理解为在CompletionHandler的实现外部,要给实现内部什么信息
	 * 在下面的代码中,我传的A为buffer,以便实现的内部打印buffer信息,也可以传递String类型等
	 * 3.前两个参数分别为与通道交互的byteBuffer和起始位置
	 */
	inChannel.read(buffer, 0, buffer,
			new CompletionHandler<Integer, ByteBuffer>() {
				public void completed(Integer result,
						ByteBuffer attachment) {
					System.out.println(result);
					attachment.flip();
					while (attachment.hasRemaining()) {
						System.out.print((char) attachment.get());
					}
				}

				public void failed(Throwable exception,
						ByteBuffer attachment) {
					System.out.println("failed"
							+ exception.getMessage());
				}
			});
	
	// 做点别的
}

纵观这两种异步I/O实现方式,我自己总感觉,将来式总是询问数据是否到位,有股非阻塞I/O的感觉。网络异步I/O也是运用将来式和回调式完成的,和文件I/O基本一致,就不再磨叽。

但java7的I/O新内容绝不止这些,还有对网络多播的支持,还有通道组等等。想学完?路还有很远、很远呢。

讲的就是这么多,如有问题联系我

原文地址:https://www.cnblogs.com/ironPhoenix/p/4200944.html