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: 2

buffer.compact();
System.out.println();
System.out.format("capacity: %s, limit: %s, position: %s", buffer.capacity(), buffer.limit(), buffer.position());
// capacity: 4, limit: 4, position: 2

byte 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();
        // 将数据包装为buffer
        ByteBuffer 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方法是非阻塞的,如果没有接受到请求,会返回null
            SocketChannel 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、创建网络服务端ServerSocketChannel
        ServerSocketChannel 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();

                }
            }
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

webpack项目打包console git分支、打包时间等信息 exec

相关链接 MDN toLocaleString child_process Node.js strftime 格式 代码 buildinfo.js const { execSync, exec } require("child_process"); // exec: 在 Windows 执行 bat 和 cmd 脚本// execSync 同步 // exec 异步// exec 使用方法 // exec(git show -s,…

notepad++里安装32位和64位的16进制编辑器Hex-Editor

这个16进制编辑器确实是个好东西&#xff0c;平时工作种会经常用到&#xff0c; 这是hex-editor的官网。这个里边只能下载32位的(64位的看最下边)&#xff0c;选一个合适的版本&#xff0c;我当时选的是最新的版本 https://sourceforge.net/projects/npp-plugins/files/Hex%20E…

[机器学习]练习KNN算法-曼哈顿距离

曼哈顿距离(Manhattan distance) 曼哈顿距离是指在几何空间中两点之间的距离&#xff0c;其计算方法是通过将两点在各个坐标轴上的差值的绝对值相加得到。在二维空间中&#xff0c;曼哈顿距离可以表示为两点在横纵坐标上的差值的绝对值之和&#xff1b;在三维空间中&#xff0…

物联网实战--入门篇之(三)嵌入式STM32

目录 一、Keil简介 二、工程结构 三、文件目录 四、STM32简介 五、编码风格 六、总结 一、Keil简介 Keil是一款常用的单片机开发工具&#xff0c;主要包含了编译、仿真、调试和开发界面(IDE)&#xff0c;后被ARM公司收购&#xff0c;与其MDK-ARM合并为MDK-ARM Keil软件包…

如何用 C++ 部署深度学习模型?

深度学习模型在诸多领域如图像识别、自然语言处理、语音识别等展现出强大的应用潜力。然而&#xff0c;模型训练与实际部署是两个不同的环节&#xff0c;许多开发者在使用Python进行模型训练后&#xff0c;出于性能、集成便利性或特定平台要求等因素&#xff0c;会选择使用C进行…

[机器学习]练习-KNN算法

1&#xff0e;&#x1d458;近邻法是基本且简单的分类与回归方法。&#x1d458;近邻法的基本做法是&#xff1a;对给定的训练实例点和输入实例点&#xff0c;首先确定输入实例点的&#x1d458;个最近邻训练实例点&#xff0c;然后利用这&#x1d458;个训练实例点的类的多数来…

基于单片机声音分贝采集和显示控制系统设计

**单片机设计介绍&#xff0c;基于单片机声音分贝采集和显示控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机声音分贝采集和显示控制系统设计&#xff0c;主要目标是实现声音分贝的实时采集、处理以及显示…

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

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

[羊城杯 2020]EasySer

[羊城杯 2020]EasySer 进入页面&#xff0c;发现是ubuntuapache2&#xff0c;但是好像没啥用 尝试访问/robots.txt&#xff0c;得到 访问/star1.php/&#xff0c;查看源码&#xff0c;得到提示 一看就知道是ssrf&#xff0c;使用http://127.0.0.1/ser.php&#xff0c;得到…

阿里云CentOS7安装Flink1.17

前提条件 阿里云CentOS7安装好jdk&#xff0c;官方文档要求java 11&#xff0c;使用java 8也可以。可参 hadoop安装 的jdk安装部分。 下载安装包 下载安装包 [hadoopnode1 ~]$ cd softinstall/ [hadoopnode1 softinstall]$ wget https://archive.apache.org/dist/flink/flin…

【物联网】Qinghub MQTT 连接协议

基础信息 组件名称 &#xff1a; mqtt-connector 组件版本&#xff1a; 1.0.0 组件类型&#xff1a; 系统默认 状 态&#xff1a; 正式发布 组件描述&#xff1a;通过MQTT 连接网关&#xff0c;发布或订阅MQTT broker相关的数据信息。 配置文件&#xff1a; 配置文件作为MQT…

前端-css-2

1.背景样式 属性名作用属性值background-color背景颜色颜色background-image设置背景图像地址url(地址)background-repeat设置背景图像重复方式 repeat&#xff1a;重复。 repeat-x&#xff1a;横向重复。 repeat-y&#xff1a;纵向重复。 no-repeat&#xff1a;不重复。 back…

Flink集群主节点JobManager启动分析

1.概述 JobManager 是 Flink 集群的主节点&#xff0c;它包含三大重要的组件&#xff1a; ResourceManager Flink集群的资源管理器&#xff0c;负责slot的管理和申请工作。 Dispatcher 负责接收客户端提交的 JobGraph&#xff0c;随后启动一个Jobmanager&#xff0c;类似 Yarn…

ssm 设备采购管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm 设备采购管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…

61、服务攻防——中间件安全CVE复现K8sDockerJettyWebsphere

文章目录 K8sDockerWebSphere K8s k8s&#xff1a;简单来说&#xff0c;跟docker一样&#xff0c;是个容器系统。 k8s对外攻击面总结 常见漏洞&#xff1a;未授权访问、提权漏洞 Docker docker逃逸&#xff1a;1、由内核漏洞引起&#xff1b;2、由Docker软件设计引起&#x…

vue3+threejs新手从零开发卡牌游戏(二十):添加卡牌被破坏进入墓地逻辑

在game目录下新建graveyard文件夹存放墓地相关代码&#xff1a; game/graveyard/p1.vue&#xff0c;这里主要设置了墓地group的位置&#xff1a; <template><div></div> </template><script setup lang"ts"> import { reactive, ref,…

HTML5 和 CSS3 提高

一、HTML5 的新特性 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这些新特性。 声明…

架构之第三方框架pinyin4j与hutool搭配使用原理

一、工作原理 Hutool工具将包括pinyin4j等翻译工具插件绑定。实现通过spi接口的方式实现调用&#xff0c;底层实现可自由切换 注&#xff1a;Hutool绑定的pinyin插件有如下图几种。也就是没有添加maven依赖如pinyin4j等拼音插件。 注&#xff1a;若没有依赖pinyin插件。使用时…

性能与压力测试

一、性能监控 1.1 jvm内存模型—堆 由于所有的对象实例以及数组都要在堆上分配内存&#xff0c;并且堆是垃圾收集器管理的主要区域&#xff0c;也被称为“GC堆”&#xff0c;所以堆是我们优化最多考虑的地方。 首先&#xff0c;堆可细分为&#xff1a; Young区&#xff08;新…

动态规划刷题(算法竞赛、蓝桥杯)--导弹拦截(线性DP)

1、题目链接&#xff1a;[NOIP1999 提高组] 导弹拦截 - 洛谷 #include <bits/stdc.h> using namespace std; const int N2e55; int a[N],x,n; int b[N],len;int main(){while(cin>>x)a[n]x;//求最长不上升子序列 b[0]2e9;//初始化为无穷大for(int i1;i<n;i){if(…