3-网络架构和Netty系列-Java NIO三件套Buffer、Selector、Channel

130 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Java NIO三件套

在NIO中有三个核心对象需要掌握:缓冲区(Buffer)、选择器(Selector)和通道(Channel)

缓冲区

Buffer操作基本API

缓冲区实际上是一个容器对象,更直接地说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的

在读取数据时,它是直接读到缓冲区中的;在写入数据时,它也是写入缓冲区的;任何时候访问NIO中的数据,都是将它放到缓冲区中。

在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer

Buffer的基本原理

在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪

position:指定下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。

limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。

capacity:指定了可以存储在缓冲区中的最大数据容量

缓冲区示意图

缓冲区写入:写入时position右移

缓冲区读取:一是把limit设置为当前的position值。二是把position设置为0。通过position下标从0开始读取,终点为limit。

缓冲区清空:恢复初始状态。

缓冲区的分配

在创建一个缓冲区对象时,会调用静态方法allocate()来指定缓冲区的容量,其实调用allocate()方法相当于创建了一个指定大小的数组,并把它包装为缓冲区对象

缓冲区分片

在NIO中,除了可以分配或者包装一个缓冲区对象,还可以根据现有的缓冲区对象创建一个子缓冲区,即在现有缓冲区上切出一片作为一个新的缓冲区,现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,子缓冲区相当于现有缓冲区的一个视图窗口。调用slice()方法可以创建一个子缓冲区。

只读缓冲区

只读缓冲区非常简单,可以读取它们,但是不能向它们写入数据。可以通过调用缓冲区的asReadOnlyBuffer()方法,将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化

直接缓冲区

直接缓冲区是为加快I/O速度,使用一种特殊方式为其分配内存的缓冲区

内存映射

内存映射是一种读和写文件数据的方法,可以比常规的基于流或者基于通道的I/O快得多

选择器

传统的Client/Server模式会基于TPR(Thread per Request),服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型

传统的Client/Server模式

在这里插入图片描述

NIO中实现非阻塞I/O的核心对象是Selector,Selector是注册各种I/O事件的地方,而且当那些事件发生时,就是Seleetor告诉我们所发生的事件

在这里插入图片描述

使用NIO中非阻塞I/O编写服务器处理程序,大体上可以分为下面三个步骤。

(1)向Selector对象注册感兴趣的事件。

(2)从Selector中获取感兴趣的事件。

(3)根据不同的事件进行相应的处理。

通道

通道是一个对象,通过它可以读取和写入数据,当然所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道,而是将数据写入包含一个或者多个字节的缓冲区

从通道读取到缓冲区的三个步骤

(1)从FileInputStream获取Channel。

(2)创建Buffer。

(3)将数据从Channel读取到Buffer中。