1. 简介

Java NIO(New I/O)是Java提供的一种非阻塞I/O操作的机制,引入了一些新的核心概念,如通道(Channel)、缓冲区(Buffer)和选择器(Selector)。相比传统的阻塞I/O操作,Java NIO能够提供更高的性能和可扩展性。

2. 基本概念

在开始使用Java NIO进行开发之前,我们需要了解以下三个基本概念:通道(Channel)缓冲区(Buffer)选择器(Selector)

  • 通道(Channel)

    通道是Java NIO中的基本概念之一,代表着一个连接到实体如文件、套接字等的开放连接。可以通过通道读取和写入数据。Java NIO提供了不同类型的通道,如文件通道(FileChannel)和网络通道(SocketChannel、ServerSocketChannel等)。

  • 缓冲区(Buffer)

    缓冲区是一个对象,用于存储数据。在Java NIO中,所有数据都通过缓冲区进行传输。缓冲区实际上是一个数组,可以存储特定类型的数据。它提供了对数据的结构化访问和操作。

  • 选择器(Selector)

    选择器是Java NIO中的一个组件,用于监听多个通道的事件(如连接、读、写等事件)。通过选择器,可以实现单个线程同时处理多个通道的I/O操作。

3. NIO核心组件

Java NIO的核心组件包括通道(Channel)、缓冲区(Buffer)和选择器(Selector)。下面将对这些组件进行详细介绍。

通道(Channel)

通道是Java NIO中的一个抽象概念,表示与实体之间的连接。它可以用于读取和写入数据。Java NIO提供了以下类型的通道:

  • FileChannel:用于对文件进行读写操作。

  • SocketChannel:用于通过TCP进行网络套接字的读写操作。

  • ServerSocketChannel:用于监听传入的TCP连接请求。

  • DatagramChannel:用于通过UDP进行网络套接字的读写操作。

缓冲区(Buffer)

缓冲区是一个对象,用于存储数据。它是一个数组,可以存储特定类型的数据。缓冲区提供了对数据的结构化访问和操作。在Java NIO中,主要有以下类型的缓冲区:

  • ByteBuffer:用于存储字节数据。

  • CharBuffer:用于存储字符数据。

  • ShortBuffer:用于存储短整型数据。

  • IntBuffer:用于存储整型数据。

  • LongBuffer:用于存储长整型数据。

  • FloatBuffer:用于存储浮点型数据。

  • DoubleBuffer:用于存储双精度浮点型数据。

Buffer详解

Buffer就是一个数组缓冲池。Buffer有两种模式,往Buffer中写数据,或者从Buffer中读取数据。读写模式通过flip()函数切换。

Buffer默认为写模式

Buffer主要通过三个变量和一个切换函数flip()来维护。

  • 1、position 当前第一个可以读或者写的下标

  • 2、limit 当前可读或者可写的最后一位下标+1

  • 3、capacity Buffer的容量,也就是数组长度。

  • 4、flip() flip()用来切换读写模式。

当从写切换读之后,position和limit之间的数据是之前写的数据,可以进行读取。 当从读切换写之后,postion和limit之间才可以被写数据。

public final Buffer flip() {
    limit = position;
    position = 0;
    return this;
}

主要方法

get(): 读取一个byte,将position++

get(byte[] bytes,int offeset,int length): 将buffer中的[offset,offset+length)复制到bytes中,position+=length

put(byte[] bytes): 往buffer中写入bytes,position += bytes.length

clear(): 将buffer清除,position=0,limit=capacity

compact(): 假设还有n个字节未被读取,就将这n个字节搬运到数组头部,从数组的第n位开始写

remaining(): 返回limit - position,也就是当前还是多少字节未读取或者未写

选择器(Selector)

选择器是Java NIO中的一个组件,用于监听多个通道的事件。通过选择器,可以实现单个线程同时处理多个通道的I/O操作。选择器提供了一个高效的事件驱动机制,可以显著减少线程的数量。它的主要方法包括:

  • open():打开一个选择器。

  • select():选择已经准备就绪的通道。

  • selectNow():非阻塞地选择已经准备就绪的通道。

  • register():注册通道到选择器,并指定感兴趣的事件。

  • cancel():取消通道的注册。

4. NIO的使用示例

下面将通过一个简单的示例来演示如何使用Java NIO进行开发。

创建通道(Channel)

首先,我们需要创建一个通道来进行数据的读取和写入。以下示例演示如何创建一个文件通道(FileChannel):

import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
​
public class ChannelExample {
    public static void main(String[] args) throws Exception {
        // 创建一个文件通道
        RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
        FileChannel channel = file.getChannel();
        
        // 使用通道进行读写操作
        // ...
        
        // 关闭通道
        channel.close();
    }
}

使用缓冲区(Buffer)

一旦创建了通道,我们可以使用缓冲区来进行数据的读取和写入操作。以下示例演示如何使用ByteBuffer进行数据的读取和写入:

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
​
public class BufferExample {
    public static void main(String[] args) throws Exception {
        // 创建一个文件通道
        RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
        FileChannel channel = file.getChannel();
        
        // 创建一个缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        // 从通道读取数据到缓冲区
        int bytesRead = channel.read(buffer);
        
        // 从缓冲区写入数据到通道
        int bytesWritten = channel.write(buffer);
        
        // 清空缓冲区
        buffer.clear();
        
        // 关闭通道
        channel.close();
    }
}

使用选择器(Selector)

选择器可以用于同时监听多个通道的事件。以下示例演示如何使用选择器:

import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
​
public class SelectorExample {
    public static void main(String[] args) throws Exception {
        // 创建一个选择器
        Selector selector = Selector.open();
        
        // 创建一个服务器套接字通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        
        // 注册通道到选择器,并指定感兴趣的事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        // 监听选择器上的事件
        while (true) {
            int readyChannels = selector.select();
            
            // 处理已经准备就绪的通道
            // ...
        }
        
        // 关闭选择器
        selector.close();
    }
}