NIO基础——三大组件

三大核心组件

NIO的三个最重要的核心分别为:Channel,Buffer和Selector。

1.Channel(通道)

Channel就像是通道,是一个关于程序与操作系统底层I/O服务交互的通道。
比如:我们的程序对系统中某一个文件进行连接,以便于我们对它进行后续的操作。

常见的Channel有以下四种:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel
    FileChannel主要用于文件传输,其他三种用于网络通信。

2.Buffer(缓冲区)

当我们有了连接通道,我们需要将拿到的数据放到一个缓冲区域,以便于程序对它的读取/写入操作

常见的Buffer有以下几种:

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer
  • 其中最常用的是ByteBuffer

3.Selector(选择器)

	Selector(选择器)是一个特殊的组件,用于采集各个通道的状态(或者说事件)。
	我们先将通道注册到选择器,并设置好关心的事件,然后就可以通过调用select()方法,静静地等待事件发生。

在selector技术之前,处理socket连接有以下两种方法。
多线程技术
系统为每一个连接分配一个thread(线程),分别去处理对应的socket连接。
多线程技术
这种方法的弊端:

  • 内存占用高。每有一个socket连接,系统就要分配一个线程去对接。当出现大量连接时,会开辟大量线程,导致占用大量内存。
  • 线程上下文切换成本高。
  • 只适合连接数较少的场景。

什么是线程上下文切换?
一个CPU在同一个时刻是只能处理一个线程的,由于时间片耗尽或出现阻塞等情况,CPU 会转去执行另外一个线程,这个叫做线程上下文切换。

线程池技术
使用线程池,让线程池中的线程去处理连接
线程池技术
这种方法的弊端:

  • 在阻塞模式下,线程只能处理一个连接。线程池中的线程获取任务,只有当任务完成/socket断开连接,才会去获取执行
    下一个任务
  • 只适合短链接的场景。

选择器(Selector)技术

为每个线程配合一个选择器,让选择器去管理多个channel。(注:FileChannel是阻塞式的,因此无法使用选择器。)
让选择器去管理多个工作在非阻塞式下的Channel,获取Channel上的事件,当一个Channel没有任务时,就转而去执行别的Channel上的任务。这种适合用在连接多,流量小的场景。

选择器技术
若事件未就绪,调用 selector 的 select() 方法会阻塞线程,直到 channel 发生了就绪事件。这些事件就绪后,select 方法就会返回这些事件交给 thread 来处理

二、聊聊ByteBuffer

1.ByteBuffer的重要属性

首先了解ByteBuffer的四个属性:

  • capacity:缓冲区的容量,不可变。(在netty中可变哦~)
  • limit:缓冲区的界限。limit之后的数据不允许读写
  • position:读写指针。position不可大于limit,且position不为负数。
  • mark:标记。记录当前position的值。position被改变后,可以通过调用reset() 方法恢复到mark的位置。

2.ByteBuffer的方法

2-1.allocate方法

allocate是ByteBuffer的一个静态方法。通过allocate我们可以给ByteBuffer分配空间,但是这个空间不可以动态变换,如果想要改变ByteBuffer的大小只能重新分配一个。

2-2.allocateDirect方法

allocateDirect也是ByteBuffer的一个静态方法。通过allocateDirect我们也可以给ByteBuffer分配空间。

allocate 与 allocateDirect的区别在哪?
可以通过代码来看,首先我们通过这两个静态方法创建两个对象。

 public static void main(String[] args) {
        System.out.println(ByteBuffer.allocate(10).getClass());
        //class java.nio.HeapByteBuffer

        System.out.println(ByteBuffer.allocateDirect(10).getClass());
        //class java.nio.DirectByteBuffer
    }

控制打印的结果是:allocate创建出来的是HeapByteBuffer对象,allocateDirect创建出来的是DirectByteBuffer对象。
那就聊一下HeapByteBuffer和DirectByteBuffer的区别:

  • HeapByteBuffer是存在于JVM的堆内存中,DirectByteBuffer是存在于直接(系统)内存中。
  • HeapByteBuffer的读写效率低于DirectByteBuffer,因为HeapByteBuffer存在于jvm中的,自然会收到垃圾回收器的影响。
  • DirectByteBuffer使用不当,容易造成内存泄露。

