java NIO详解

作者: rockjh | 来源:发表于2017-12-27 18:28 被阅读0次

概述

BIO是面向字节流和字符流的,数据从流中顺序获取

NIO是面向通道和缓冲区的,数据总是从通道中读到buffer缓冲区内,或者从buffer缓冲区内写入通道

Channel通道和Buffer缓冲区是NIO的核心,几乎在每一个IO操作中使用它们,Selector选择器则允许单个线程操作多个通道,对于高并发多连接很有帮助。

操作系统的IO一般分为两个阶段,等待和就绪操作,比如读可以分为等待系统可读和真正的读,写可以分为等待系统可写和真正的写,在传统的BIO中是这两个阶段都会阻塞,在NIO中第一个阶段不是阻塞的,第二个阶段是阻塞的,如下图,BIO是阻塞IO,NIO是非阻塞IO

nio.jpg

Buffer(缓冲区)

常用Buffer类型

ByteBuffer,MappedByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer。对应了几大基本数据类型

ByteBuffer可以选择实例化为DirectByteBuffer和HeapByteBuffer,如果数据量比较小的中小应用情况下,可以考虑使用heapBuffer;反之可以用directBuffer。一般来说DirectByteBuffer可以减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,更不宜维护,通常会用内存池来提高性能。其他buffer类似

利用Buffer读写数据步骤

  • 将数据写入buffer

  • 调用flip将写模式改为读模式

  • 从buffer中读取数据,进行操作

  • 调用clear()清除整个buffer数据或者调用compact()清空已读数据

buffer的属性

  • capacity容量,该buffer最多存储的字节数

  • position位置,写模式下,position从0到capacity-1,变更为读模式后,position归零,边读边移动

  • limit限制,写模式下,代表我们能写的最大量为capacity,读模式下,变更为原position位置,即有数据的位置

buffer_modes.png

buffer api

  • 通过allocate()方法为buffer分配内存大小,如开辟一个48字节的ByteBuffer buffer:ByteBuffer.allocate(48)

  • 写数据可以通过通道写,如:FileChannel.read(buffer);也可以通过put方法来写数据,如:buffer.put(127)

  • flip()翻转方法,将写模式切换到读模式,position归零,设置limit为之前的position位置

  • 读数据可以读到通道,如:FileChannel.write(buffer);也可以调用get()方法读取,byte aByte=buffer.get()

  • buffer.rewind()将position置0,limit不变,这样我们就可以重复读取数据啦

  • buffer.clear()将position置为0,limit设置为capacity,这里并没有删除buffer里面的数据,只是把标记位置改了;

  • buffer.compact()清除已读数据,这里也没有删除数据,将position设置为未读的个数,将后面几个未读的字节顺序的复制到前面的几个字节,limit设置为capacity,比如buffer容量3个字节,读取hello,在读取了2个字节后我就调用了compact()方法,那么此时position为1,limit为3,buffer内部存储的数据buff[0]='l',buff[1]='e',buff[2]='l',因为有一个'l'没有读完,将'l'提取到最前面供下次读取

  • mark()可以标记当前的position位置,通过reset来恢复mark位置,可以用来实现重复读取满足条件的数据块

  • equals()两个buffer相等需满足,类型相同,buffer剩余(未读)字节数相同,所有剩余字节数相同

  • compareTo()比较buffer中的剩余元素,只不过此方法适合排序

Channel(通道)

Channel的重要实现

  • FileChannel用于文件数据的读写,transferTo()方法可以将通道的数据传送至另外一个通道,完成数据的复制

  • DatagramChannel用于UDP数据的读写

  • SocketChannel用于TCP的数据读写,通常我们所说的客户端套接字通道

  • ServerSocketChannel允许我们监听TCP链接请求,通常我们所说的服务端套接字通道,每一个请求都会创建一个SocketChannel

Scatter和Gather

  • java nio在Channel实现类也实现了Scatter和Gather相关类

  • Scatter.read()是从通道读取的操作能把数据写入多个buffer,即一个通道向多个buffer写数据的过程,但是read必须写满一个buffer后才会向后移动到下一个buffer,因此read不适合大小会动态改变的数据。代码如下:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
  • Gather.write()是从可以把多个buffer的数据写入通道,write是只会写position到limit之间的数据,因此写是可以适应大小动态改变的数据。代码如下:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

在有些场景会非常有用,比如处理多份需要分开传输的数据,举例来说,假设一个消息包含了header和body,我们可能会把header和body分别放在不同的buffer

Selector(选择器)

