Java NIO详解

一、概念

NIO, 即new io,也叫非阻塞io

二、NIO三个核心组件:

  • Buffer数据缓冲区
  • Channel通道
  • Selector选择器
1、Buffer缓冲区

缓冲区本质上是一个可以存放数据的内存块(类似数组),可以在这里进行数据写入和读取。此内存块包含在NIO Buffer对象中,并且有一些列API,可以对内存块中的数据进行操作,相比较直接对数组的操作,Buffer API更加容易操作和管理。

1) Buffer进行数据写入与读取的步骤
  • 将数据写入缓冲区
  • 切换为读取模式,调用API: buffer.flip(),此时position位置会变为0,即从头开始读
  • 读取缓冲区数据
  • 调用API: buffer.cler()清除整个缓冲区数据或者buffer.compact()清除已读的数据
2) Buffer工作原理
三个重要属性
  • capacity容量: 即内存块的大小
  • position位置: 内存块当前数据操作的位置,即读数据的位置或者写数据的位置
  • limit 限制: 写入模式,等于capacity。 读取模式,limit等于写入的数据量

image.png

3) ByteBuffer内存类型

ByteBuffer为性能关键型代码提供了"直接内存(堆外内存,off-heap memory)" 和 "非直接内存(堆内内存,on-heap memory)"两种实现

// 堆内内存 ByteBuffer buffer = ByteBuffer.allocate(4);// 堆外内存 ByteBuffer buffer = ByteBuffer.allocateDirect(4);

堆外内存好处:

1、进行网络IO或者文件IO时,堆外内存会比堆内内存少一次拷贝。(file/socket --- OS memory --- jvm heap), GC会移动对象内存,在写file或socket的过程中,JVM的实现中,会先把数据复制到堆外,再进行写入。

2、GC范围之外,降低了GC压力,但实现了自动管理。 DirectByteBuffer中有一个Cleaner对象(PhantomReference),Cleaner被GC前会执行clean方法,触发DirectByteBuffer中定义的Deallocator

建议: 

1、性能确实可观的时候才去使用,分配给大型、长寿命(网络传输、文件读写场景)

2、通过虚拟机参数MaxDirectMemortSieze限制堆外内存大小,防止耗尽整个机器的内存。

什么是堆外内存?

正常的java非空对象,都是在jvm中的,而且受垃圾收集器的管理,即堆内内存(on-heap memory) 在某种特定的场景下,例如在文件读取写入的时候,如果一份数据写在堆内存的某个位置,假如此时在位置A,而此时jvm垃圾回收器进行了一次垃圾回收,此时的数据在内存中的位置可能会发生变化,变成了位置B,操作系统在读取这个数据的时候,通过A去查找,会导致读取到错误的数据,这种场景的解决方案: 堆外内存(off-heap memory),jvm在会先将写入的数据,复制一份到堆外,操作系统直接从堆外读取。

示例:

