Netty从入门到超神-NIO 三大核心(selector,channel,buffer)(二)

news2024/11/16 13:28:51

前言

上一篇文章认识了一下Java的三大IO,这一章节我们详细了解一下NIO的工作原理以及三大核心Selector,Channel,Buffer并尝试来做一些小案例。

Java NIO 模型

Java NIO有三个核心的组件: selector 选择器 , channel 通道 , buffer 缓冲区,模型如下:

Selector 多路复用器 

选择器,也叫多路复用器,Java的NIO通过selector实现一个线程处理多个客户端链接,多个channel可以注册到同一个Selector,Selector能够监测到channel上是否有读/写事件发生,从而获取事件和对事件进行处理,所以Selector切到哪个channel是由事件决定的。当线程从某个客户端通道未读取到数据时,可以把空闲时间用来做其他任务,性能得到了提升。

Channel 通道

  • 双向通信: 传统的 InputStreamOutputStream 是单向的,要么只能读取数据(InputStream),要么只能写入数据(OutputStream)。而 Channel 可以同时进行读写操作。例如,FileChannel 可以从文件中读取数据,也可以将数据写入文件。
  • 非阻塞IO: Channel 结合 Selector 可以实现非阻塞I/O操作,即在进行I/O操作时,线程不会被阻塞,可以继续执行其他任务。
  • 与Buffer结合使用: Channel 的读写操作通常与 Buffer 结合使用,数据在传输过程中都会经过 BufferChannel 从外部源(如文件、网络)读取数据到 Buffer,然后应用程序处理 Buffer 中的数据;或者将应用程序的数据写入 Buffer,再通过 Channel 将数据写到外部源。
常用的Channel类

FileChannel:主要用于文件的I/O操作,可以从文件中读取数据或将数据写入文件。

DatagramChannel:用于通过UDP协议进行数据的读写操作。UDP是一种无连接的、不可靠的传输协议,但它的传输效率较高。可以在非阻塞模式下发送和接收数据报文。不需要建立连接即可发送和接收数据。

SocketChannel:用于通过TCP协议进行客户端与服务器之间的网络通信。TCP是面向连接的、可靠的传输协议。可以在非阻塞模式下进行数据的读写。支持与服务器建立连接,并通过该连接进行数据传输。

Buffer 缓冲区

buffer主要是和channel通道做数据交互,Channel 提供从文件或网络读取数据的渠道,但是数据读取到一个它稍后处理的buffer中,实现了IO的非阻塞。 每个channel通道都会对应一个buffer,buffer是一个内存块,底层有一个数组,NIO的buffer可以写如数据,也可以从中读取数据。在Java中封装了很多基于buffer的类,如:

  • ByteBuffer:存储字节数据到缓冲区
  • ShortBuffer:存储字符串数据到缓冲区
  • CharBuffer:存储字符数据到缓冲区
  • IntBuffer:存储整数数据到缓冲区
  • LongBuffer:存储长整型数据到缓冲区
  • FloatBuffer:存储小数到缓冲区
  • DoubleBuffer:存储小数到缓冲区
  • MappedByteBuffer:基于内存操作文件

Buffer 的理解和使用

buffer : 缓冲区,buffer主要是和channel通道做数据交互,可以把数据写入Buffer以及从Buffer读取数据,java.nio.Buffer源码,以及常用子类如下:

Buffer可以看做是有一个数组来存储元素,Buffer类提供了四个很重要的属性:

  • capacity:Buffer所能够存放的最大容量,最多只能向 Buffer 写入 capacity 大小的字节

  • position:下一个被读或写的位置,随着不停的写入数据,position会向后移动,初始值是0,最大值是capacity - 1。当然在读数据的时候也需要知道读取的位置,当调用 flip 方法将 Buffer 从写模式转换为读模式时,position 被重新设置为 0 ,随着不停的读取,position会指向下个读取位置。
  • mark: 标记位置,用于记录某次读写的位置

  • limit: 对position的限制,在写模式下限制你能将多少数据写入Buffer中,limit等同于Buffer的容量(capacity)。当切换Buffer为读模式时,限制你最多能读取到多少数据。因此,当切换Buffer为读模式时,限制会被设置为写模式下的position值,即:你能读到之前写入的所有数据,限制被设置为已写的字节数,在写模式下就是position。

buffer使用数组存储元素,下面用 java.nio.ByteBuffer 来举例,源码如下:

