• Java NIO详解


    NIO

    NIO(non-blocking IO)是jdk1.4之后提供的一套API,
    可以替代原来的标准IO,
    支持面向缓冲区的、基于通道的IO操作,
    可以更高效地进行IO操作。

    NIO有3个核心组件:
    Channel,
    Buffer,
    Selector。
    在这里插入图片描述

    Channel

    Channel是连接数据源头与目的地的通道,
    CHannel不直接操作数据,Channel通过Buffer来进行交互,
    Channel是Buffer的载体。

    获取Channel
    SocketChannel是Channel的实现类。

    // 通过open()获取Channel
    SocketChannel channel = SocketChannel.open();
    
    • 1
    • 2

    Buffer

    Buffer是一个数据对象,可以理解为固定大小的容器。

    NIO中的数据访问,
    都是通过缓冲区Buffer来进行操作的,
    读取数据时从缓冲区读取,写入数据时写入到缓冲区。

    缓冲区两个核心方法

    put() 写入到缓冲区;
    get() 从缓冲区读取。

    缓冲区四个核心属性

    capacity:
    容量,缓冲区最大可以存储的数据量,声明后不能更改;
    limit:
    界限,缓冲区中可以操作数据的范围;
    position:
    位置,缓冲区中正在操作数据的位置;
    mark:
    标记,记录当前position,可以通过reset()恢复到mark的位置。

    注意:

    0 <= mark <= position <= limit <= capacity
    
    • 1

    在这里插入图片描述
    获取Buffer
    ByteBuffer是Buffer的实现类。

    // 通过allocate()获取Buffer,需要声明大小。
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    // 通过flip()切换读写模式
    buffer.flip();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    非直接缓冲区

    通过allocate()分配的缓冲区,建立在jvm内存之中。

    操作系统出于安全方面的考虑,一般不会允许应用程序和磁盘之间直接进行传输,
    如果我们的应用程序想从磁盘读取数据,要分以下几步走:
    应用程序向操作系统发出读请求;
    操作系统将磁盘数据加载到内核地址空间;
    再把内核地址空间中的数据copy到用户地址空间,在这儿就是jvm;
    然后在应用程序读取。
    在这里插入图片描述
    同样,如果我们的应用程序想往磁盘写数据,
    也要先写到用户地址空间,然后copy到内核地址空间,再写入到磁盘中。

    直接缓冲区

    通过allocateDirect()分配的缓冲区,建立在物理内存之中。

    数据的读写直接在物理内存中进行。
    在这里插入图片描述

    Selector

    Selector允许一个线程处理多个Channel,
    Selector会轮训注册在它上面的所有Channel,如果某个Channel为读写做好准备,处于就绪状态,
    Selector就会对其进行后续的IO操作,
    不会同步阻塞等待,这就是non-blocking。
    因为线程不会一直等待IO条件准备就绪,如果某个Channel没有就绪,会转而去处理其它Channel。

    就绪状态:
    SelectionKey.OP_READ:可读;
    SelectionKey.OP_WRITE:可写;
    SelectionKey.OP_CONNECT:连接;
    SelectionKey.OP_ACCEPT:接收。

    当Channel处于某个就绪状态,就会被Selector查询到,然后执行相应的操作。
    在这里插入图片描述
    传统的IO,面向IO流,每一个线程对应一个连接,如果IO尚未处于就绪状态,线程就会阻塞等待。
    在这里插入图片描述
    获取Selector

    // 通过open()获取Selector
    Selector selector = Selector.open();
    
    • 1
    • 2

    完整实例

    Server

    package com.example.duohoob.nio;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.net.SocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    import java.util.Set;
    
    public class NioServer {
    
    	public static void main(String[] args) throws IOException {
    		// 创建ServerSocketChannel
    		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    		
    		// 绑定到本地8088端口
    		SocketAddress endpoint = new InetSocketAddress(8088);
    		serverSocketChannel.socket().bind(endpoint);
    		
    		// 设置非阻塞
    		serverSocketChannel.configureBlocking(false);
    		
    		// 通过open()获取Selector
    		Selector selector = Selector.open();
    		
    		// 将Channel注册到Selector,需要指定关注的就绪状态,多个用'|'分割。
    		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 接收就绪状态
    		
    		while (true) {
    			// 轮询已就绪Channel
    			selector.select();
    			
    			// 遍历已就绪事件selectionKeys
    			Set<SelectionKey> selectionKeys = selector.selectedKeys();
    			Iterator<SelectionKey> iterator = selectionKeys.iterator();
    			while (iterator.hasNext()) {
    				SelectionKey selectionKey = (SelectionKey) iterator.next();
    				// 处理连接事件
    				if (selectionKey.isAcceptable()) {
    					// 接收SocketChannel
    					SocketChannel socketChannel = serverSocketChannel.accept();
    					
    					// 设置非阻塞
    					socketChannel.configureBlocking(false);
    					
    					// 将SocketChannel注册到Selector,指定关注可读就绪状态。
    					socketChannel.register(selector, SelectionKey.OP_READ);
    				}
    				
    				// 处理可读事件
    				if (selectionKey.isReadable()) {
    					// 通过allocate()获取Buffer,需要指定大小。
    					ByteBuffer buffer = ByteBuffer.allocate(1024);
    					
    					// 读取
    					SocketChannel channel = (SocketChannel) selectionKey.channel();
    					channel.read(buffer);
    					
    					System.out.println(new String(buffer.array(), "utf-8"));
    				}
    				
    				// 处理完毕,移除。
    				iterator.remove();
    			}
    		}
    	}
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    Client

    package com.example.duohoob.nio;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    
    public class NioClient {
    
    	public static void main(String[] args) throws IOException {
    		// 创建SocketChannel
    		SocketChannel socketChannel = SocketChannel.open();
    		
    		// 设置非阻塞
    		socketChannel.configureBlocking(false);
    		
    		// 服务端
    		InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8088);
    		
    		if (!socketChannel.connect(inetSocketAddress)) {
    			while (!socketChannel.finishConnect()) {
    				// 连接服务端
    			}
    		}
    		
    		// 获取Buffer
    		String message = "message from client.";
    		ByteBuffer buffer = ByteBuffer.wrap(message.getBytes("utf-8"));
    		
    		// 写入
    		socketChannel.write(buffer);
    		
    		// 关闭SocketChannel
    		socketChannel.close();
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    python统计应用
    使用示波器探头的五个有效步骤
    Leaflet加载天地图
    国科大移动互联网考试资料(2023+2020+2018真题+答案)
    b、B、KB、Kib、MB、MiB、GB、GiB、TB、TiB的区别
    git介绍、安装、配置
    git学习笔记——git pull篇
    【操作系统】2009年408真题第 46 题
    (STM32)从零开始的RT-Thread之旅--GPIO
    BrokerChain——基于“做市商账户”的区块链跨分片协议
  • 原文地址:https://blog.csdn.net/qq_35549286/article/details/127124958