传统的输入输出流都是阻塞的输入输出。举个列子:当用传统的流进行数据输入时,如果流中没有数据,它会阻塞当前线程往下执行,等到从流中读到数据为止。另外传统的输入输出流每次处理的是一个字节或一个字符,通常效率不是很高。从JDK 1.4开始 Java提供了NIO功能,可以代替传统的输入输出功能,在效率上也有很大提升。
标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中(双向操作)。NIO可以使用非阻塞模式。
NIO概述
NIO在处理文件时会将文件的一段区域直接映射到内存中,这样访问文件时就可以像访问内存一样,比传统的输入输出要快很多。主要的实现类都在java.nio下面。
Channe
l和Buffer
是NIO中两个核心的概念。Channel的概念和传统的InputStram和OutputStream对标,最大的区别是Channel提供了一个map()方法将文件的块数据映射到内存中。可以面向一大块数据进行处理。Buffer可以理解成缓冲,其本质是一个数组。从Channel中读出来的数据要先存在Buffer中,要写到Channel中的数据也要先放到Buffer中。
另外,NIO还提供了将Unicode字符串映射成字节序列的Charset类,以及支持非阻塞输入输出的Selector类。
Channels and Buffers
标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Channel、Buffer和Selectors是NIO的核心组件。
Channel常用的实现类:
- FileChannel:文件
- DatagramChannel:UDP数据报
- SocketChannel:TCP客户端
- ServerSocketChannel:TCP服务端
Buffer常见实现类:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
Buffer的使用
Buffer的本质就是一个缓冲区,但是Buffer提供了丰富的API来让我们操作这块数据区。
System.out.println("capacity:"+buffer.capacity());
System.out.println("limit:"+buffer.limit());
System.out.println("length:"+buffer.length());
System.out.println("position:"+buffer.position());
buffer.append("a");
buffer.append('b');
buffer.put('c');
System.out.println("---------------------------");
System.out.println("capacity:"+buffer.capacity());
System.out.println("limit:" + buffer.limit());
System.out.println("length:" + buffer.length());
System.out.println("position:" + buffer.position());
//flip方法会将limit的位置移动到当前posiion位置,这样Buffer中没
//赋值的空间将都不能被访问。通常flip方法是为读取数据做准备的,可以
//防止读到null数据,读取完毕之后调用clear方法
buffer.flip();
System.out.println("---------------------------");
System.out.println("capacity:"+buffer.capacity());
System.out.println("limit:" + buffer.limit());
System.out.println("length:" + buffer.length());
System.out.println("position:" + buffer.position());
Channel的使用
FileInputStream fis = new FileInputStream("file.txt");
FileChannel channel = fis.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int hasRead = 0;
while ((hasRead=channel.read(buffer))>0){
byte[] buff = new byte[1024];
buffer.flip();
buffer.get(buff, 0, hasRead);
System.out.println(new String(buff,0,hasRead));
buffer.clear();
}
Selector
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
Selector selector = Selector.open();
channel.configureBlocking(false);
//注册到Selector上
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0)
continue;
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}
CharSet
用于对字符串编解码
JDK7的NIO2
JDK 1.7版本对NIO进行优化改进。Path、Paths和Files这些类、Filevisiter、watchService AsynchronousFileChannel
这些类进行文件内容的异步读写。AsynchronousSocketChannel
这些类进行服务器IO异步读写。
BIO、NIO和AIO的区别
-
BIO
(Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO
的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。(特点就是线程必须等待数据读取或者写入完成才能继续干其他事情。) -
NIO
(New I/O):同时支持阻塞与非阻塞模式(文件channel只支持阻塞模式,socket的channel支持阻塞和非阻塞模式),但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO
的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。(特点就是线程不必等待IO读写完成,在IO进行过程中线程可以不停地轮询IO的状态,一旦发现IO状态变化,就可以做出相应处理) -
AIO
( Asynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。AIO
中虽然不需要线程来轮询,但是需要线程来等待通知。另外,
AIO
的异步特性并不是Java实现的,而是使用了系统底层API的支持,在Unix系统下,采用了epoll IO模型,而windows便是使用了IOCP模型。
参考
公众号推荐
欢迎大家关注我的微信公众号「程序员自由之路」