您的位置 首页 java

Java NIO,快看看有哪些你还不知道

上一篇我们聊了高并发 IO 原理及模型,相信大家都基本掌握好了,So,我们今天继续来说说Java NIO,它是当今众多主流技术框架的基石,实属我们高性能通信旅程必备良方!

一、Java NIO简介

在JDK1.4之前,Java IO类库都是阻塞的IO;从1.4开始,引进了新的异步IO库,被称为Java New IO,简称 Java NIO,是一种非阻塞的IO,为Java提供了高速且带有缓冲区的IO,它属于 IO多路复用模型 ,主要包含以下三大组件:

  • Channel 通道
  • Buffer 缓冲区
  • Selector 选择器

二、New IO与Old IO的PK

NIO(New IO)相较于OIO(Old IO),主要区别有以下几个:

  • OIO是面向Stream的,NIO则是面向缓冲区。在一般的OIO中,我们以流的方式顺序读取一个或者多个字节,不能随意改变读取的位置;而在NIO中,通过Channel和Buffer,我们可以从通道读取数据到缓冲区中,或者将数据从缓冲区写入通道中,并且NIO可以随意读取缓冲区的任意数据;
  • OIO是阻塞的,NIO是非阻塞的。比如我们调用read方法时,OIO模式下,整个线程会阻塞等待,直到read操作完成;而在NIO模式下,如果此时没有数据,则read直接返回,如果此时有数据,则read数据并返回。
  • OIO没有Selector选择器,而NIO则是基于Selector实现,这个需要底层操作系统的支持。

下面进入NIO三大组件解说之旅,请大家坐好扶稳

三、Channel通道详解

在NIO中,同一个网络连接使用一个通道,所有的NIO的IO操作都是从通道开始的,它即可以从通道读取数据,也可以向通道写入数据。目前主要的Channel有如下四种:

  • FileChannel:文件通道,用于文件数据的读写;
  • Socket Channel:套接字通道,用于Socket套接字TCP连接的数据读写;
  • ServerSocketChannel:服务器套接字通道,允许我们监听TCP连接的请求,为每个监听到的请求创建一个相应的Socket套接字通道;
  • DatagramChannel:数据报通道,用于UDP协议的数据读写。

现在说一下FileChannel的用法,加深大伙对NIO的一个印象,这里通过RandomAccessFile进行文件的读取操作:

四、Buffer缓冲区详解

应用程序与通道的主要交互,就是进行数据的读取与写入,在他们之间有一个缓冲区,缓冲区本质上是一个内存块,提供了一组有效的方法来进行写入和读取的交替访问。

Buffer类是一个抽象类,在NIO中有8种缓冲区类,分别如下:

  • 整型Buffer:ByteBuffer、IntBuffer、LongBuffer
  • 浮点型Buffer:FloatBuffer、DoubleBuffer
  • 字符型Buffer:CharBuffer
  • 内存映射型Buffer:MappedByteBuffer

Buffer类的重要属性

Buffer类底层是由一个byte[]数组构成,为了记录读写的状态和位置,它提供了一些重要的属性,其中,比较重要的有如下三个:capacity(容量)、 position (读写位置)、limit(读写限制)。

除此之外,还有一个标记属性mark,可以临时保存 当前position,配合reset属性,可以恢复到之前mark标记的position位置。

1、capacity属性

capacity表示内部容量的大小,一旦写入的数据超过了capacity容量大小,将不能再写入。同时,在它初始化后,也不能再改变了。

2、position属性

这里需要注意一下,position属性与缓冲区的读写模式有关,在不同的模式下,position属性的值是不一样的,当读写模式互相切换的时候,position会进行相应的调整,规则如下

  • 在写入模式下,position的值变化规则如下:(1)在刚进入到写模式时,position值为0,表示当前的写入位置为从头开始。(2)每当一个数据写到缓冲区之后,position会向后移动到下一个可写的位置。(3)初始的position值为0,最大可写值position为limit-1。当position值达到limit时,缓冲区就已经无空间可写了。
  • 在读模式下,position的值变化规则如下:(1)当缓冲区刚开始进入到读模式时,position会被重置为0。(2)当从缓冲区读取时,也是从position位置开始读。读取数据后,position向前移动到下一个可读的位置。(3)position最大的值为最大可读上限limit,当position达到limit时,表明缓冲区已经无数据可读。

