zoukankan      html  css  js  c++  java
  • NIO复习(1):buffer

    先回顾下NIO中的"三剑客"模型:selector、channel、buffer

    对于网络通讯而言,代码最常处理的就是3件事:管理连接、读取数据、写入数据。上图中,selector就是用来管理连接的(通常只需要一个selector线程处理就行,可避免上下文切换),selector上注册了一堆channel(通道),channel是双向的(in/out),读写数据时,channel必须通过buffer(缓冲) 。selector通过Event事件轮询来选择(或者叫激活)某个channel。

    这篇主要回顾buffer的用法,先看类图:

    Buffer是一个抽象类,下面派生出了几种基本类型的子类,比如网络通讯中最常用的ByteBuffer,每个子类中真正用于存放数据的,是一个叫hb的数组。Buffer本身是可读可写的,为了方便标识读/写的位置,里面有3个非常重要的标识变量:

    position :表示下1个读或写的位置(即:hb数组的下标索引)

    limit:hb数组中有效数据的最大位置

    capacity:hb数组的容量(注:capacity与limit的区别,比如1个数组的容量是10,但是里面只存放了6个有效数据,那么在读取时,capacity为10,而limit则为6)

    jdk源码上,已经明确说明: position <= limit <= capacity。

    经常容易忽视的方法:flip()

    由于buffer是可读可写的,以IntBuffer为例,“写入时”每向buffer中放入1个int数字,position就向后移1位,所有数据写完后,position就指向最后1个数字的末尾了。“读取时”需要调用flip()方法把position重新移到位置0,才能读到数据。

    package com.cnblogs.yjmyzz;
    
    import java.nio.IntBuffer;
    
    public class BufferTest {
    
        public static void main(String[] args) {
            IntBuffer intBuffer = IntBuffer.allocate(6);
    
            System.out.println("after init => ");
            System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity() + "
    ");
    
            System.out.println("begin write => ");
            for (int i = 0; i < 4; i++) {
                intBuffer.put(i + 1);
                System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity());
            }
    
            System.out.println("
    -------before flip----------");
            System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity());
            intBuffer.flip();
            System.out.println("
    -------after flip----------");
            System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity() + "
    ");
    
            System.out.println("begin read =>");
            for (int i = 0; i < 4; i++) {
                System.out.println(intBuffer.get());
                System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity());
            }
    
    
        }
    }
    

    输出结果:

    after init => 
    pos:0,limit:6,cap:6
    
    begin write => 
    pos:1,limit:6,cap:6
    pos:2,limit:6,cap:6
    pos:3,limit:6,cap:6
    pos:4,limit:6,cap:6
    
    -------before flip----------
    pos:4,limit:6,cap:6
    
    -------after flip----------
    pos:0,limit:4,cap:6
    
    begin read =>
    1
    pos:1,limit:4,cap:6
    2
    pos:2,limit:4,cap:6
    3
    pos:3,limit:4,cap:6
    4
    pos:4,limit:4,cap:6
    

    图解如下,这是刚初始化后:

    写入4个元素后: 

    flip之后:

    所有数据读取完成后:

     

    接下来说一说mark(),这个方法必须与reset()结合才能看出效果,字面理解:mark就是在某个位置打个标志(做个记号),数据读取过程中,position会不断向前移,如果又想回到刚才做了记号的位置,调用reset即可。

    IntBuffer intBuffer = IntBuffer.allocate(6);
    for (int i = 0; i < 4; i++) {
        intBuffer.put(i + 1);
    }
    intBuffer.flip();
    
    System.out.println(intBuffer.get());
    System.out.println(intBuffer.get());
    System.out.println("-------after read 2 times----------");
    System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity() + "
    ");
    intBuffer.mark();
    System.out.println("-------after read 3 times----------");
    System.out.println(intBuffer.get());
    System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity() + "
    ");
    
    intBuffer.reset();
    System.out.println("-------after reset----------");
    System.out.println("pos:" + intBuffer.position() + ",limit:" + intBuffer.limit() + ",cap:" + intBuffer.capacity() + "
    ");
    

    输出:

    仔细观察上面的输出,在读取了2个数字后,position向前移动到了位置2,这时调用mark做了标记,然后再读取1个数,position继续向前移到3,然后再调用reset,position又退回到刚才的位置2。利用mark和reset,可以在缓冲区中,重复读取某一段数据。

    ByteBuffer的数据类型问题

    网络传输中ByteBuffer恐怕是用得最多的一种缓冲,特别要注意下“数据类型”问题。写入数据时,除了put方法外,ByteBuffer还提供了一系列的putXXX方法

    类似的,读取数据时除了get之外,还有一系列的getXXX方法

    比如在第位置1,调用了putChar('杨')写入数据,相应的读取位置1时,要调用getChar(),如果不匹配,可能会有异想不到的问题。

    ByteBuffer buffer = ByteBuffer.allocate(5);
    buffer.putShort((short) 1); // (1)
    buffer.putChar('杨'); // (2)
    
    buffer.flip();
    
    System.out.println(buffer.getShort()); //这里要与(1)处put的类型一致
    System.out.println(buffer.getChar()); //这里要与(2)处put的类型一致
    

      

    参考文档:

    https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/nio/Buffer.html

  • 相关阅读:
    统计中的精度和召回率的解释
    人工智能在医疗行业中的应用
    解决Myeclipse在调试(debug)时无法显示变量值问题(转载)
    溢出的问题
    返回引用和对象遇到的坑
    vmWare上网配置(转载)
    linux内核相关的术语(转载)
    一个死锁问题
    Ethernet,token ring,FDDI,ATM,WLAN(转载)
    kmalloc与vmalloc
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/nio-buffer.html
Copyright © 2011-2022 走看看