// 初始化buffer,此时每个元素会被初始化为0
ByteBuffer buffer = ByteBuffer.allocate(4);
System.out.println(String.format("初始化: capacity容量: %s, limit限制: %s, position位置: %s",buffer.capacity(), buffer.limit(), buffer.position()));
>> 输出: 初始化: capacity容量: 4, limit限制: 4, position位置: 0// 写入3个字节数据
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 2);
System.out.println(String.format("初始化: capacity容量: %s, limit限制: %s, position位置: %s",buffer.capacity(), buffer.limit(), buffer.position()));
>> 输出: 初始化: capacity容量: 4, limit限制: 4, position位置: 3//读数据, 不用buffer.flip()切换,position为4,应该是读到第4个字节的数据,但第4个字节没有,则默认为0,即buffer中有了4个数据, 所以读取模式下,limit也为4
byte b1 = buffer.get();
System.out.println(String.format("写入两个字节数据后,capacity容量: %s,limit限制:%s, position位置:%s",buffer.capacity(), buffer.limit(), buffer.position()));
System.out.println(b1);
// >> 输出 capacity容量: 4,limit限制:4, position位置:4
// >> 0// 调用buffer.flip(), 将position变为0
buffer.flip();
System.out.println(String.format("capacity容量: %s,limit限制:%s, position位置:%s",buffer.capacity(), buffer.limit(), buffer.position()));
// >> 输出 capacity容量: 4,limit限制:4, position位置:0// 从position=1开始读取数据
byte b2 = buffer.get();
System.out.println(String.format("capacity容量: %s,limit限制:%s, position位置:%s",buffer.capacity(), buffer.limit(), buffer.position()));
System.out.println(b2);
// >> 输出 capacity容量: 4,limit限制:4, position位置:1
// >> 1// 清除已经读过的数据, 转为写入模式
buffer.compact();
System.out.println(String.format("capacity容量: %s,limit限制:%s, position位置:%s",buffer.capacity(), buffer.limit(), buffer.position()));// >> 输出 capacity容量: 4,limit限制:4, position位置:3//清除所有数据
buffer.clear();
System.out.println(String.format("capacity容量: %s,limit限制:%s, position位置:%s",buffer.capacity(), buffer.limit(), buffer.position()));// >> 输出 capacity容量: 4,limit限制:4, position位置:0

buffer.compact()方法: 将buffer的position设置为capicity - buffer中剩余未读取的数据索引, 即: position = capicity - compact之前的position

buffer.put((byte)1);
buffer.put((byte)2);
System.out.println();
System.out.format("capacity: %s, limit: %s, position: %s", buffer.capacity(), buffer.limit(), buffer.position());
// capacity: 4, limit: 4, position: 2buffer.compact();
System.out.println();
System.out.format("capacity: %s, limit: %s, position: %s", buffer.capacity(), buffer.limit(), buffer.position());
// capacity: 4, limit: 4, position: 2byte ss = buffer.get();
System.out.println();
System.out.println(ss);
System.out.format("capacity: %s, limit: %s, position: %s", buffer.capacity(), buffer.limit(), buffer.position());
// ss = 0
// capacity: 4, limit: 4, position: 3

2、Channel通道

buffer缓冲区数据传输的渠道,类似BIO中的sokcet+io流

image.png

1)客户端通道: SocketChannel

NIO的客户端通道,SocketChannel用于建立TCP网络连接,类似jav.net.Socket

示例:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;public class NIOClient {public static void main(String[] args) throws IOException {SocketChannel client = SocketChannel.open();// 设置SocketChannel为非阻塞模式,默认的socket相关都是阻塞的client.configureBlocking(false);client.connect(new InetSocketAddress("127.0.0.1", 8888));// 没连接上则一直等待while (!client.finishConnect()){Thread.yield();}Scanner scanner = new Scanner(System.in);System.out.println("连接服务端成功,请输入需要发送的内容:" );// 发送内容String msg = scanner.nextLine();// 将数据包装为bufferByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());// 是否还有数据剩余,有就继续写while (buffer.hasRemaining()) {client.write(buffer);}// 读取服务端响应数据ByteBuffer responseBuffer = ByteBuffer.allocate(1024);while (client.isOpen() && client.read(responseBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束// (此处做一个简单的判断: 超过0字节就认为请求结束了)// position > 0, 代表该请求有数据在写入,否则一直阻塞等待if (responseBuffer.position() > 0)break;}responseBuffer.flip();byte[] content = new byte[responseBuffer.limit()];ByteBuffer buffer1 = responseBuffer.get(content);System.out.println("收到服务端响应: " + new String(content));scanner.close();client.close();}
}

2)服务端通道: ServerSocketChannel

NIO的服务端通道,类似java.net.ServerSocket

