多模型图像特征可视化

        特征图可视化是指将网络中某一层的特征图可视化出来,以便观察网络在不同层次上学到的特征。卷积可视化可以帮助深度学习研究者更好地理解卷积的概念和原理,从而更好地设计和优化卷积神经网络。通过可视化,研究者可以更清晰地看到卷积运算中的每一个步骤,包括输入、卷积核、卷积操作和输出,从而更好地理解卷积的本质和作用。此外,卷积可视化还可以帮助研究者更好地理解卷积神经网络中的高级概念,如池化、批量归一化等,从而更好地设计和优化深度学习模型。

        本文将对卷积过程中的特征图进行可视化,研究图片经过每层卷积特征提取后,会得到哪些特征,对比观察这些特征的变化情况,能够更加熟练掌握特征图可视化操作。

        特征图可视化,这种方法是最简单,输入一张照片,然后把网络中间某层的输出的特征图按通道作为图片进行可视化展示即可。本文使用一张狗的图片进行特征图可视化。为了加快训练速度,本文仅选取一张图片进行特征图可视化,如下图1。

1、ResNet50特征图可视化

1.1 使用IntermediateLayerGetter类
# 返回输出结果
import random
import cv2
import torchvision
import torch
from matplotlib import pyplot as plt
import numpy as np
from torchvision import transforms
from torchvision import models
# 定义函数,随机从0-end的一个序列中抽取size个不同的数
def random_num(size, end):range_ls = [i for i in range(end)]num_ls = []for i in range(size):num = random.choice(range_ls)range_ls.remove(num)num_ls.append(num)return num_lspath = "./input/dog.png"
transformss = transforms.Compose([transforms.ToTensor(),transforms.Resize((224, 224)),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
# 注意如果有中文路径需要先解码,最好不要用中文
img = cv2.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 转换维度
img = transformss(img).unsqueeze(0)
model = models.resnet50(pretrained=True)
new_model = torchvision.models._utils.IntermediateLayerGetter(model, {'layer1': '1', 'layer2': '2', "layer3": "3"})
out = new_model(img)
tensor_ls = [(k, v) for k, v in out.items()]
# 这里选取layer2的输出画特征图
v = tensor_ls[1][1]
# 选择目标卷积层
target_layer = model.layer2[0]
"""
如果要选layer3的输出特征图只需把第一个索引值改为2,即:
v=tensor_ls[2][1]
只需把第一个索引更换为需要输出的特征层对应的位置索引即可
"""
# 取消Tensor的梯度并转成三维tensor,否则无法绘图
v = v.data.squeeze(0)
print(v.shape)  # torch.Size([512, 28, 28])
# 随机选取25个通道的特征图
channel_num = random_num(25, v.shape[0])
plt.figure(figsize=(10, 10))
for index, channel in enumerate(channel_num):ax = plt.subplot(5, 5, index + 1, )plt.imshow(v[channel, :, :])
plt.savefig("./output/feature.jpg", dpi=600)

for index, channel in enumerate(channel_num):ax = plt.subplot(5, 5, index + 1, )plt.imshow(v[channel, :, :])
plt.savefig("./output/feature.jpg", dpi=600,cmap="gray") #加上cmap参数就可以显示灰度图

IntermediateLayerGetter有一个不足就是它不能获取二级层的输出,比如ResNet的layer2,他不能获取layer2里面的卷积的输出。

模型地址:C:\Users\Administrator\.cache\torch\hub\checkpoints

1.2 使用hook机制(推荐)
# 返回输出结果
import randomimport cv2
import torchvision
import torch
from matplotlib import pyplot as plt
import numpy as np
from torchvision import transforms
from torchvision import models# 定义函数,随机从0-end的一个序列中抽取size个不同的数
def random_num(size, end):range_ls = [i for i in range(end)]num_ls = []for i in range(size):num = random.choice(range_ls)range_ls.remove(num)num_ls.append(num)return num_lspath = "./input/dog.png"
transformss = transforms.Compose([transforms.ToTensor(),transforms.Resize((224, 224)),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])# 注意如果有中文路径需要先解码,最好不要用中文
img = cv2.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 转换维度
img = transformss(img).unsqueeze(0)model = models.resnet50(pretrained=True)# 选择目标层
target_layer = model.layer2[2]
# 注册钩子函数,用于获取目标卷积层的输出
outputs = []
def hook(module, input, output):outputs.append(output)hook_handle = target_layer.register_forward_hook(hook)_ = model(img)v = outputs[-1]"""
如果要选layer3的输出特征图只需把第一个索引值改为2,即:
v=tensor_ls[2][1]
只需把第一个索引更换为需要输出的特征层对应的位置索引即可
"""
# 取消Tensor的梯度并转成三维tensor,否则无法绘图
v = v.data.squeeze(0)print(v.shape)  # torch.Size([512, 28, 28])# 随机选取25个通道的特征图
channel_num = random_num(25, v.shape[0])
plt.figure(figsize=(10, 10))
for index, channel in enumerate(channel_num):ax = plt.subplot(5, 5, index + 1, )plt.imshow(v[channel, :, :])
plt.savefig("./output/feature2.jpg", dpi=600)

for index, channel in enumerate(channel_num):ax = plt.subplot(5, 5, index + 1, )plt.imshow(v[channel, :, :],cmap="gray")
plt.savefig("./output/feature2.jpg", dpi=600)

2、AlexNet特征图可视化

上面的ResNet用的是预训练模型,这里我们自己构建AlexNet

from torch import nnclass AlexNet(nn.Module):def __init__(self):super(AlexNet, self).__init__()self.conv1 = nn.Sequential(nn.Conv2d(3, 96, 11, 4, 2),nn.ReLU(),nn.MaxPool2d(3, 2),)self.conv2 = nn.Sequential(nn.Conv2d(96, 256, 5, 1, 2),nn.ReLU(),nn.MaxPool2d(3, 2),)self.conv3 = nn.Sequential(nn.Conv2d(256, 384, 3, 1, 1),nn.ReLU(),nn.Conv2d(384, 384, 3, 1, 1),nn.ReLU(),nn.Conv2d(384, 256, 3, 1, 1),nn.ReLU(),nn.MaxPool2d(3, 2))self.fc=nn.Sequential(nn.Linear(256*6*6, 4096),nn.ReLU(),nn.Dropout(0.5),nn.Linear(4096, 4096),nn.ReLU(),nn.Dropout(0.5),nn.Linear(4096, 100),)def forward(self, x):x=self.conv1(x)x=self.conv2(x)x=self.conv3(x)output=self.fc(x.view(-1, 256*6*6))return outputmodel=AlexNet()
for name in model.named_children():print(name[0])
#同理先看网络结构
#输出
path = "./input/dog.png"
transformss = transforms.Compose([transforms.ToTensor(),transforms.Resize((224, 224)),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])#注意如果有中文路径需要先解码,最好不要用中文
img = cv2.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)#转换维度
img = transformss(img).unsqueeze(0)model = AlexNet()## 修改这里传入的字典即可new_model = torchvision.models._utils.IntermediateLayerGetter(model, {"conv1":1,"conv2":2,"conv3":3})
out = new_model(img)tensor_ls=[(k,v) for  k,v in out.items()]#选取conv2的输出
v=tensor_ls[1][1]#取消Tensor的梯度并转成三维tensor,否则无法绘图
v=v.data.squeeze(0)print(v.shape)  # torch.Size([512, 28, 28])#随机选取25个通道的特征图
channel_num = random_num(25,v.shape[0])
plt.figure(figsize=(10, 10))
for index, channel in enumerate(channel_num):ax = plt.subplot(5, 5, index+1,)plt.imshow(v[channel, :, :])  # 灰度图参数cmap="gray"
plt.savefig("./output/feature.jpg",dpi=300)

也就是说AlexNet这里分为了4部分,三个卷积和一个全连接(其实就是我们自己定义的foward前向传播),我们想要哪层的输出改个字典就好了

new_model = torchvision.models._utils.IntermediateLayerGetter(model, {“conv1”:1,“conv2”:2,“conv3”:3})

plt.imshow(v[channel, :, :],cmap=“gray”) 加上cmap参数就可以显示灰度图

for index, channel in enumerate(channel_num):ax = plt.subplot(5, 5, index+1,)plt.imshow(v[channel, :, :],cmap="gray")  # 灰度图参数cmap="gray"
plt.savefig("./output/feature.jpg",dpi=300)

3、简易网络特征图可视化

from torch import nn
import torch
from torch.nn import functional as F
import cv2
from matplotlib import pyplot as pltclass Net(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(3, 6, 5)self.pool1 = nn.MaxPool2d(4, 4)self.conv2 = nn.Conv2d(6, 16, 5)self.pool2 = nn.MaxPool2d(4, 4)def forward(self, x):output = []  # 保存特征层的列表,在最后的时候返回x = self.conv1(x)output.append(x)  # 加入output中x = F.relu(x)output.append(x)x = self.pool1(x)output.append(x)x = self.conv2(x)output.append(x)x = F.relu(x)output.append(x)x = self.pool2(x)return x, outputnet = Net()path = "./input/dog.png"
img = cv2.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = torch.tensor(img, dtype=torch.float32) / 255.0  # Normalize to [0, 1]
img = img.unsqueeze(0)  # 添加批次维度
img = img.permute(0, 3, 1, 2)  # 更改尺寸顺序
print(img.shape)_, output = net(img)# 打印并可视化图层
for layer in output:fig = plt.figure(figsize=(15, 5))  # 定义图形大小for i in range(layer.shape[1]):  # 迭代通道ax = fig.add_subplot(1, layer.shape[1], i + 1, xticks=[], yticks=[])  # 1 行,通道列数plt.imshow(layer[0, i].detach().numpy(), cmap="gray")  # 将通道显示为灰度图像plt.show()

4、Yolo网络特征图可视化

import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torch.nn import functional as F
from torchvision import transforms
from PIL import Image
from net.yolov4 import YoloBody  # Assuming YoloBody is defined in yolov4.py
import cv2# Define the hook function to get activations
activation = {}  # Save the obtained outputdef get_activation(name):def hook(model, input, output):activation[name] = output.detach()return hookdef main():anchors_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]]# Call the modelmodel = YoloBody(3, 20)  # Assuming 3 anchors and 20 classesmodel.eval()# 输出网络for name, param in model.named_parameters():print(name, param.shape)# 在特定层上前向钩子model.backbone.conv1.conv.register_forward_hook(get_activation('conv'))# 虚拟输入图像x = torch.randn(1, 3, 416, 416)# 获取模型预测结果(可能需要根据模型结构进行修改)_, _, _ = model(x)# 从挂钩中读取激活信息bn = activation['conv']print(bn.shape)# 显示特征图plt.figure(figsize=(12, 12))for i in range(bn.shape[1]):plt.subplot(8, 8, i + 1)plt.imshow(bn[0, i, :, :].cpu().numpy(), cmap='gray')plt.axis('off')plt.show()return 0if __name__ == '__main__':transform = transforms.Compose([transforms.Resize([416, 416]),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])# 加载并预处理图像img_path = './input/dog.png'img = Image.open(img_path)input_img = transform(img).unsqueeze(0)print(input_img.shape)main()

报错net的问题解决方案:zhanggong0564/YOLOV3 at d94c249d8d9ec532d2f6cff1a2be183a987c2c7f (github.com)
解决文件在YOLOV3/nets,将nets文件copy放在项目根目录下面,重命名为net即可解决。

from pathlib import Path
from pooch.utils import LOGGER
import torch
import math
import matplotlib.pyplot as plt
import numpy as npdef feature_visualization(x, module_type, stage, n=2, save_dir=Path('./output')):"""x:              Features to be visualizedmodule_type:    Module typestage:          Module stage within the modeln:              Maximum number of feature maps to plotsave_dir:       Directory to save results"""print(f"save_dir: {save_dir}")if 'Detect' not in module_type:batch, channels, height, width = x.shape  # batch, channels, height, widthprint(f"x.shape: {x.shape}")if height > 1 and width > 1:save_dir.mkdir(parents=True, exist_ok=True)  # Ensure the directory existsf = save_dir / f"stage{stage}_{module_type.split('.')[-1]}_features.png"  # filenameprint(f"f: {f}")blocks = torch.chunk(x[0].cpu(), channels, dim=0)  # select batch index 0, block by channelsn = min(n, channels)  # number of plotsfig, ax = plt.subplots(math.ceil(n / 2), 2, tight_layout=True)  # 8 rows x n/8 colsax = ax.ravel()plt.subplots_adjust(wspace=0.05, hspace=0.05)for i in range(n):ax[i].imshow(blocks[i].squeeze(), cmap='gray')  # Add cmap='gray'ax[i].axis('off')LOGGER.info(f'Saving {f}... ({n}/{channels})')plt.savefig(f, dpi=300, bbox_inches='tight')plt.close()np.save(str(f.with_suffix('.npy')), x[0].cpu().numpy())  # npy save

5、VGGNet特征图可视化

VGG19本是用来进行分类的,进行可视化和用作VGG loss 自然也就是用到全连接层之前的内容,先要了解VGG19全连接层之前的结构

from torchvision.models import vgg19,vgg16
import torch
import torch.nn.functional as F
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")#对于torch 的models就已经包含了vgg19
#在这里pretrained=False,在下面我自己进行了模型预加载,选择Ture,则电脑联网时
#自动会完成下载
vgg_model = vgg19(pretrained=False).features[:].to(device)
print(vgg_model)
加载vgg model
import torch
from torchvision import models# 定义 VGG19 模型
vgg_model = models.vgg19(pretrained=True)# 将权重保存到文件中
torch.save(vgg_model.state_dict(), 'vgg19-dcbb9e9d.pth')# 以 strict=False 加载预训练的权重
vgg_model.load_state_dict(torch.load('vgg19-dcbb9e9d.pth'), strict=False)
vgg_model.eval()# 将所有参数的 requires_grad 设置为 False
for param in vgg_model.parameters():param.requires_grad = False

模型地址 C:\Users\Administrator/.cache\torch\hub\checkpoints\vgg19-dcbb9e9d.pth

可视化
import numpy as np
import matplotlib.pyplot as plt
def get_row_col(num_maps):# 根据特征地图的数量计算子地图行数和列数的函数rows = int(np.sqrt(num_maps))cols = int(np.ceil(num_maps / rows))return rows, cols
#输入的应该时feature_maps.shape = (H,W,Channels)
#下图对relu1_2 进行了可视化,有64channels,拼了个了8*8的图
def visualize_feature_map(feature_maps):#创建特征子图,创建叠加后的特征图#param feature_batch: 一个卷积层所有特征图# np.squeeze(feature_maps, axis=0)print("visualize_feature_map shape:{},dtype:{}".format(feature_maps.shape,feature_maps.dtype))num_maps = feature_maps.shape[2]feature_map_combination = []plt.figure(figsize=(8, 7))# 取出 featurn map 的数量,因为特征图数量很多,这里直接手动指定了。#num_pic = feature_map.shape[2]row, col = get_row_col(num_maps)# 将 每一层卷积的特征图,拼接层 5 × 5for i in range(0, num_maps):feature_map_split = feature_maps[:, :, i]feature_map_combination.append(feature_map_split)plt.subplot(row, col, i+1)plt.imshow(feature_map_split)plt.axis('off')plt.savefig('./output/relu1_2_feature_map.png') # 保存图像到本地plt.show()
feature_maps = np.random.rand(8, 8, 64)
visualize_feature_map(feature_maps)

参考文献:

基于pytorch使用特征图输出进行特征图可视化-CSDN博客

【Pytorch】 特征图的可视化_pytorch 特征图可视化-CSDN博客

深度学习——pytorch卷积神经网络中间特征层可视化_pytorch获取卷积神经网络中间层-CSDN博客

Pytorch 深度学习特征图可视化——(Yolo网络、rcnn)_basicconv-CSDN博客

zhanggong0564/YOLOV3 at d94c249d8d9ec532d2f6cff1a2be183a987c2c7f (github.com) 

计算机视觉特征图可视化与注意力图可视化(持续更新)-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/630155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[SS]语义分割——基础知识

语义分割前言 一、定义 1、概念 语义分割(Semantic Segmentation)是计算机视觉中的一项任务,目标是将图像中的每个像素按其语义类别进行分类。与传统的目标检测不同,语义分割对图像中的每个像素都进行分类,而不是只…

Vue 如何把computed里的逻辑提取出来

借用一下百度的ai 项目使用&#xff1a; vue 文件引入 <sidebar-itemv-for"route in routes":key"route.menuCode":item"route":base-path"route.path"click"onColor"/>import { handleroutes } from "./handle…

牛客-寻找第K大、LeetCode215. 数组中的第K个最大元素【中等】

文章目录 前言牛客-寻找第K大、LeetCode215. 数组中的第K个最大元素【中等】题目及类型思路思路1&#xff1a;大顶堆思路2&#xff1a;快排二分随机基准点 前言 博主所有博客文件目录索引&#xff1a;博客目录索引(持续更新) 牛客-寻找第K大、LeetCode215. 数组中的第K个最大元…

C#: CRC8,CRC16,CRC32 校验代码

说明&#xff1a;CRC即循环冗余校验码&#xff08;Cyclic Redundancy Check&#xff09;&#xff1a;是数据通信领域中最常用的一种查错校验码&#xff0c;其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查&#xff08;CRC&#xff09;是一种数据传输检错功能&…

统计学-R语言-5.1

文章目录 前言随机性和规律性概率变量的分布离散型--二项、泊松、几何二项分布几何分布泊松分布 连续型--均匀、正态均匀分布正态分布 其它统计分布--χ2分布、t分布、F分布χ2分布t分布F分布 练习 前言 从本篇文章开始介绍有关概率与分布的介绍。 随机性和规律性 当不能预测…

嵌入式-Stm32-江科大基于标准库的GPIO的八种模式

文章目录 一&#xff1a;GPIO输入输出原理二&#xff1a;GPIO基本结构三&#xff1a;GPIO位结构四&#xff1a;GPIO的八种模式道友&#xff1a;相信别人&#xff0c;更要一百倍地相信自己。 &#xff08;推荐先看文章&#xff1a;《 嵌入式-32单片机-GPIO推挽输出和开漏输出》…

vue-cli解决跨域

在vue.config.js中 找到devServer 在devServer中创建proxy代理 proxy:{ path&#xff08;路径中包含这个path就会导航到target的目标接口&#xff09;&#xff1a;{ target:"目标接口" } } 例&#xff1a; 1 同源策略只针对于浏览器&#xff0c;代理服务器到后端接…

操作系统课程设计-Linux 进程控制

目录 前言 1 实验题目 2 实验目的 3 实验内容 3.1 进程的创建 3.1.1 步骤 3.1.2 关键代码 3.2 子进程执行新任务 3.2.1 步骤 3.2.2 关键代码 4 实验结果与分析 4.1 进程的创建 4.2 子进程执行新任务 5 代码 5.1 进程的创建 5.2 子进程执行新任务 前言 本实验为课…

Electron+React项目打包踩坑记录

首先&#xff0c;如何打包 写下本文的时间是 2024/01/16&#xff0c;搜索了网络上 ElectronReact 的打包方式&#xff0c;中间行不通&#xff0c;本文采用的方式是记录本文时 Electron 快速入门(https://www.electronjs.org/zh/docs/latest/tutorial/quick-start)记录的打包方式…

Stream API 函数式编程 - 告别for循环,代码竟能写的如此优雅?

目录 一、Stream API 函数式编程 1.1、Stream 简介 a&#xff09;为什么引入 Stream&#xff1f;Stream 的出现就是为了让关于集合的操作更加简单&#xff1a; b&#xff09;Stream 的特性&#xff1a; c&#xff09;对stream的操作分为为两类&#xff0c;中间操作 和 结束…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-3 textarea

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>textarea</title> </head><body> <h2>多行文本框:</h2> <!--textarea&#xff08;文本域&#xff09;cols(列) rows(行)--> …

Template Engine-06-模板引擎 Handlebars 入门介绍

拓展阅读 java 表达式引擎 logstash 日志加工处理-08-表达式执行引擎 AviatorScriptMVELOGNLSpELJEXLJUELJanino QLExpress 阿里表达式引擎系统学习 什么是 Handlebars&#xff1f; Handlebars 是一种简单的模板语言。 它使用模板和输入对象生成 HTML 或其他文本格式。Ha…

go语言(一)----声明常量

package mainimport ("fmt""time" )func main() {fmt.Print("hello go!")time.Sleep(1 * time.Second)}运行后&#xff0c;结果如下&#xff1a; 1、golang表达式中&#xff0c;加&#xff1b;和不加&#xff1b;都可以 2、函数的{和函数名一…

【Qt之模型视图】1. 模型和视图架构

1. 模型/视图架构是什么及有什么用 MVC&#xff08;Model-View-Control&#xff09;是一种源自Smalltalk的设计模式&#xff0c;通常用于构建用户界面。 MVC由三种类型的对象组成。模型是应用对象&#xff0c;用来表示数据&#xff1b;视图是模型的用户界面&#xff0c;用来显…

【征服redis5】redis的Redisson客户端

目录 1 Redisson介绍 2. 与其他Java Redis客户端的比较 3.基本的配置与连接池 3.1 依赖和SDK 3.2 配置内容解析 4 实战案例&#xff1a;优雅的让Hash的某个Field过期 5 Redisson的强大功能 1 Redisson介绍 Redisson 最初由 GitHub 用户 “mrniko” 创建&#xff0c;并在…

瑞_Java开发手册_(七)设计规约

文章目录 设计规约的意义设计规约 &#x1f64a;前言&#xff1a;本文章为瑞_系列专栏之《Java开发手册》的设计规约篇。由于博主是从阿里的《Java开发手册》学习到Java的编程规约&#xff0c;所以本系列专栏主要以这本书进行讲解和拓展&#xff0c;有需要的小伙伴可以点击链接…

Java数据结构实现数组(配套习题)

数据结构 数组 一组相同数据类型的集合 特点 数组在内存中是连续分配的创建时要指明数组的大小数组名代表首地址,索引从0开始,到数组的长度-1数组一旦创建好,大小不可以改变使用索引 获取索引位置的值 arr[index]修改 arr[index] val删除 (假删除)遍历,将数组中的元素,依次…

在全志T113-i平台上实现H.265视频解码步骤详解

H.265&#xff0c;也被称为HEVC(HighEfficiency Video Coding)&#xff0c;作为H.264的继任者&#xff0c;提供了更好的视频压缩和更高的视频质。H.265通过引入更多先进的编码技术&#xff0c;如更强大的运动估计和更高效的变换编码&#xff0c;对比H.264进行了改进。这些改进使…

【latex】参考文献排版前移,在最前面引用\usepackage{url}

【LaTeX】参考文献排版前移&#xff0c;在最前面引用\usepackage{url} 写在最前面完整解决步骤请教申申latex编译报错解决方案 写在最前面 参考文献从21开始排版前移了 解决方案&#xff1a;在最前面加一行 \usepackage{url}完整解决步骤 请教申申 申申yyds&#xff01;&am…

Java NIO (一)简介

1 NIO简介 在1.4版本之前&#xff0c;Java NIO类库是阻塞IO&#xff0c;从1.4版本开始&#xff0c;引进了新的异步IO库&#xff0c;被称为Java New IO类库&#xff0c;简称为Java NIO。New IO类库的目的 就是要让Java支持非阻塞IO。 Java NIO类库包含三个核心组件&#xff1a; …