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();

Buffer

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

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

缓冲区两个核心方法

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

缓冲区四个核心属性

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

注意:

0 <= mark <= position <= limit <= capacity

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

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

非直接缓冲区

通过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();

完整实例

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();
			}
		}
	}
	
}

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();
	}
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值