Selector用于检查一个或多个NIO Channel的状态是否可读可写,这样就可以实现单线程管理多个Channels,也就是可以管理多个网络连接,NIO非阻塞主要就是通过Selector注册事件监听,监听通道将数据就绪后,就进行实际的读写操作,因此前面说的IO两个阶段,一阶段NIO仅仅是异步监听,二阶段就是同步实际操作数据

Selector监听事件类别

  • SelectionKey.OP_CONNECT是Channel和server连接成功后,连接就绪

  • SelectionKey.OP_ACCEPT是server Channel接收请求连接就绪

  • SelectionKey.OP_READ是Channel有数据可读时,处于读就绪

  • SelectionKey.OP_WRITE是Channel可以进行数据写入是,写就绪

使用Selector的步骤

  • 创建一个Selector,Selector selector = Selector.open();

  • 注册Channel到Selector上面,将Channel切换为非阻塞的:channel.configureBlocking(false),然后绑定Selector:SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

  • SelectionKey.OP_READ为监听的事件类别,如果要监听多个事件,可利用位的或运算结合多个常量如:int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

需要使用Selector的Channel必须是非阻塞的,FileChannel不能切换为非租塞的,因此FileChannel不适用于Selector

API

  • Selector.select()方法是阻塞的,因此可以放心的将它写在while(true)中,不用担心cpu会空转

  • Selector.wakeup()唤醒select()造成的阻塞,可能是有新的事件注册,优先级更高的事件触发(如定时器事件),希望及时处理。其原理是向通道或者连接中写入一个字节,阻塞的select因为有IO事件就绪,立即返回

Issues

  • 在使用TcpClient和TcpServer时,最开始客户端结束之后,服务端catch住了异常,但是并没有处理,那个客户端的Channel还是可以从Selector出来,原因是注册了这个事件没有取消注册,在catch中取消注册该事件就行,SelectionKey.cancel()

  • localhost是一个域名,通常指向127.0.0.1可在host文件配置

  • 127.0.0.1指本机地址环回地址,只有自己的机器可以访问

  • 0.0.0.0表示网络中的本机地址,即如果本机器连接了两个网络,在注册服务端时使用0.0.0.0,这两个网络的其他主机都可以访问这个服务端

我们可以理解为本机有三块网卡,一块网卡叫做 loopback(这是一块虚拟网卡)127.*整个网段用作loopback网络接口的默认地址,按照惯例通常设置为127.0.0.1这个地址和网络无关,这个是环回地址,直接找本地地址,如果服务端绑定这个地址,那就只有本地服务器才可以访问这个服务端,另外一块网卡叫做 ethernet (这是你的有线网卡),还有一块网卡叫做 wlan(这是你的无线网卡)

实例源代码如下图:前往下载

demo.png

相关文章

  • java NIO详解

    NIO原理 NIO与IO的区别 首先来讲一下传统的IO和NIO的区别,传统的IO又称BIO,即阻塞式IO,NIO就...

  • java NIO详解

    概述 BIO是面向字节流和字符流的,数据从流中顺序获取 NIO是面向通道和缓冲区的,数据总是从通道中读到buffe...

  • java NIO详解

    Java NIO系列教程(1):Java NIO 概述 NIO学习系列:缓冲区内部实现机制 Java NIO系列教...

  • Java NIO详解

    NIO含义 New I/O,原因在于它相对于之前的I/O类库是新增的。由于之前老的I/O类库是阻塞I/O,New ...

  • Java NIO 详解

    Java NIO简介: 1、JAVA NIO(New IO)是从java1.4版本开始引入的一个新的IO API,...

  • Java Nio详解

    在java io中,核心概念为流(Stream),面向流的编程,一个流要么是输出流,要么是输入流,不能够同时是输出...

  • nio

    参考文章 Java Nio Java NIO学习笔记 - NIO客户端时序图 Java NIO学习笔记 - NIO...

  • Java Nio 之Buffer

    Java Nio 系列Java Nio 之BufferJava Nio 之直接内存Java Nio 之高级搬砖工(...

  • Java Nio 之高级搬运工(FileChannel)二

    Java Nio 系列Java Nio 之BufferJava Nio 之直接内存Java Nio 之高级搬砖工(...

  • Java Nio 之高级搬砖工(FileChannel) 一

    Java Nio 系列Java Nio 之BufferJava Nio 之直接内存Java Nio 之高级搬砖工(...

网友评论

    本文标题:java NIO详解

    本文链接:https://www.haomeiwen.com/subject/stmugxtx.html