2-3.put方法

put方法可以将数据放入到缓冲区中。操作完成后,position的值会+1,并指向下一个可存放的区域,limit=capacity。
在这里插入图片描述

2-4.flip方法

flip方法会切换对当前缓冲区的去操作,写/读->读/写。

当是写模式切换到读模式时,先是limit=position,然后position=0。
当是读模式切换到写模式时,恢复为put时的值。

2-5.get方法

  • get方法会读取缓冲区里的数据,一次只能读取一个。
  • 读取后,position的值会+1,指向下一个可读区。当position大于limit时,会报异常。
  • get方法如果传入指定的索引位置:get(i)。则position的值不会产生变动。

2-6.clean方法

clean方法就像初始化一样,会把ByteBuffer的里属性值都恢复到最初,并且清除缓冲区里的数据。
在这里插入图片描述

2-7.compact

compact方法会把已经读取的数据清除,后面未读取的数据向前压缩,然后切换到写模式。
数据前移后,原始位置的数据不会清楚,但是在后面的写入操作中会被覆盖。
在这里插入图片描述

2-8.rewind方法

rewind方法只能在读模式下使用,使用后,会恢复position、limit和capacity的值

在这里插入图片描述

2-9.mark方法和reset方法

这个两个方法通常都是搭配着使用。
mark会保存当前position的值,reset方法会把mark保存的值重新赋给position。

3.字符串与ByteBuffer的相互转换

3-1.方法一

        // 方法一
        // 编码:字符串的getByte方法
        ByteBuffer buffer = ByteBuffer.allocate(15);
        buffer.put(str.getBytes());
        // 解码:先切换到读模式,然后通过StandardCharsets的decoder来解码
        buffer.flip();
        String decodeStr = StandardCharsets.UTF_8.decode(buffer).toString();

3-2.方法二

        // 方法二
        // 编码:StandardCharsets的encode方法获取ByteBuffer
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(str);
        // 解码: 通过StandardCharsets的decoder方法解码
        String decodeStr2 = StandardCharsets.UTF_8.decode(buffer2).toString();

3-3.方法三

        // 方法三:
        ByteBuffer buffer3 = ByteBuffer.wrap(str.getBytes());
        // 解码: 通过StandardCharsets的decoder方法解码
        String decodeStr3 = StandardCharsets.UTF_8.decode(buffer3).toString();

4.黏包和半包

场景

将数据打包发送给服务端,每条数据以"\n"做分割。
比如现在有这三段话,我要一次性发给服务器

  • Hello world!\n

  • I’m LIKEGAKKI!\n

  • How are you?\n
    经过传输后,服务端的产生了两个ByteBuffer:

  • Hello,world\nI’m LIKEGAKKI\nHo(黏包)

  • w are you?\n?(半包)

原因
黏包

发送方在发送数据时,并不是一条一条地发送数据,而是将数据整合在一起,当数据达到一定的数量后再一起发送。这就会导致多条信息被放在一个缓冲区中被一起发送出去。

半包

因为我们分配缓冲区的大小是固定,如果空间小于数据量,那就只能先把当前缓冲区里的数据读取完,再去接收剩下的的数据。数据就会出现被截断的断层现象。

解决方法
  • 在循环中用get(i)方法依次读取数据,当读取的数据匹配‘\n’时,说明之前的读取的是一段信息。(get(i)方法不会修改position的值)
  • 记录该段数据长度,以便于申请对应大小的缓冲区
    将缓冲区的数据通过get()方法写入到target中。
  • 调用compact方法切换模式,因为缓冲区中可能还有未读的数据。

本篇文章根据在学习《黑马程序员Netty实战》时所做的笔记总结。