public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>
{

    // These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    //存储数据的byte数组
    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers

这里看到了,ByteBuffer底层就是使用一个 final byte[] hb; 来存储元素,其他的Buffer是相同的道理。

创建容量为10字节的buffer: ByteBuffer byteBuffer = ByteBuffer.allocate(10);

写入三个字节的数据byteBuffer.put("aaa".getBytes()); 

调用读写转换方法 byteBuffer.flip(); 

读3个字节元素: byte[] bytes = new byte[4]; byteBuffer.get(bytes , 0 ,2); 

Buffer API介绍 

public abstract class Buffer {
    //返回此缓冲区容量capacity
    public final int capacity( )
    //返回此缓冲区位置position
    public final int position( )
    //设置缓冲区的位置position 
    public final Buffer position (int newPositio)
    //返回此缓冲区limit
    public final int limit( )
    //设置此缓冲区的限制limit
    public final Buffer limit (int newLimit)
    //在此缓冲区的位置设置标记
    public final Buffer mark( )
    //将此缓冲区的位置重置为以前标记的位置
    //把position设置为mark
    public final Buffer reset()
    //清除此缓冲区, 即将各个标记恢复到初始状态
    //把position设置为 0 ,limit = capacity;
    public final Buffer clear( )
    //读写反转此缓冲区
    public final Buffer flip( )
    //重置此缓冲区,position = 0;   mark = -1;
    public final Buffer rewind( )
    //返回当前位置与限制之间的元素数
    public final int remaining( )
    //返回当前位置之后是否还有元素
    public final boolean hasRemaining( )
    //告知此缓冲区是否为只读缓冲区
    public abstract boolean isReadOnly( );
 
    //返回此缓冲区是否具有可访问的底层实现数组
    public abstract boolean hasArray();
    //返回此缓冲区的底层实现数组
    public abstract Object array();
    //返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
    public abstract int arrayOffset();
    //告知此缓冲区是否为直接缓冲区
    public abstract boolean isDirect();
}


public abstract class FileChannel
    extends AbstractInterruptibleChannel
    implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
{
	//从通道读取数据并放到缓冲区中
	public int read(ByteBuffer dst) ;
	//把缓冲区的数据写到通道中
	public int write(ByteBuffer src) ;
	//从目标通道中复制数据到当前通道
	public long transferFrom(ReadableByteChannel src, long position, long count);
	//把数据从当前通道复制给目标通道
	public long transferTo(long position, long count, WritableByteChannel target);

}

Bytebuffer 简单使用

@Test
public void byteBufferTest(){
	//创建一个容量 1024 的bytebuffer
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    //存储元素
    byteBuffer.putChar('哈');
    byteBuffer.putInt(123);
    byteBuffer.putShort((short) 123);

    //读写转换
    byteBuffer.flip();
    
    //获取元素
    System.out.println(byteBuffer.getChar());   //哈
    System.out.println(byteBuffer.getInt());    //123
    System.out.println(byteBuffer.getLong());   //BufferUnderflowException 缓冲区溢出异常
}

使用buffer是需要注意,如果put的数据类型,和get是使用的类型不一致,可能会出现BufferUnderflowException 缓冲区溢出异常

写数据到文件

这个案例是通过Java把一段字符串写到磁盘的某个文件,它的大概流程示意图如下:

实现步骤如下:

  • 把数据写入一个ByteBuffer缓冲区
  • 创建一个FileOutputStream 输出流,目的是磁盘的一个文件
  • 通过FileOutputStream得到FileChannel通道
  • 调用channel.write,把ByteBuffer中的数据写入FileChannel,从而写到磁盘文件

实现代码如下:

	//使用NIO向磁盘写一个文件
   @Test
   public void nioWriteTest() throws IOException {
       //文件输出流
       FileOutputStream fileOutputStream = new FileOutputStream("d:/1.txt");
       //获取通道
       FileChannel channel = fileOutputStream.getChannel();
       //构建一个 容量1024字节长度的缓冲取
       ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
       System.out.println(byteBuffer.getClass().getName());
       //给buffer写入数据
       byteBuffer.put("你好NIO".getBytes());
       //转换
       byteBuffer.flip();
       //把buffer中的数据通过通道写入初盘文件
       channel.write(byteBuffer);
       //关闭通道
       channel.close();
       //关闭输出流
       fileOutputStream.close();
   }

从文件读数据

这个案例是通过Java把某个文件中数据读取到内存中,它的大概流程示意图如下:

实现步骤如下:

  • 创建一个FileInputStream,目的是读取磁盘的某个文件
  • 通过FileInputStream得到FileChannel
  • 创建一个ByteBuffer用来接收数据
  • 调用 channel.read 把数据写入bytebuffer
  • 再从bytebuffer中得到真实的数据

实现代码如下:

@Test
 public void nioReadTest() throws IOException {
     //文件输入流
     File file = new File("d:/1.txt");
     FileInputStream fileInputStream = new FileInputStream(file);
     //获取通道
     FileChannel channel = fileInputStream.getChannel();
     //创建一个buffer,容量为file的长度
     ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
     //从通道中读取数据到bytebuffer中
     channel.read(byteBuffer);
     //输出结果
     System.out.println(new String(byteBuffer.array()));
     //关闭资源
     channel.close();
     fileInputStream.close();
 }

 使用NIO完成文件拷贝

这个案例是通过NIO实现文件拷贝,它的大概流程示意图如下:

实现步骤如下:

  • 创建一个FileInputStream,目的是读取磁盘的某个文件
  • 通过FileInputStream得到FileChannel
  • 创建一个ByteBuffer用来接收数据
  • 调用 channel.read 把数据写入bytebuffer
  • 创建FileOutputStream,目的是把数据写到另外一个文件
  • 通过FileOutputStream得到FileChannel通道
  • 调用channel.write,把ByteBuffer中的数据写入FileChannel,从而写到磁盘文件

实现代码如下:

//文件拷贝 1.txt 中的内容拷贝到2.txt
@Test
public void nioCopyTest() throws IOException {

    //文件对象
    File file = new File("d:/1.txt");
    //文件输入流
    FileInputStream fileInputStream = new FileInputStream(file);
    //得到通道
    FileChannel channel = fileInputStream.getChannel();

    //文件输出流
    FileOutputStream fileOutputStream = new FileOutputStream("d:/2.txt");
    //获取通道
    FileChannel outChannel = fileOutputStream.getChannel();

    //缓冲区,容量为file的长度
    ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());

    while(true){

        //每次读取需要把缓冲区复位,否则当bytebuffer总的position等于limit的时候,
        //read的返回值是 0 ,用于不会走-1,就会死循环
        byteBuffer.clear();
        
        //把数据读取到缓冲区
        int readLenth = channel.read(byteBuffer);
        System.out.println("redLength = "+readLenth);
        //读取结果长度为-1说明读完了
        if(readLenth == -1){
            break;
        }
        //缓冲区读写交换
        byteBuffer.flip();
        outChannel.write(byteBuffer);
    }
    //关闭通道
    channel.close();
    //关闭流
    fileInputStream.close();
    //关闭通道
    outChannel.close();
    //关闭流
    fileOutputStream.close();
}

