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.mzph.cn/news/779829.shtml

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

相关文章

第十四届省赛大学B组(C/C++) 冶炼金属

题目描述 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V&#xff0c;V 是一个正整数&#xff0c;这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X&#xff0c;当普通金属 O 的数目不足 V 时&#xff0c;无法继续…

【Java】LinkedList模拟实现

目录 整体框架IMyLinkedList接口IndexNotLegalException异常类MyLinkedList类成员变量(节点信息)addFirst(头插)addLast(尾插)在指定位置插入数据判断是否存在移除第一个相等的节点移除所有相等的节点链表的长度打印链表释放回收链表 整体框架 IMyLinkedList接口 这个接口用来…

Leetcode704_二分查找

1.leetcode原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2.题目描述 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff…

京东云服务器价格_云主机价格查询系统_2024年京东云优惠活动

2024年京东云服务器优惠价格表&#xff0c;轻量云主机优惠价格5.8元1个月、轻量云主机2C2G3M价格50元一年、196元三年&#xff0c;2C4G5M轻量云主机165元一年&#xff0c;4核8G5M云主机880元一年&#xff0c;游戏联机服务器4C16G配置26元1个月、4C32G价格65元1个月、8核32G费用…

OpenHarmony如何模拟搭建本地http静态服务

简介 本文是在基于OpenHarmony 4.0的基础上&#xff0c;介绍了一种编写一个前端http静态服务的思路. 方案设计 在OpenHarmony上&#xff0c;如果想要访问本地网页。有两种方案 u 方案一&#xff1a;使用file协议&#xff0c;将html放至entry/src/main/resource/rawfile下&#…

构建ELK+Filebeat+kafka+zookeeper大数据日志分析平台

主机IP 角色 所属服务层 部署服务 192.168.11.11 日志生产 采集层 filebeat 192.168.11.12 日志缓存 数据处理层、缓存层 Zookeeperkafkalogstash 192.168.11.13 192.168.11.14 日志展示 持久、检索、展示层 Logstashelasticsearchkibana 数据流向 filebeat--…

#设计模式#3.1 抽象工厂

抽象工厂模式是一种创建型设计模式&#xff0c;它提供了一种方式&#xff0c;可以将一组具有同一主题的单独的工厂封装起来。在抽象工厂模式中&#xff0c;抽象工厂定义了产品的创建接口&#xff0c;但是由子类决定实例化哪一个产品类。 在Python中&#xff0c;你可以使用abc模…

维修西格玛泰克触摸屏ETV0551 Sigmatek ETV 0555工业电脑控制面板

控制面板 ETV 0555 控制面板是自动化编程和可视化的智能终端流程。 过程诊断以及操作和监控自动化程序 使用此终端进行简化。触摸屏作为过程数据和参数的输入介质&#xff1b; 输出是显示在 5.7“ VGA TFT 彩色显示屏上。 使用LSE掩模编辑器&#xff0c;可以在PC上创建图形&…

华清远见STM32U5开发板助力2024嵌入式大赛ST赛道智能可穿戴设备及IOT选题项目开发

第七届&#xff08;2024&#xff09;全国大学生嵌入式芯片与系统设计竞赛&#xff08;以下简称“大赛”&#xff09;已经拉开帷幕&#xff0c;大赛的报名热潮正席卷而来&#xff0c;高校电子电气类相关专业&#xff08;电子、信息、计算机、自动化、电气、仪科等&#xff09;全…

新能源充电桩站场AI视频智能分析烟火检测方案及技术特点分析

新能源汽车充电起火的原因多种多样&#xff0c;涉及技术、设备、操作等多个方面。从技术层面来看&#xff0c;新能源汽车的电池管理系统可能存在缺陷&#xff0c;导致电池在充电过程中出现过热、短路等问题&#xff0c;从而引发火灾。在设备方面&#xff0c;充电桩的设计和生产…

若干比赛题目以及补题复盘

一些补题或者比赛中比较典型的问题 1.Problem - E - Codeforces 这道题有这几个点&#xff1a; 1.答案长度只有可能是因数 2.如果一个字符串k是答案&#xff0c;那么这个字符串满足k*xs,差别不大于一&#xff0c;那么这x个字符串中与k不同的至多有一个&#xff0c;所以判断…

Linux部署Kafka2.8.1

安装Jdk 首先确保你的机器上安装了Jdk&#xff0c;Kafka需要Java运行环境&#xff0c;低版本的Kafka还需要Zookeeper&#xff0c;我此次要安装的Kafka版本为2.8.1&#xff0c;已经内置了一个Zookeeper环境&#xff0c;所以我们可以不部署Zookeeper直接使用。 1、解压Jdk包 t…

接口自动化框架搭建(八):pytest+allure+jenkins接入

1&#xff0c;安装allure插件 2&#xff0c;创建jenkins项目 怎么确定路径&#xff0c;可以查看工作空间&#xff0c;jenkins默认根目录就是工作空间 配置执行用例的命令&#xff0c;可以现在pycharm上试一下&#xff0c;然后在jenkins中配置&#xff1a; 把启动java服务的代…

每天五分钟深度学习:使用神经网络完成人脸的特征点检测

本文重点 我们上一节课程中学习了如何利用神经网络对图片中的对象进行定位,也就是通过输出四个参数值bx、by、bℎ和bw给出图片中对象的边界框。 本节课程我们学习特征点的检测,神经网络可以通过输出图片中对象的特征点的(x,y)坐标来实现对目标特征的识别,我们看几个例子。…

162.乐理基础-和声大调、旋律大调

内容参考于&#xff1a; 三分钟音乐社 上一个内容&#xff1a;161.音程、和弦板块总结、重点、建议 首先需要回忆一下18.调式、自然大调式&#xff08;C大调、D大调。。。&#xff09;与19.音阶是什么、有什么用&#xff0c;在18.调式、自然大调式&#xff08;C大调、D大调。…

【php程序开发从入门到精通】——搭建PHP开发环境

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

使用Spark进行数据清洗和存储:从商城数据到HDFS和数据库

摘要&#xff1a;本文介绍了如何使用Spark进行数据清洗和存储的过程。通过结合Spark的强大功能和Java编程知识&#xff0c;我们可以处理商城上报的数据&#xff0c;并将清洗后的数据存储到HDFS和数据库中。本文提供了详细的代码示例&#xff0c;帮助读者理解和实践数据清洗和存…

联通iccid 19转20 使用luhn 算法的计算公式

联通iccid 19转20 使用luhn 算法的计算公式 第一次对接iccid 才知道 使用的是luhn 算法 19转20位 文章来源于 文章来源 当时也是一脸懵逼 的状态&#xff0c;然后各种chatgpt 寻找&#xff0c;怎么找都发现不对&#xff0c;最后看到这片java的文章实验是正确的&#xff0c;因…

服务器被CC攻击之后怎么办?

1.取消域名绑定取消域名绑定后Web服务器的CPU能够马上恢复正常状态&#xff0c;通过IP进行访问连接一切正常。但是不足之处也很明显&#xff0c;取消或者更改域名对于别人的访问带来了不变&#xff0c;另外&#xff0c;对于针对IP的CC攻击它是无效的&#xff0c;就算更换域名攻…

常用的SQL术语和概念

以下是一些主要的SQL术语和概念&#xff1a; 列&#xff08;Column&#xff09;&#xff1a;表中的垂直部分&#xff0c;代表某种特定类型的数据属性&#xff0c;也称为字段。每列都有自己的名称、数据类型以及可能的约束条件。行&#xff08;Row&#xff09;&#xff1a;表中…