zoukankan      html  css  js  c++  java
  • Java NIO教程 前言

    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新内容绝不止这些,还有对网络多播的支持,还有通道组等等。想学完?路还有很远、很远呢。

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

  • 相关阅读:
    使用工具创建Ribbon的按钮
    【20160924】GOCVHelper 图像增强部分(1)
    Xamarin Essentials教程语音播报TextToSpeech
    Xamarin Essentials教程发送邮件Email
    Xamarin SQLite教程数据库访问与生成
    XamarinEssentials教程应用程序信息AppInfo
    XamarinAndroid组件教程RecylerView自定义适配器动画
    Xamarin Essentials教程安全存储SecureStorage
    Xamarin Essentials应用教程文件系统FileSystem
    XamarinAndroid组件教程RecylerView适配器设置动画示例
  • 原文地址:https://www.cnblogs.com/mengbin0546/p/12599818.html
Copyright © 2011-2022 走看看