缓冲区创建成功之后,默认处于写模式,数据写入后,如果要读取数据,这时要进行模式切换,我们可以使用flip方法,将写模式切换为读模式,在这个flip翻转过程中,position会进行非常巨大的调整,具体的规则是:position由原来的写入位置,变成新的可读位置,也就是0,表示可以从头开始读。flip翻转的另外一半工作,就是要调整limit属性。

3、limit属性

limit表示读写的最大上限,它也和缓冲区的读写模式有关,在不同的模式下,它的含义也是不一样的。

  • 在写模式下,limit属性值的含义为可以写入的数据最大上限。在刚进入到写模式时,limit的值会被设置成缓冲区的capacity容量值,表示可以一直将缓冲区的容量写满。
  • 在读模式下,limit的值含义为最多能从缓冲区中读取到多少数据。

如何使用Java NIO Buffer?下面举个简单的栗子

  1. 使用Buffer子类创建实例对象,并且分配内存空间,这里调用子类的 allocate ()方法即可;
  2. 调用put方法,将数据写入至缓冲区;
  3. 写入成功后,调用flip()方法,从开始的写模式切换为读取模式;
  4. 调用get方法,从缓冲区读取数据;
  5. 读取完成后,调用clear()或者compact()方法,将缓冲区转换为写入模式,此方法会将position清0,limit设置为capacity的最大容量值。

五、Selector选择器详解

Selector是实现IO多路复用的关键,它属于一个IO事件查询器,通过这个查询器,我们可以只用一个线程,即可获取多个通道的IO事件状态,这是非常高效的一个行为。实现IO多路复用,从开发层面来说,首先把通道注册到选择器中,然后通过选择器来查询这些注册的通道是否有已经就绪的IO事件,比如可读、可写、网络连接完成等。相比OIO的每个网络连接对应一个线程,NIO大大减小了系统开销。

Selector选择器和通道的关系密切,它们通过注册的方式连接在一起。调用通道的Channel.register(Selector sel, int ops)方法,可以将通道注册到一个选择器中,这个方法的第一个参数代表目标通道需要注册到的选择器,第二个参数代表选择器需要监控的IO事件类型。

可供监听的IO事件类型有如下四种:

  1. SelectionKey.OP_READ:可读
  2. SelectionKey.OP_WRITE:可写
  3. SelectionKey.OP_CONNECT:连接
  4. SelectionKey.OP_ACCEPT:接收

如何使用选择器?

  1. 获取选择器实例;
  2. 将通道注册到选择器中;
  3. 轮询感兴趣的IO就绪事件。

以下为服务端使用选择器的一个小栗子,供大家参考

public class ServerSocket {

private Selector selector = null;

private ByteBuffer readByteBuffer = ByteBuffer.allocate(1024);

public static void main(String[] args) {

ServerSocket serverSocket = new ServerSocket(9988);

serverSocket.serverRun();

}

public ServerSocket(int port) {

try {

this.selector = Selector. open ();

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//创建服务端通道

serverSocketChannel.configureBlocking(false);//服务端通道设置为非阻塞

serverSocketChannel.bind(new InetSocketAddress(port));//绑定服务端端口

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//将服务端通道注册到选择器,并且监听连接事件

System.out.println(“服务端初始化成功,端口为” + port);

} catch (Exception e) {

e.printStackTrace();

}

}

public void serverRun() {

//循环监听

while (true) {

try {

//多路复用开始监听

int num = this.selector.select();

if (num == 0) {

continue;

}

//返回可选择的key集合

Set<SelectionKey> selectionKeys = this.selector.selectedKeys();

//遍历结果集进行处理

Iterator<SelectionKey> it = selectionKeys.iterator();

while (it.hasNext()) {

SelectionKey key = it.next();

it.remove();

if (key.isValid()) {

if (key.isAcceptable()) {

//通道是否有新连接

System.out.println(“acceptable..”);

}

if (key.isConnectable()) {

//通道连接成功

System.out.println(“connectable..”);

}

if (key.isReadable()) {

//通道可读

System.out.println(“readable..”);

}

if (key.isWritable()) {

//通道可写

System.out.println(“writable..”);

}

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

文章来源:智云一二三科技

文章标题:Java NIO,快看看有哪些你还不知道

文章地址:https://www.zhihuclub.com/183623.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

网站地图