Java NIO 使用

1. 核心部分

  1. Buffer(缓冲区):缓存数据;
  2. Channel(管道):传输 Buffer 中的数据;
  3. Selector(选择器):通过检查一个或多个Channel的状态,判断是否处于可读、可写的状态,来实现单线程管理多个Channel。

1.1 Buffer

Buffer类维护了4个核心变量属性来提供关于其所包含的数组的信息:
其中 0 <= mark <= position <= limit <= capacity

  1. capacity(容量):Buffer能够容纳的数据元素的最大数量,底层是数组,在Buffer创建时被设定;
  2. position(位置):初始为0。当写数据时,表示当前位置写的位置,最大可为capacity-1;当读数据时,需要将Buffer从写模式切换成读模式,重置为0;
  3. limit(上界):缓冲区里的数据的总数。当写数据时,表示能容纳多少数据,limit等于capacity;当读数据时,需要将Buffer从写模式切换成读模式,表示能读取多少数据,limit等于写数据时的position;
  4. mark(标记):一个备忘位置。用于记录上一次读写的位置,包含pos,limit 和 cap 的值。

写模式
capacity和limit值相等,指向最大数量。通过改变position的值,记录写入位置。

读模式
通过 filp() 切换成读模式。limit指向position的位置,position指向头部,读取buffer中的值。
读写buffer

1.2 Channel

Channel类似流,但又有些不同:

  1. 可以从一个Channel中读取数据,再写入到另一个Channel中。而流的读写通常是单向的;
  2. Channel可以异步地读写;
  3. Channel中的数据总是要先读到一个Buffer,或者写入一个Buffer。

Channel负责传输数据,不直接操作数据的。操作数据都是通过Buffer来进行操作。

操作:从Channel读取数据后写入到Buffer,再从Buffer读取数据后写入到Channel。
channel buffer

非直接与直接缓冲区
非直接缓冲区需要经过一个 copy 的阶段的(从内核空间copy到用户空间);直接缓冲区不需要经过 copy 阶段,可理解成内存映射文件。

分散读取与聚集写入

  1. 分散读取(scatter):将一个通道中的数据分散读取并写入到多个缓冲区中;
  2. 聚集写入(gather):将多个缓冲区中的数据集中写入到一个通道中。

1.3 Selector

Selector 是选择器,可理解成多路复用器,能够仅用单线程来处理多个Channel。它通过检查一个或多个Channel的状态,判断是否处于可读、可写的状态,来实现单线程管理多个Channel,也就是可以管理多个网络连接。
selector

2. 测试

2.1 文件传输

传统IO

@Test
public void transferFileByIO() {
    File inputFile = new File("D:\\test.pdf");
    File outputFile = new File("D:\\Downloads\\test_1.pdf");

    try (
            BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(inputFile));
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
    ){
        int len;
        byte[] buffer = new byte[1024];
        while ((len = bufferedInputStream.read(buffer)) != -1){
            bufferedOutputStream.write(buffer,0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

NIO

@Test
public void transferFileByNio(){
    File inputFile = new File("D:\\test.pdf");
    File outputFile = new File("D:\\Downloads\\test_2.pdf");

    try (
            RandomAccessFile inputRandomAccessFile = new RandomAccessFile(inputFile, "rw");
            RandomAccessFile outputRandomAccessFile = new RandomAccessFile(outputFile, "rw");

            FileChannel inputFileChannel = inputRandomAccessFile.getChannel();
            FileChannel outputFileChannel = outputRandomAccessFile.getChannel();
    ){
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while(inputFileChannel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            outputFileChannel.write(byteBuffer);
            byteBuffer.clear();
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.2 网络通信

服务端

步骤:

  1. 将Channel注册到Selector中,监听Accept、Connect、Read和Write事件;
  2. Selector轮询Channel,当事件就绪时,进行处理;
  3. 处理完事件,删除该SelectionKey。

ServerSocketChannel 注册接收事件,成功接收后的 SocketChannel 注册读事件,Selector 监听注册使得事件,并进行处理。

public class NioServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        
        // 非阻塞模式
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8001));
        
        Selector selector = Selector.open();
        
        // 通道注册到选择器中,注册接收事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 轮询Channel,直到某个注册的channel有事件就绪
        while(selector.select() > 0) {
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("服务端已连接:" + socketChannel.getRemoteAddress());
                    // 非阻塞
                    socketChannel.configureBlocking(false);
                    // 通道注册到选择器中,注册读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    while(socketChannel.read(byteBuffer) > 0) {
                        byteBuffer.flip();
                        System.out.println("客户端 " + socketChannel.getRemoteAddress() +" 说:" + new String(byteBuffer.array()));
                        byteBuffer.clear();
                    }
                }
                // 删除已处理事件
                iterator.remove();
            }
        }
    }
}

客户端

这里等待用户输入信息,然后发送给服务端。

public class NioClient {
	public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8001));
        // 非阻塞模式
        socketChannel.configureBlocking(false);
        while(true) {
            Scanner scanner = new Scanner(System.in);
            String line = scanner.nextLine();
            socketChannel.write(ByteBuffer.wrap(line.getBytes()));
        }
    }
}

测试
客户端连接
客户端发消息

参考:
Java NIO Tutorial
Java NIO Tutorial 翻译
如何学习Java的NIO?
Java NIO之Selector(选择器)
Java NIO浅析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值