相关文章链接
processSelectedKeys() vs runAllTasks()
NioServerSocketChannel-Unsafe初始化详解
位运算基础
什么是位运算?
位运算就是直接对二进制数字的每一位(0 或 1)进行操作的运算。
先理解二进制
我们平时用的是十进制(0-9),计算机用的是二进制(0-1):
十进制 二进制
0 → 0000
1 → 0001
2 → 0010
3 → 0011
4 → 0100
5 → 0101
6 → 0110
7 → 0111
8 → 1000
转换规则:从右往左,每一位代表 2 的 n 次方
二进制: 1001
↓↓↓↓
位置: 3210 (从右往左数,从0开始)
计算: 1×2³ + 0×2² + 0×2¹ + 1×2⁰
= 1×8 + 0×4 + 0×2 + 1×1
= 8 + 0 + 0 + 1
= 9 (十进制)
1. 左移运算符 <<
基本概念
<< 表示左移,就是把二进制数字向左移动若干位。
语法:数字 << 移动位数
图解左移
原始数字: 1 (二进制: 0001)
1 << 0 → 0001 (向左移0位,不动) = 1
1 << 1 → 0010 (向左移1位) = 2
1 << 2 → 0100 (向左移2位) = 4
1 << 3 → 1000 (向左移3位) = 8
1 << 4 → 10000 (向左移4位) = 16
规律:1 << n 等于 2^n(2 的 n 次方)
1 << 0 = 2⁰ = 1
1 << 1 = 2¹ = 2
1 << 2 = 2² = 4
1 << 3 = 2³ = 8
1 << 4 = 2⁴ = 16
为什么左移一位等于乘以2?
十进制类比:
123 向左移一位(补0) = 1230 = 123 × 10
二进制同理:
101 (5) 向左移一位 = 1010 (10) = 5 × 2
2. 位或运算 |
基本概念
| 表示位或运算,规则:只要有一个是 1,结果就是 1
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
组合多个事件
// 同时关心 READ 和 WRITE
int events = OP_READ | OP_WRITE;
// 计算过程:
OP_READ = 0001
OP_WRITE = 0100
------ (对齐每一位进行或运算)
结果 = 0101 (十进制: 5)
// 验证:
第0位: 0 | 0 = 0
第1位: 0 | 1 = 1 ← OP_READ 的位
第2位: 1 | 0 = 1 ← OP_WRITE 的位
第3位: 0 | 0 = 0
结果: 0101
更多组合示例
// 示例1: READ + WRITE + ACCEPT
int events = OP_READ | OP_WRITE | OP_ACCEPT;
OP_READ = 00001
OP_WRITE = 00100
OP_ACCEPT = 10000
-------
结果 = 10101 (十进制: 21)
// 示例2: 所有事件
int events = OP_READ | OP_WRITE | OP_CONNECT | OP_ACCEPT;
OP_READ = 00001
OP_WRITE = 00100
OP_CONNECT = 01000
OP_ACCEPT = 10000
-------
结果 = 11101 (十进制: 29)
3. 位与运算 &
基本概念
& 表示位与运算,规则:两个都是 1,结果才是 1
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
判断是否包含某个事件
// 假设 interestOps = 5 (二进制: 0101)
// 表示同时关心 READ 和 WRITE
int interestOps = 5; // 0101
// 判断是否包含 READ
(interestOps & OP_READ) != 0
// 计算过程:
interestOps = 0101
OP_READ = 0001
------
结果 = 0001 (不等于0,说明包含 READ)
// 判断是否包含 WRITE
(interestOps & OP_WRITE) != 0
// 计算过程:
interestOps = 0101
OP_WRITE = 0100
------
结果 = 0100 (不等于0,说明包含 WRITE)
// 判断是否包含 ACCEPT
(interestOps & OP_ACCEPT) != 0
// 计算过程:
interestOps = 0101
OP_ACCEPT = 10000
-------
结果 = 00000 (等于0,说明不包含 ACCEPT)
核心位运算符总结
| 运算符 | 名称 | 作用 | 示例 |
|---|---|---|---|
<< | 左移 | 向左移动若干位 | 1 << 2 = 4 |
| | 位或 | 组合多个标志 | OP_READ | OP_WRITE |
& | 位与 | 判断是否包含某个标志 | (ops & OP_READ) != 0 |
~ | 位非 | 取反 | ~OP_WRITE |
四、SelectionKey 中的位运算
事件常量定义
public static final int OP_READ = 1 << 0; // 二进制: 0001 (十进制: 1)
public static final int OP_WRITE = 1 << 2; // 二进制: 0100 (十进制: 4)
public static final int OP_CONNECT = 1 << 3; // 二进制: 1000 (十进制: 8)
public static final int OP_ACCEPT = 1 << 4; // 二进制: 10000 (十进制: 16)
详细计算过程
// OP_READ = 1 << 0
原始: 0001 (1)
左移0位: 0001 (不动)
结果: 1 (十进制)
// OP_WRITE = 1 << 2
原始: 0001 (1)
左移2位: 0100 (向左移2位,右边补0)
结果: 4 (十进制)
// OP_CONNECT = 1 << 3
原始: 0001 (1)
左移3位: 1000 (向左移3位,右边补0)
结果: 8 (十进制)
// OP_ACCEPT = 1 << 4
原始: 0001 (1)
左移4位: 10000 (向左移4位,右边补0)
结果: 16 (十进制)
为什么要这样设计?
关键点:每个事件占用不同的二进制位!
OP_READ = 0001 (第0位是1)
OP_WRITE = 0100 (第2位是1)
OP_CONNECT = 1000 (第3位是1)
OP_ACCEPT = 10000 (第4位是1)
这样设计的好处:可以用一个数字同时表示多个事件!
interestOps() 方法返回值详解
返回值类型:int
返回值含义:一个整数,用二进制位表示当前关心的所有事件。
// 假设只关心 READ 事件
key.interestOps(SelectionKey.OP_READ);
int interestSet = key.interestOps();
System.out.println(interestSet); // 输出: 1
System.out.println(Integer.toBinaryString(interestSet)); // 输出: 1 (二进制: 0001)
// 假设关心 READ 和 WRITE 事件
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
int interestSet = key.interestOps();
System.out.println(interestSet); // 输出: 5
System.out.println(Integer.toBinaryString(interestSet)); // 输出: 101 (二进制: 0101)
// 解释: 0001 (READ) | 0100 (WRITE) = 0101 (5)
// 假设关心所有事件
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_ACCEPT);
int interestSet = key.interestOps();
System.out.println(interestSet); // 输出: 21
System.out.println(Integer.toBinaryString(interestSet)); // 输出: 10101 (二进制: 010101)
// 解释: 0001 (READ) | 0100 (WRITE) | 10000 (ACCEPT) = 10101 (21)
如何判断返回值中包含哪些事件?
使用位与运算(&):
int interestSet = key.interestOps();
// 使用位与运算判断
if ((interestSet & SelectionKey.OP_READ) != 0) {
System.out.println("关心 READ 事件");
}
if ((interestSet & SelectionKey.OP_WRITE) != 0) {
System.out.println("关心 WRITE 事件");
}
if ((interestSet & SelectionKey.OP_ACCEPT) != 0) {
System.out.println("关心 ACCEPT 事件");
}
if ((interestSet & SelectionKey.OP_CONNECT) != 0) {
System.out.println("关心 CONNECT 事件");
}
设置多个事件
可以! 使用**位或运算(|)**组合多个事件。
// 关心单个事件
key.interestOps(SelectionKey.OP_READ);
// 关心两个事件
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// 关心三个事件
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_ACCEPT);
// 关心所有事件(不常用)
key.interestOps(
SelectionKey.OP_READ |
SelectionKey.OP_WRITE |
SelectionKey.OP_ACCEPT |
SelectionKey.OP_CONNECT
);
动态修改关心的事件
// 初始只关心读
key.interestOps(SelectionKey.OP_READ);
// 添加写事件(保留原有的读事件)
int currentOps = key.interestOps();
key.interestOps(currentOps | SelectionKey.OP_WRITE);
// 现在同时关心 READ 和 WRITE
// 移除写事件(只保留读事件)
currentOps = key.interestOps();
key.interestOps(currentOps & ~SelectionKey.OP_WRITE);
// 现在只关心 READ
常见组合场景
// 1. 服务端:只关心接受新连接
serverKey.interestOps(SelectionKey.OP_ACCEPT);
// 2. 客户端连接中:关心连接是否完成
clientKey.interestOps(SelectionKey.OP_CONNECT);
// 3. 正常通信:同时关心读和写
clientKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// 4. 只读模式:只关心读
clientKey.interestOps(SelectionKey.OP_READ);
// 5. 只写模式:只关心写(缓冲区满时常用)
clientKey.interestOps(SelectionKey.OP_WRITE);
为什么 SelectionKey 用位运算?
- 节省空间:一个 int (4字节) 可以表示 32 种事件
- 高效:位运算是 CPU 最快的运算
- 方便组合:用
|轻松组合多个事件 - 方便判断:用
&快速判断是否包含某个事件
五、完整示例代码
示例1:位运算演示
public class BitOperationDemo {
// 定义事件常量
public static final int OP_READ = 1 << 0; // 1
public static final int OP_WRITE = 1 << 2; // 4
public static final int OP_CONNECT = 1 << 3; // 8
public static final int OP_ACCEPT = 1 << 4; // 16
public static void main(String[] args) {
System.out.println("=== 事件常量值 ===");
System.out.println("OP_READ = " + OP_READ +
" (二进制: " + toBinaryString(OP_READ) + ")");
System.out.println("OP_WRITE = " + OP_WRITE +
" (二进制: " + toBinaryString(OP_WRITE) + ")");
System.out.println("OP_CONNECT = " + OP_CONNECT +
" (二进制: " + toBinaryString(OP_CONNECT) + ")");
System.out.println("OP_ACCEPT = " + OP_ACCEPT +
" (二进制: " + toBinaryString(OP_ACCEPT) + ")");
System.out.println("\n=== 组合多个事件(位或运算 |)===");
// 组合 READ 和 WRITE
int events1 = OP_READ | OP_WRITE;
System.out.println("READ | WRITE = " + events1 +
" (二进制: " + toBinaryString(events1) + ")");
// 组合 READ、WRITE 和 ACCEPT
int events2 = OP_READ | OP_WRITE | OP_ACCEPT;
System.out.println("READ | WRITE | ACCEPT = " + events2 +
" (二进制: " + toBinaryString(events2) + ")");
System.out.println("\n=== 判断是否包含某个事件(位与运算 &)===");
int interestOps = OP_READ | OP_WRITE; // 5
System.out.println("当前关心的事件: " + interestOps +
" (二进制: " + toBinaryString(interestOps) + ")");
// 判断是否包含各个事件
System.out.println("包含 READ? " +
((interestOps & OP_READ) != 0));
System.out.println("包含 WRITE? " +
((interestOps & OP_WRITE) != 0));
System.out.println("包含 CONNECT? " +
((interestOps & OP_CONNECT) != 0));
System.out.println("包含 ACCEPT? " +
((interestOps & OP_ACCEPT) != 0));
System.out.println("\n=== 添加事件 ===");
int ops = OP_READ;
System.out.println("初始: " + ops +
" (二进制: " + toBinaryString(ops) + ")");
ops = ops | OP_WRITE; // 添加 WRITE
System.out.println("添加 WRITE 后: " + ops +
" (二进制: " + toBinaryString(ops) + ")");
ops = ops | OP_ACCEPT; // 添加 ACCEPT
System.out.println("添加 ACCEPT 后: " + ops +
" (二进制: " + toBinaryString(ops) + ")");
System.out.println("\n=== 移除事件(位与非运算 & ~)===");
ops = ops & ~OP_WRITE; // 移除 WRITE
System.out.println("移除 WRITE 后: " + ops +
" (二进制: " + toBinaryString(ops) + ")");
}
// 工具方法:转换为固定长度的二进制字符串
private static String toBinaryString(int num) {
String binary = Integer.toBinaryString(num);
// 补齐到5位
return String.format("%5s", binary).replace(' ', '0');
}
}
运行结果:
=== 事件常量值 ===
OP_READ = 1 (二进制: 00001)
OP_WRITE = 4 (二进制: 00100)
OP_CONNECT = 8 (二进制: 01000)
OP_ACCEPT = 16 (二进制: 10000)
=== 组合多个事件(位或运算 |)===
READ | WRITE = 5 (二进制: 00101)
READ | WRITE | ACCEPT = 21 (二进制: 10101)
=== 判断是否包含某个事件(位与运算 &)===
当前关心的事件: 5 (二进制: 00101)
包含 READ? true
包含 WRITE? true
包含 CONNECT? false
包含 ACCEPT? false
=== 添加事件 ===
初始: 1 (二进制: 00001)
添加 WRITE 后: 5 (二进制: 00101)
添加 ACCEPT 后: 21 (二进制: 10101)
=== 移除事件(位与非运算 & ~)===
移除 WRITE 后: 17 (二进制: 10001)
示例2:SelectionKey 完整生命周期
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class SelectionKeyLifecycleDemo {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(7397));
serverChannel.configureBlocking(false);
// 1. 创建 SelectionKey(注册时返回)
SelectionKey serverKey = serverChannel.register(
selector,
SelectionKey.OP_ACCEPT
);
System.out.println("=== SelectionKey 创建成功 ===");
System.out.println("关联的 Channel: " + serverKey.channel());
System.out.println("关联的 Selector: " + serverKey.selector());
System.out.println("感兴趣的事件: " + serverKey.interestOps());
while (true) {
// 2. 等待事件就绪
selector.select();
// 3. 获取就绪的 SelectionKey 集合
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove(); // 必须手动移除!
// 4. 检查 key 的状态
if (!key.isValid()) {
System.out.println("Key 已失效");
continue;
}
// 5. 根据就绪事件进行处理
if (key.isAcceptable()) {
System.out.println("=== 处理 ACCEPT 事件 ===");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 6. 新连接也注册,返回新的 SelectionKey
SelectionKey clientKey = client.register(
selector,
SelectionKey.OP_READ
);
// 7. 附加数据到 SelectionKey
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientKey.attach(buffer);
System.out.println("新客户端连接,创建新的 SelectionKey");
}
if (key.isReadable()) {
System.out.println("=== 处理 READ 事件 ===");
SocketChannel client = (SocketChannel) key.channel();
// 8. 获取附加的 Buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
int len = client.read(buffer);
if (len > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("收到数据: " + new String(data));
buffer.clear();
// 9. 修改感兴趣的事件(现在想写数据)
key.interestOps(SelectionKey.OP_WRITE);
} else if (len == -1) {
// 10. 客户端断开,取消 key
System.out.println("客户端断开连接");
key.cancel();
client.close();
}
}
if (key.isWritable()) {
System.out.println("=== 处理 WRITE 事件 ===");
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer response = ByteBuffer.wrap("Hello Client!".getBytes());
client.write(response);
// 写完后,改回关注读事件
key.interestOps(SelectionKey.OP_READ);
}
}
}
}
}
示例3:实际应用场景
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
public class SelectionKeyPracticalDemo {
// 客户端会话信息
static class ClientSession {
String clientId;
long connectTime;
ByteBuffer buffer;
public ClientSession() {
this.clientId = UUID.randomUUID().toString();
this.connectTime = System.currentTimeMillis();
this.buffer = ByteBuffer.allocate(1024);
}
}
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
// 场景1:服务端 ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(7397));
serverChannel.configureBlocking(false);
// 服务端通常只关心 ACCEPT 事件
SelectionKey serverKey = serverChannel.register(
selector,
SelectionKey.OP_ACCEPT
);
System.out.println("=== 服务端启动,监听端口 7397 ===");
printInterestOps("服务端", serverKey);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
// 接受客户端连接
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 场景2:为每个客户端创建会话信息
ClientSession session = new ClientSession();
// 场景3:客户端 SocketChannel 同时关心 READ 和 WRITE
SelectionKey clientKey = client.register(
selector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE
);
// 场景4:将会话信息附加到 SelectionKey
clientKey.attach(session);
System.out.println("\n=== 新客户端连接 ===");
System.out.println("客户端ID: " + session.clientId);
System.out.println("连接时间: " + session.connectTime);
printInterestOps("客户端", clientKey);
}
if (key.isReadable()) {
System.out.println("\n=== 可读事件触发 ===");
SocketChannel client = (SocketChannel) key.channel();
// 场景5:从 SelectionKey 获取会话信息
ClientSession session = (ClientSession) key.attachment();
int len = client.read(session.buffer);
if (len > 0) {
session.buffer.flip();
byte[] data = new byte[session.buffer.remaining()];
session.buffer.get(data);
System.out.println("客户端ID: " + session.clientId);
System.out.println("收到数据: " + new String(data));
session.buffer.clear();
// 场景6:读完数据后,只关心写事件
key.interestOps(SelectionKey.OP_WRITE);
System.out.println("修改为只关心 WRITE 事件");
printInterestOps("客户端", key);
} else if (len == -1) {
System.out.println("客户端断开: " + session.clientId);
key.cancel();
client.close();
}
}
if (key.isWritable()) {
System.out.println("\n=== 可写事件触发 ===");
SocketChannel client = (SocketChannel) key.channel();
ClientSession session = (ClientSession) key.attachment();
ByteBuffer response = ByteBuffer.wrap(
("Hello, " + session.clientId).getBytes()
);
client.write(response);
System.out.println("发送响应给客户端: " + session.clientId);
// 场景7:写完后,改回只关心读事件
key.interestOps(SelectionKey.OP_READ);
System.out.println("修改为只关心 READ 事件");
printInterestOps("客户端", key);
}
}
}
}
// 工具方法:打印当前关心的事件
private static void printInterestOps(String name, SelectionKey key) {
int ops = key.interestOps();
System.out.println(name + " interestOps: " + ops +
" (二进制: " + Integer.toBinaryString(ops) + ")");
System.out.println("关心的事件:");
if ((ops & SelectionKey.OP_READ) != 0) {
System.out.println(" - OP_READ (可读)");
}
if ((ops & SelectionKey.OP_WRITE) != 0) {
System.out.println(" - OP_WRITE (可写)");
}
if ((ops & SelectionKey.OP_ACCEPT) != 0) {
System.out.println(" - OP_ACCEPT (接受连接)");
}
if ((ops & SelectionKey.OP_CONNECT) != 0) {
System.out.println(" - OP_CONNECT (连接完成)");
}
}
}


被折叠的 条评论
为什么被折叠?