使用transferFrom拷贝文件

FileChannel提供了 transferFrom方法可以实现通道和通道之间的数据拷贝,方法包括三个参数:

public abstract long transferFrom(ReadableByteChannel src,
                                      long position, long count)throws IOException;
  • src : 源通道,即从哪个通道拷贝数据
  • position :拷贝的开始位置; 必须是非负数
  • count : 要拷贝的最大字节数; 必须是非负数

实现代码如下:

 //文件拷贝 1.txt 中的内容拷贝到2.txt
  @Test
  public void nioCopyTest2() throws IOException {

      //读操作=================================================================================
      //文件对象
      File file = new File("d:/1.txt");
      //文件输入流
      FileInputStream fileInputStream = new FileInputStream(file);
      //得到通道
      FileChannel inputChannel = fileInputStream.getChannel();

      //文件输出流
      FileOutputStream fileOutputStream = new FileOutputStream("d:/2.txt");
      //获取通道
      FileChannel outChannel = fileOutputStream.getChannel();
      //使用transferFrom拷贝数据,将inputChannel中数据拷贝到outChannel
      outChannel.transferFrom(inputChannel, 0, inputChannel.size());

      outChannel.close();
      inputChannel.close();
      fileInputStream.close();
      fileOutputStream.close();
  }

HeapByteBufferR只读buffer的使用

HeapByteBuffer,只读Buffer,只允许从中读数据,不允许写数据,否则抛出ReadOnlyBufferException异常,案例如下:

/**
     * 只读buffer
     */
@Test
public void nioOnlyRead() throws IOException {
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    for (int i = 0 ; i < 10 ; i++){
        byteBuffer.putInt(i);   //0123456789
    }

    //读写转换
    byteBuffer.flip();

    //得到一个只读buffer ,使用的是java.nio.HeapByteBufferR
    ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();

    //java.nio.HeapByteBufferR
    System.out.println(readOnlyBuffer.getClass().getName());

    while(readOnlyBuffer.hasRemaining()){
        System.out.print(readOnlyBuffer.getInt());  //0123456789
    }

    readOnlyBuffer.putInt(10);  //ReadOnlyBufferException ,不允许写
}