<think>嗯,用户想了解Excel中的VLOOKUP函数的用法和参数含义。首先,我需要回忆一下VLOOKUP的基本结构。VLOOKUP是垂直查找函数,用于在表格的首列查找指定的值,然后返回该行中指定列的数据。它的四个参数分别是查找值、表格范围、列索引号和匹配模式。 接下来,我得确认每个参数的具体作用。第一个参数是lookup_value,也就是用户要查找的值,比如产品ID或者姓名。第二个参数是table_array,即查找的范围,这里要注意的是,查找值必须位于这个范围的第一列,否则函数无法正确工作。第三个参数是col_index_num,也就是用户想要返回的数据在表格范围中的第几列,这个列数是从表格范围的第一列开始算起的,不是整个工作表的列。第四个参数是range_lookup,决定是精确匹配还是近似匹配,通常用FALSE或0来指定精确匹配,避免错误。 然后,用户可能需要一个具体的例子来理解如何使用。比如,假设有一个产品价格表,用户想根据产品ID查找对应的价格。这时候,VLOOKUP就可以在ID列查找,然后返回价格列的数据。同时,要提醒用户注意表格范围最好使用绝对引用,比如$A$2:$C$100,这样在拖动公式时范围不会变化。 可能遇到的问题也需要考虑。比如,如果查找值不在表格的第一列,或者列索引号超过了表格的列数,函数会返回错误。此外,数据格式不一致也可能导致问题,比如查找值是数字,而表格中是文本格式的数字,这时候需要统一格式。还有,当出现#N/A错误时,可能是查找值不存在,或者匹配模式设置不正确,需要检查参数是否正确。 用户可能还会问到如何避免常见的错误,比如部分数据匹配出错的情况。这时候需要检查是否有多余的空格,或者数据格式不一致,或者表格范围没有锁定导致拖动时偏移。此外,XLOOKUP作为VLOOKUP的升级版,可能在某些情况下更方便,比如可以向左查找或者返回多列数据,这也是可以提及的内容。 最后,整理步骤,从基础语法到参数说明,再到具体案例,逐步引导用户理解如何使用VLOOKUP函数。同时,提供常见错误和解决方法,帮助用户在实际应用中避免问题。如果有必要,还可以比较VLOOKUP和其他查找函数的区别,比如HLOOKUP或XLOOKUP,但根据用户的问题,主要集中在VLOOKUP本身即可。</think>### Excel VLOOKUP函数详解 #### 一、基础语法与参数说明 VLOOKUP函数的语法为: $$=VLOOKUP(lookup\_value, table\_array, col\_index\_num, [range\_lookup])$$ 包含4个参数: 1. **lookup_value**(必填):要查找的值(如单元格引用或具体值) 2. **table_array**(必填):包含数据的表格范围(必须包含查找列和返回列) 3. **col_index_num**(必填):返回值所在列的序号(从table_array第一列开始计数) 4. **range_lookup**(可选):匹配类型 - `TRUE`/`1`:近似匹配(默认值,需数据升序排列) - `FALSE`/`0`:精确匹配(常用选项) [^1][^2] #### 二、使用步骤演示(工资表查询案例) 假设需要根据员工编号查询工资: 1. 建立查询单元格(如`B12`) 2. 输入公式: ```excel =VLOOKUP(A12, $A$2:$D$100, 4, 0) ``` - `A12`:待查询的员工编号 - `$A$2:$D$100`:锁定数据区域(绝对引用) - `4`:返回第4列(工资列) - `0`:精确匹配 [^2][^3] #### 三、常见错误与解决方法 | 错误现象 | 原因 | 解决方案 | |---------|------|---------| | #N/A | 查找值不存在 | 检查数据源或改用`IFERROR`容错 | | #REF! | 列序号超出范围 | 确认col_index_num ≤ 表格列数 | | 部分匹配失败 | 数据格式不一致 | 统一数值/文本格式 | | 结果错位 | 表格未锁定 | 使用`$`符号固定区域引用 | [^3][^4] #### 四、进阶技巧 1. **多条件查询**: 使用辅助列合并多个条件字段 ```excel =VLOOKUP(A2&B2, $D$2:$F$100, 3, 0) ``` 2. **通配符匹配**: `"*"`匹配任意字符,`"?"`匹配单个字符 ```excel =VLOOKUP("张*", $A$2:$C$100, 3, 0) ``` 3. **跨表查询**: 引用其他工作表数据 ```excel =VLOOKUP(A2, Sheet2!$A$2:$D$100, 4, 0) ``` [^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值