示例:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;public class NIOServer {public static void main(String[] args) throws IOException {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.bind(new InetSocketAddress(8888));System.out.println("服务端启动成功!等待新连接...");while (true) {// 在nio中,accept方法是非阻塞的,如果没有接受到请求,会返回nullSocketChannel accept = serverSocketChannel.accept();// 有新的连接,读取数据if (accept != null) {System.out.println("收到新连接:" + accept.getRemoteAddress());accept.configureBlocking(false);// 读取数据ByteBuffer requestBuffer = ByteBuffer.allocate(1024);while (accept.isOpen() && accept.read(requestBuffer) != -1) {// 长连接情况下,需要手动判断数据有没有读取结束// (此处做一个简单的判断: 超过0字节就认为请求结束了)// position > 0, 代表该请求有数据在写入,否则一直阻塞等待if (requestBuffer.position() > 0)break;}// 没数据了,不再继续后面的操作if (requestBuffer.position() == 0)continue;requestBuffer.flip();byte[] content = new byte[requestBuffer.limit()];requestBuffer.get(content);System.out.println("收到数据来自:" + accept.getRemoteAddress());System.out.println(new String(content));// 响应结果String response = "HTTP/1.1 200 OK \r\n" +"Content-Length: 11 \r\n\r\n" +"Hello World";ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());while (buffer.hasRemaining()) {accept.write(buffer);}}}}
}
3、Selector选择器

Selector是一个Java NIO组件,可以监听一个或多个NIO通道,并根据绑定的事件,确定哪些通道已准备好进行相应的操作,如某个通道注册了读取事件,Selector就可以确定该通道什么时候可以进行读取。

实现了单个线程可以管理多个通道,从而管理多个网络连接,节约内存开销。

一个线程使用Selector监听多个channel的不同事件,4个事件分别对应SelectionKey的4个常量:

  • Connect连接事件(SelectionKey.OP_CONNECT)
  • Accept准备就绪事件(SelectionKey.OP_ACCEPT)
  • Read读取事件(SelectionKey.OP_READ)
  • Write写入事件(SelectionKey.OP_WRITE)

image.png

实现一个线程处理多个通道的核心概念理解: 事件驱动机制 非阻塞的网络通道下,开发者通过Selector注册对于通道感兴趣的事件类型,线程通过监听时间来处理相应的代码执行(拓展:更底层是操作系统的多路复用机制)

示例:

import java.io.IOException;
import java.net.InetSocketAddress;
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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;public class NIOServer2 {private static List<SocketChannel> channelList = new ArrayList<>();public static void main(String[] args) throws IOException {// 1、创建网络服务端ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);// 2、构建Selector一个选择器,将channel注册上去Selector selector = Selector.open();// 将serverSocketChannel注册到selector, 注册感兴趣事件:ACCEPT事件SelectionKey register = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, serverSocketChannel);// 3、绑定端口serverSocketChannel.bind(new InetSocketAddress(8888));System.out.println("服务端启动成功!等待新连接...");while (true) {// 不再轮询通道,改用下面轮询事件的方式.select方法有阻塞效果,直到有事件通知才会有返回selector.select();// 获取选择器事件集合Set<SelectionKey> selectionKeys = selector.selectedKeys();// 遍历Iterator<SelectionKey> iter = selectionKeys.iterator();while (iter.hasNext()) {SelectionKey next = iter.next();iter.remove();// 关注READ和ACCEPT事件if (next.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) next.attachment();// 将客户端注册到Selector上,并关注READ事件SocketChannel clientChannel = server.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ, clientChannel);System.out.println("收到新连接: " + clientChannel.getRemoteAddress());}if (next.isReadable()) {SocketChannel socketChannel = (SocketChannel) next.attachment();ByteBuffer buffer = ByteBuffer.allocate(1024);while (socketChannel.isOpen() && socketChannel.read(buffer) != -1) {if (buffer.position() > 0)break;}if (buffer.position() == 0)continue;buffer.flip();byte[] content = new byte[buffer.limit()];buffer.get(content);System.out.println("收到消息,来自:"+ socketChannel.getRemoteAddress());System.out.println(new String(content));String res = "HTTP/1.1 200 OK\r\n" +"Content-Length: 11\r\n\r\n" +"Hello World";ByteBuffer byteBuffer = ByteBuffer.wrap(res.getBytes());while (byteBuffer.hasRemaining()) {socketChannel.write(byteBuffer);}// 取消时间订阅next.cancel();}}}}
}

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

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

相关文章

git 修改历史 commit message

目录 1&#xff0c;修改当前的2&#xff0c;修改历史的1&#xff0c;先查看 log2&#xff0c;开始修改 3&#xff0c;其他注意点1&#xff0c;中途不想修改了2&#xff0c;commit ID 会发生变化3&#xff0c;推送远程4&#xff0c;精准定位 1&#xff0c;修改当前的 直接使用下…

Linux实现m4a格式转换为wav格式

需要在linux上安装ffmpeg 参考博客 Linux上安装ffmpeg修改环境变量【这一点很重要&#xff0c;自己因为没有添加环境变量&#xff0c;捣鼓了很长时间】 将ffmpeg的绝对路径添加到 PATH 环境变量中&#xff0c;以让系统能找到ffmpeg的安装路径。 # /home//project/ffmpeg-6.1-a…

三个表的联合查询的场景分析-场景4:c表维护a和b表的id关联关系(一对多)

基础SQL演练&#xff0c;带详细分析&#xff0c;笔记和备忘。 目录 背景介绍 表数据 需求1&#xff1a;查询g表所有记录&#xff0c;以及关联的h的id 需求2&#xff1a;在需求1基础上&#xff0c;查出关联的h的其它字段&#xff08;name&#xff09; 需求3&#xff1a;在需…

【排序算法】希尔排序

文章目录 &#x1f4dd;希尔排序( 缩小增量排序 )&#x1f320;分组思想&#x1f320;缩小增量的过程&#x1f320; 排序步骤&#x1f309;希尔排序的特性总结&#xff1a; &#x1f6a9;总结 &#x1f4dd;希尔排序( 缩小增量排序 ) 希尔排序是一种经典的排序算法&#xff0c…

Java复习第十三天学习笔记(HTML),附有道云笔记链接

【有道云笔记】十三 3.29 HTML https://note.youdao.com/s/Ru3zoNqM 一、基本标签 HTML:超文本标记语言 定义页面结构 CSS&#xff1a;层叠样式表 页面显示的样式、排版 BootStrap JS: JavaScript 界面交互(动态交互、逻辑) JQuery <!DOCTYPE html> <html> &l…

体育馆场地预约系统项目管理

1 前言 体育馆作为提供体育活动设施的重要场所&#xff0c;其使用和管理效率对于满足公众需求、提高体育活动质量具有重要意义。然而&#xff0c;传统体育馆场地预约方式仍然存在流程繁琐、效率低下等问题&#xff0c;已无法满足现代社会的需求。旨在提高体育馆的预约和管理效率…

WebGIS开发

1.准备工作 高德开发API注册账号&#xff0c;创建项目拿到key和密钥 高德key 2.通过JS API引入高德API <html><head><meta charset"utf-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><metaname&quo…

人工智能 框架 paddlepaddle 飞桨 使用指南 使用例子 线性回归模型demo 1

安装过程&使用指南&线性回归模型 使用例子 本来预想 是安装 到 conda 版本的 11.7的 但是电脑没有gpu 所以 安装过程稍有变动,下面简单讲下 conda create -n paddle_env117 python=3.9 由于想安装11.7版本 py 是3.9 所以虚拟环境名称也是 paddle_env117 activa…

HTTP 与 HTTPS 的区别

基本概念 HTTP&#xff08;HyperText Transfer Protocol&#xff1a;超文本传输协议&#xff09;是一种应用层协议&#xff0c;主要用于在网络上进行信息的传递&#xff0c;特别是用于Web浏览器和服务器之间的通信。 它使用明文方式发送数据&#xff0c;这意味着传输的内容可…

C++中的STL简介与string类

目录 STL简介 STL的版本 STL的六大组件 string类 标准库中的string类 string类的常用接口 string类对象对容量的操作 size()函数与length()函数 capacity()函数 capacity的扩容方式 reserve()函数 resize()函数 string类对象的操作 push_back()函数 append()函数 operator()函数…

LLM推理入门指南②:深入解析KV缓存

在本系列文章《LLM推理入门指南①&#xff1a;文本生成的初始化与解码阶段》中&#xff0c;作者对Transformer解码器的文本生成算法进行了高层次概述&#xff0c;着重介绍了两个阶段&#xff1a;单步初始化阶段&#xff0c;即提示的处理阶段&#xff0c;和逐个生成补全词元的多…

【Go】六、函数

文章目录 1、函数的定义2、内存分析3、注意点4、函数数据类型5、自定义数据类型&#xff08;起别名&#xff09;6、支持对返回值命名 1、函数的定义 语法&#xff1a; func 函数名&#xff08;形参列表)&#xff08;返回值类型列表&#xff09;{执行语句..return 返回值列…

HarmonyOS实战开发-Stage模型下Ability的创建和使用

介绍 本篇Codelab基于Stage模型&#xff0c;对Ability的创建和使用进行讲解。首先在课程中我们将带领大家使用DevEco Studio创建一个Stage模型Ability&#xff0c;并使用UIAbilityContext启动另一个Ability&#xff0c;然后借助Want&#xff0c;在Ability之间传递参数&#xf…

.Net 知识杂记

记录平日中琐碎的.net 知识点。不定期更新 目标框架名称(TFM) 我们创建C#应用程序时&#xff0c;在项目的工程文件(*.csproj)中都有targetFramework标签&#xff0c;以表示项目使用的目标框架 各种版本的TFM .NET Framework .NET Standard .NET5 及更高版本 UMP等 参考文档&a…

云主机8核16G配置租用优惠价格1198元1年、4688元三年

京东云8核16G租用优惠价格1198元1年、4688元三年&#xff0c;配置为8C16G-270G SSD系统盘-5M带宽-500G月流量&#xff0c;华北-北京地域。京东云8核16G服务器活动页面 atengyun.com/go/jd 京东云8核16G租用优惠价格 京东云&#xff1a;轻量云主机CPU内存&#xff1a;8C16G公网带…

从TCP/IP协议到socket编程详解

​ 我的所有学习笔记&#xff1a;https://github.com/Dusongg/StudyNotes⭐⭐⭐ ​ 文章目录 1 网络基础知识1.1 查看网络信息1.2 认识端口号1.3 UDP1.4 TCP1.4.1 确认应答机制1.4.2 TCP三次握手/四次挥手为什么是三次握手为什么是四次挥手listen 的第二个参数 backlog—— 全…

【MySQL探索之旅】MySQL数据表的增删查改——约束

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

A Little Is Enough: Circumventing Defenses For Distributed Learning

联邦学习的攻击方法&#xff1a;LIE 简单的总结&#xff0c;只是为了能快速想起来这个方法。 无目标攻击 例如总共50个客户端&#xff0c;有24个恶意客户端&#xff0c;那么这个时候&#xff0c;他需要拉拢2个良性客户端 计算 50 − 24 − 2 50 − 24 0.923 \frac{50-24-2}{…

主干网络篇 | YOLOv8更换主干网络之EfficientNet

前言:Hello大家好,我是小哥谈。EfficientNet是一种高效的卷积神经网络架构,由Mingxing Tan和Quoc V. Le在2019年提出,其设计思想是在不增加计算复杂度的情况下提高模型的准确性。它引入了一个称为"复合系数"的概念,该系数用于同时缩放网络的深度、宽度和分辨率。…

Qt 图形视图 /图形视图框架坐标系统的设计理念和使用方法

文章目录 概述Qt 坐标系统图形视图的渲染过程Item图形项坐标系Scene场景坐标系View视图坐标系map坐标映射场景坐标转项坐标视图坐标转图形项坐标图形项之间的坐标转换 其他 概述 The Graphics View Coordinate System 图形视图坐标系统是Qt图形视图框架的重要组成部分&#xf…