MappedByteBuffer 的使用

nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高,它可以基于内存实现文件的修改,这里的内存指的是“堆外内存”。

我们来做过案例,使用MappedByteBuffer来修改一个文本内容:"helloworld"把 h和w修改为大写。

@Test
 public void mappedByteBuffer() throws IOException {
     //随机访问文件,RW代表支持而读写,文件内容为 :helloworld
     File file = new File("d:/3.txt");
     RandomAccessFile randomAccessFile = new RandomAccessFile(file,"rw");

     //通道
     FileChannel channel = randomAccessFile.getChannel();

     //得到MappedByteBuffer : mode:读写模式, position: 映射区域的起始位置 size: 映射区域大小
     MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
     //第1个字节修改为 大 H
     mappedByteBuffer.put(0,(byte)'H');
     //第6个字节修改为 大 W
     mappedByteBuffer.put(5,(byte)'W');
     randomAccessFile.close();
 }

总结

本篇文件介绍了一下Java NIO 三大核心:selector , channel , buffer ,重点讲了Buffer的底层原理和几个小案例。

文章结束啦,如果对你有帮助的话,请一定给个好评哦~~~

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

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

相关文章

SpringBoot日常:Spring之@PostConstruct解析

简介 spring的Bean在创建的时候会进行初始化&#xff0c;而初始化过程会解析出PostConstruct注解的方法&#xff0c;并反射调用该方法。 PostConstruct 的使用和特点 只有一个非静态方法能使用此注解&#xff1b;被注解的方法不得有任何参数&#xff1b;被注解的方法返回值必…

Marin说PCB之TP测试的Layout设计要求

提及到TP点这个器件想必诸位道友们肯定不会陌生吧&#xff0c;我们的单板在量产之前都是需要做很多测试的&#xff0c;一般在产品研发的A版本和B版本的时候都是需要在单板上加上这个器件的。小编我最近在做一个改板&#xff0c;项目组为了降本增效&#xff0c;把单板的尺寸缩小…

Git 忽略已经提交的文件

对于未提交过的文件直接用ignore文件即可,不再赘述 对于已经提交过的文件,但是实际上不需要的,可以用git rm --cached命令 比如下图这个 .vsconfig被我误提交了或者忘了在ignore里添加了 但是我实际上不想要这个文件,那么在项目根目录打开git bash ,输入 git rm --cached .vsc…

LMDeploy 量化部署

创建环境和模型 conda create -n lmdeploy python3.10 -y conda activate lmdeploy conda install pytorch2.1.2 torchvision0.16.2 torchaudio2.1.2 pytorch-cuda12.1 -c pytorch -c nvidia -y pip install timm1.0.8 openai1.40.3 lmdeploy[all]0.5.3 mkdir /root/models …

[海思3403] 初始配置

虚拟机和板卡桥接 首先将虚拟机设置为桥接模式 板卡用网线和PC机连接&#xff0c;PC机用VMware打开Ubuntu虚拟机 点击虚拟网络编辑器&#xff0c;点击更改设置

爬取数据时,如何避免违法问题

目录 如何判断一个网站是否有明确禁止爬取数据&#xff1f; 如何处理爬取到的个人隐私数据以符合数据保护法规&#xff1f; 在爬取数据时&#xff0c;如何避免给目标网站带来过多的流量压力&#xff1f; 思维导图 在爬取数据时&#xff0c;避免违法问题的关键在于确保遵守相…

智慧工地:物联网技术和传感器技术的应用

随着科技的不断发展&#xff0c;物联网技术在各个领域得到了广泛的应用。在建筑行业中&#xff0c;智慧工地系统中物联网应用正逐渐成为一种趋势。本文将深入探讨智慧工地系统中物联网和传感器技术应用的内容。 物联网&#xff08;IoT&#xff09;和传感器技术在智慧工地中扮演…

养宠家庭除浮毛必入!希喂、安德迈、有哈宠物空气净化器真实对比

养过猫咪的铲屎官应该都体验过被换毛季支配的恐惧吧&#xff0c;夏天布偶的掉毛量已经全新升级了&#xff01;不仅是物体表面&#xff0c;连空气中都夹杂着浮毛&#xff0c;早上起来鼻子里偶尔都能发现它们的身影。长期生活在这样的环境中&#xff0c;肯定会对身体健康造成损害…

Spring数据访问层管理 ▎集成MyBatis ▎AOP ▎事务管理 ▎SpringWeb配置

