NIO基础

缓冲区

Buffer类及其实现

Buffer类是缓冲区的实现,类似于Java中的数组,也是用于存放和获取数据的。但它包含了一系列对于数组的快捷操作。

Buffer是一个抽象类,它的核心内容:

1
2
3
4
5
6
7
8
9
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;

// 直接缓冲区实现子类的数据内存地址
long address;
  • ByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

已IntBuffer为例,看看如何创建一个Buffer类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
// 创建一个缓冲区不能直接new,需要使用静态方法去生成。
// 方式一:申请一个容量为10的int缓冲区
IntBuffer buffer1 = IntBuffer.allocate(10);
// 方式二:将现有的数组直接转换为缓冲区
int[] arr = new int[] {1, 2, 3, 4, 5, 6};
IntBuffer buffer2 = IntBuffer.wrap(arr);

buffer2.put(1, 9);
System.out.println(buffer2.get(1));

System.out.println(buffer2.get());
System.out.println(buffer2.get());
System.out.println(buffer2.get());
System.out.println(buffer2.get());
}

通过对源码的观察,大致可以得到以下结构:

image-20230829233120856

  • Buffer

    缓冲区的一些基本变量定义,如当前位置position、容量capacity、最大限制limit、标记mark等。

  • IntBuffer等子类

    定义了存放数据的数组(只有堆缓冲区实现子类才会用到)、是否只读等。

  • HeapIntBuffer等堆缓冲区实现子类

    数据存放在堆中,实际上就是用的父类的数组在保存数据,并且实现了父类定义的所有底层操作。


缓冲区写操作


通道

文件锁FileLock

我们可以创建一个跨进程文件锁来防止多个进程之间的文件争抢操作。由于是进程级别的,它可以解决多个进程并发访问同一个文件的问题,但不适用于控制同一个进程中多个线程对同一个文件的访问

  • 进程对文件加独占锁后,当前进程对文件可读可写,独占此文件,其他进程不能读该文件进行读写操作。
  • 进程对文件加共享锁后,进程可以对文件进行读操作,无法进行写操作,共享锁可以被多个进程添加。只要存在共享锁,就不能添加独占锁。

多路复用网络通信

选择器与I/O多路复用

选择器是当具体有某一个状态(比如读、写、请求)已经就绪时,才会进行处理,而不是让我们的程序主动地进行等待。

3种IO多路复用模型

  • select: 当这些连接出现具体的某个状态时,只是知道已经就绪了,但不知道具体时哪一个连接已经就绪。每次调用需要线性便利所有连接,时间复杂度为O(n),并且存在最大连接数限制
  • poll:与select相同,但由于底层采用链表,所以没有最大连接数限制
  • epoll:采用事件通知方式,当某个连接就绪时,能直接进行精准通知(内核实现中,epoll根据每个文件描述符file descriptor上面的callback函数实现,只要就绪就会直接回调callback函数,实现精准通知,但仅linux支持这种方式),时间复杂度O(1),Java在Linux环境就是采用该模式进行实现。

Reactor模式


参考:柏马-NIO基础