Java NIO小结

Java NIO是什么

J2SE 1.4引进的Java IO新特性,实现JSR 51,是Java原IO系统的增强和补充,故取New IO之名。相关的类都放在java.nio包下。

为什么需要Java nio

两个字,效率,NIO能够处理的所有场景,原IO基本都能做到,NIO因效率而生,效率包括处理速度和吞吐量(Througthout, Scalability)。Java原IO都是流式的(Stream Oriented),一个Byte一个Byte的读取,且需要在JVM(用户空间)和操作系统内核空间之间复制数据(Bytes),速度较慢。IO主要分两块,文件系统IO和网络IO。文件系统方面,通过批量处理(Buffer),操作直接委托给操作系统(Direct),充分的利用操作系统的IO能力,提高访问性能,不过NIO还不支持文件系统的异步调用。网络IO方面,提供非阻塞操作,减少处理网络IO的线程数,增强可伸缩性(Scalability)。额外提一下,线程切换(Context Swith)是繁重的,多了会严重影响性能(可伸缩性, Scalability),线程越多越糟糕,n核的机器参考的线程数是n或n+1。

Java NIO的三个重要抽象

Java NIO引入的最重要三个抽象是Buffer,Channel,Selector,因为Java NIO中文件系统部分是阻塞的的,故不会用到Selector。下面分别来介绍。

Buffer

读或写数据的暂存容器,与Channel匹配使用。提供批量的向Channel写入或者读取数据功能。有四个int属性需要理解:

  • mark
    标记的位置,default -1。
  • position
    游标当前位置
  • limit
    存活(可用)Byte最后一个的位置
  • capacity
    Buffer的最大容量

很多Buffer的操作都与上面四个属性有关,比如flip()rewind(), clear()等。最重要的一个Buffer实现是ByteBuffer,主要实现对Byte的存取操作,常用的还有CharBuffer。

Channel

Client与IO设备之间读写的交互通道,数据先放到Buffer里面,再经Channel写入设备,或者从Channel里面读取数据。常用的有FileChannel,ServerSocketChannel和SocketChannel。

Selector

针对非阻塞(No-Blocking)的Channel,提供事件通知的机制。很多教程强调Selector提供了多路复用(单个线程或少数线程负责多个Channel的事件通知)的机制,作为Node.js的粉丝,我觉得Selector太复杂了,解释也很难懂。依我的理解,非阻塞式编程是基于事件的,Selector就是事件的通讯机制。非阻塞式才是关键,Event Bus构建非阻塞式编程的环境。

Java NIO不是终点

在一个理想的n核环境里,有n或者n+1个线程在拼命的工作(非阻塞式的在n个核上执行CPU指令),很少切换线程,充分利用CPU资源。但是现在JDBC依然是阻塞,虽然网络的交互非阻塞了,收到Message之后通常需要操作数据库,操作数据库的过程是阻塞的,需要大量的线程来等待。根据木桶原理,依然需要大量的线程,依然没能拥有完美的伸缩性(scalability)。比原来好多了,但还不是终点。有人争辩说,我用多线程服务器工作得很好,有多少多少的吞吐量云云,同学,可以更好的,不是吗?

文件操作的一个小例子

本来文件操作是非常简单的,无需多言,但ByteBuffer与字符(Char)之间还有一个编码解码的过程,要是ByteBuffer里面的编码结束之后还剩Byte怎么办?要留到下一次read一起编码。例子参考了Java官网的Example。注意rewind(),flip()

public void readFileNio(String filePath) throws Exception {
		File file = new File(filePath);
		if (!(file.exists() && file.isFile())) {
			throw new Exception("file not exist");
		}

		String encoding = System.getProperty("file.encoding");
		FileInputStream fileInputStream = new FileInputStream(file);
		FileChannel channel = fileInputStream.getChannel();
		System.out.println(channel.size());

		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024);

		while (channel.read(byteBuffer) > 0) {
			byteBuffer.rewind();
			CharBuffer charBuffer = Charset.forName(encoding)
					.decode(byteBuffer);
			System.out.print(charBuffer.toString());
			byteBuffer.flip();
		}

		fileInputStream.close();
	}

你可能感兴趣的:(Core,Java)