前言: 在现代软件开发中&#xff0c;数据访问层的管理至关重要。Spring框架凭借其模块化结构和易用性&#xff0c;成为Java EE开发的首选。本文将探讨Spring在数据访问层的管理、MyBatis的集成、面向切面编程&#xff08;AOP&#xff09;、事务管理和Spring Web配置。 数据访…

【Material-UI】Select组件中的Native Select与TextField详解

文章目录 一、Select 组件概述1. 组件介绍2. Native Select 与 TextField 的区别 二、Native Select 组件详解1. 何为 Native Select2. Native Select 的基本用法3. Native Select 的优势与适用场景4. 自定义 Native Select 的样式 三、TextField 与 Select 的结合使用1. TextF…

BMS(三)

BAT (Pin 1): 连接到电池组的最高电压端&#xff08;CELL5&#xff09;。芯片通过该引脚监控整个电池组的总电压。 VC5 (Pin 2): 连接到第五节电池的正极&#xff0c;用于监控第五节电池的电压。 VC4 (Pin 3): 连接到第四节电池的正极&#xff0c;用于监控第四节电池的电压。 V…

【Datawhale X 李宏毅苹果书 AI夏令营】《深度学习详解》Task2 打卡

文章目录 前言学习目标一、线性模型二、分段线性曲线总结 前言 本文是【Datawhale X 李宏毅苹果书 AI夏令营】的Task2学习笔记打卡。 学习目标 李宏毅老师对应视频课程&#xff1a;https://www.bilibili.com/video/BV1JA411c7VT?p3 《深度学习详解》第一章主要介绍了深度学习…

Windows中Jupyter notebook设置默认目录

起因是我想白嫖实验室的显卡&#xff0c;就想在实验室电脑上安一个jupyter&#xff0c;就有了一系列问题 默认位置是这样的 方法一&#xff1a;配置文件 step1&#xff1a;生成jupyter配置文件 打开cmd&#xff0c;输入【jupyter notebook --generate-config】&#xff0c;输…

基于yolov8的行人跌倒检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的行人跌倒检测系统是利用先进的深度学习技术&#xff0c;特别是YOLOv8模型&#xff0c;来实现高效、准确的行人跌倒行为检测。YOLOv8作为YOLO系列的最新版本&#xff0c;通过改进的网络架构和训练策略&#xff0c;在保持高检测速度的同时&#xff0c;…

opencv实战项目十七:透射变换仪表表盘校正

文章目录 前言一、透射变换介绍&#xff1a;二、opencv实现2.1 cv2.getPerspectiveTransform()2.2 cv2.warpPerspective&#xff08;&#xff09; 三&#xff0c;代码实现&#xff1a;四&#xff0c;矫正效果&#xff1a; 前言 在这个信息化时代&#xff0c;图像处理技术在众多…

Python 利用rasterio库进行坐标转换

这是一篇必应上找的手册上的文章&#xff0c;特此记录 参考链接&#xff1a; 原文 参考链接2 影像中的RPC信息 目录 1、仿射变换矩阵转换2、使用控制点转换3、使用RPC进行坐标点转换 1、仿射变换矩阵转换 上面说的利用仿射转换矩阵进行转换&#xff0c;一些注意事项可以参考原文…

TQRFSOC开发板47DR LWIP自环测试

本例程基于RFSOC 47DR开发板实现LWIP自环通讯测试。使用开发板的网口与电脑或路由器等设备连接&#xff0c;使开发板与电脑在同一局域网内&#xff0c;使用telnet工具连接开发板&#xff0c;进行LWIP自环测试。 LWIP自环测试工程在Hello World项目上进行修改&#xff0c;首先打…

eclipse 配置 ABAP 连接操作手册

参考 GUI配置 按选择是否勾选single sign-on的方式&#xff0c;选择是否输入密码 点击NEXT 点击完成。

sqli-labs靶场通关攻略(46-50关)

第46关 &#xff08;ORDER BY数字型注入&#xff09; 输入?sort1 修改参数为2试一下 看到按照字母顺序进行了排序&#xff0c;所以它便是一个使用了order by语句进行排序的查询的一种查询输出方式 1出现报错&#xff0c;那么我们使用报错注入 查库 ?sort1 and updatexml(1,…

go.uber.org/ratelimit 源码分析

go.uber.org/ratelimit 源码分析 go 提供了一用来接口限流的包。其中"go.uber.org/ratelimit" 包正是基于漏桶算法实现的。 使用方式&#xff1a; 通过 ratelimit.New 创建限流器对象&#xff0c;参数为每秒允许的请求数&#xff08;RPS&#xff09;。使用 Take()…