• Netty(二)- NIO三大组件之Buffer


    一、Buffer 基本介绍

    Buffer(缓冲区):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。
    在这里插入图片描述

    二、Buffer 类及其子类

    (1)在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类。

    (2)常用Buffer子类一览(除boolean之外的7个基本类型对应的buffer)

    在这里插入图片描述

    (3)Buffer 的四个属性:

    public abstract class Buffer {
        // Invariants: mark <= position <= limit <= capacity
        private int mark = -1;
        private int position = 0;
        private int limit;
        private int capacity;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    属性描述
    Capacity容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
    Limit表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作,且极限是可以修改的
    Position位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下次读写作准备
    Mark标记

    在这里插入图片描述
    (4)Buffer类相关方法

    public abstract class Buffer {
        //JDK1.4时,引入的api
        public final int capacity( )//返回此缓冲区的容量
        public final int position( )//返回此缓冲区的位置
        public final Buffer position (int newPositio)//设置此缓冲区的位置
        public final int limit( )//返回此缓冲区的限制
        public final Buffer limit (int newLimit)//设置此缓冲区的限制
        public final Buffer mark( )//在此缓冲区的位置设置标记
        public final Buffer reset( )//将此缓冲区的位置重置为以前标记的位置
        public final Buffer clear( )//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖
        public final Buffer flip( )//反转此缓冲区
        public final Buffer rewind( )//重绕此缓冲区
        public final int remaining( )//返回当前位置与限制之间的元素个数
        public final boolean hasRemaining( )//告知在当前位置和限制之间是否有元素
        public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区
     
        //JDK1.6时引入的api
        public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
        public abstract Object array();//返回此缓冲区的底层实现数组
        public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
        public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (5)ByteBuffer

    从前面可以看出对于 Java 中的基本数据类型(boolean除外),都有一个 Buffer 类型与之相对应,最常用的自然是ByteBuffer 类(二进制数据),该类的主要方法如下:

    public abstract class ByteBuffer {
        //缓冲区创建相关api
        public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
        public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量
        public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用
        //构造初始化位置offset和上界length的缓冲区
        public static ByteBuffer wrap(byte[] array,int offset, int length)
         //缓存区存取相关API
        public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
        public abstract byte get (int index);//从绝对位置get
        public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
        public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、Buffer 的使用

    public class BasicBuffer {
    
        public static void main(String[] args) {
            // 创建一个Buffer, 大小为 3, 即可以存放3个int
            IntBuffer intBuffer = IntBuffer.allocate(3);
    
            // 向buffer 存放数据
            for(int i = 0; i < intBuffer.capacity(); i++) {
                intBuffer.put( i * 2);
            }
    
            // 将buffer转换,之前是写,现在转换为读
            intBuffer.flip();
    
            while (intBuffer.hasRemaining()) {
                System.out.println(intBuffer.get());
            }
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出:

    0
    2
    4
    
    • 1
    • 2
    • 3

    debug查看属性值变化:

    在这里插入图片描述

    在这里插入图片描述

    执行完flip()方法后,position值的变化:

    flip()方法源码:

    public Buffer flip() {
      limit = position;
      position = 0;
      mark = -1;
      return this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    四、关于Buffer 的注意事项和细节

    1. put和get的数据类型应该相同

    ByteBuffer 支持类型化的 put 和 get,put 放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常;

    public class NIOByteBufferPutGet {
        public static void main(String[] args) {
            // 创建一个Buffer
            ByteBuffer buffer = ByteBuffer.allocate(64);
    
            // 类型化方式放入数据
            buffer.putInt(100);
            buffer.putLong(9);
            buffer.putChar('你');
            buffer.putShort((short) 4);
    
            // 取出
            buffer.flip();
    
            System.out.println(buffer.getInt());
            System.out.println(buffer.getLong());
            System.out.println(buffer.getChar());
            System.out.println(buffer.getShort());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    输出:

    100
    9

    4

    2. 可以将一个普通 Buffer 转成只读 Buffer

    public class ReadOnlyBuffer {
        public static void main(String[] args) {
    
            // 创建一个buffer
            ByteBuffer buffer = ByteBuffer.allocate(3);
    
            for(int i = 0; i < 3; i++) {
                buffer.put((byte)i);
            }
    
            // 转换成读模式
            buffer.flip();
    
            // 得到一个只读的Buffer
            ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
            System.out.println(readOnlyBuffer.getClass());
    
            // 读取
            while (readOnlyBuffer.hasRemaining()) {
                System.out.println(readOnlyBuffer.get());
            }
    
            // 抛出ReadOnlyBufferException异常
            readOnlyBuffer.put((byte)100);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    输出:

    class java.nio.HeapByteBufferR
    0
    1
    2
    Exception in thread "main" java.nio.ReadOnlyBufferException
    	at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:172)
    	at com.lwk.nettydemo.nio.ReadOnlyBuffer.main(ReadOnlyBuffer.java:28)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3. 可以使用MappedByteBuffer让文件直接在内存中修改

    NIO 还提供了 MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,不用进行拷贝,提高了性能,而如何同步到文件由 NIO 来完成

    /**
     * MappedByteBuffer 可让文件直接在内存(堆外内存)修改,操作系统不需要再拷贝一次
     */
    public class MappedByteBufferTest {
        public static void main(String[] args) throws Exception {
    
            RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
            // 获取对应的通道
            FileChannel channel = randomAccessFile.getChannel();
    
            /**
             * 参数1:FileChannel.MapMode.READ_WRITE 使用的读写模式
             * 参数2:0:可以直接修改的起始位置
             * 参数3:5:是映射到内存的大小(不是索引位置),即 将1.txt的5个字节映射到内存
             * 可以直接修改的范围就是 0-5
             * 实际类型 DirectByteBuffer
             */
            MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
    
            mappedByteBuffer.put(0, (byte) 'H');
            mappedByteBuffer.put(3, (byte) '9');
            //IndexOutOfBoundsException异常,因为索引0-4已经占了5个字节,所以再修改索引5时,对应的字节超过了范围,报错
            //mappedByteBuffer.put(5, (byte) 'Y');
    
            randomAccessFile.close();
            System.out.println("修改成功~~");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    注意:在程序目录下发现没有修改成功
    在这里插入图片描述

    但其实在磁盘目录下已经修改成功
    在这里插入图片描述

    4. 可以通过 Buffer 数组完成读写操作(Scattering 和 Gathering)

    前面讲的读写操作,都是通过一个Buffer 完成的,NIO 还支持通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering

    /**
     * Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入  【分散】
     * Gathering: 从buffer读取数据时,可以采用buffer数组,依次读 【聚集】
     */
    public class ScatteringAndGatheringTest {
        public static void main(String[] args) throws Exception {
    
            // 使用 ServerSocketChannel 和 SocketChannel 网络
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
    
            // 绑定端口到socket ,并启动
            serverSocketChannel.socket().bind(inetSocketAddress);
    
            // 创建buffer数组
            ByteBuffer[] byteBuffers = new ByteBuffer[2];
            byteBuffers[0] = ByteBuffer.allocate(5);
            byteBuffers[1] = ByteBuffer.allocate(3);
    
            // 等客户端连接(telnet)
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 假定从客户端接收8个字节
            int messageLength = 8;
            // 循环读取
            while (true) {
    
                int byteRead = 0;
    
                while (byteRead < messageLength) {
                    long l = socketChannel.read(byteBuffers);
                    // 累计读取的字节数
                    byteRead += l;
                    System.out.println("byteRead=" + byteRead);
                    // 使用流打印,看看当前的这个buffer的 position 和 limit
                    Arrays.stream(byteBuffers).map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);
                }
    
                // 将所有的buffer进行flip
                Arrays.asList(byteBuffers).forEach(ByteBuffer::flip);
    
                // 将数据读出显示到客户端
                long byteWirte = 0;
                while (byteWirte < messageLength) {
                    long l = socketChannel.write(byteBuffers); //
                    byteWirte += l;
                }
    
                // 将所有的buffer进行clear
                Arrays.asList(byteBuffers).forEach(ByteBuffer::clear);
    
                System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    当发送6个字节时:

    在这里插入图片描述

    当发送8个字节时:

    在这里插入图片描述

  • 相关阅读:
    Python Streamlit 教程之使用 Python 和 Streamlit 打造令人惊叹的仪表板(教程含源码)
    壳聚糖导管复合辛伐他汀/泊洛沙姆407水凝胶/负载转化生长因子β1温敏性壳聚糖水凝胶的制备
    一篇文章带你搞懂 单调栈 是怎么回事
    【深度学习推荐系统 工程篇】三、浅析FastTransFormer看 GPU推理优化 思路
    【Yolov5+Deepsort】训练自己的数据集(3)| 目标检测&追踪 | 轨迹绘制 | 报错分析&解决
    ORA-28001:the password has expired,Linux上修改Oracle密码
    Java中的锁
    第6章:数据库设计基础知识
    【react】react 使用 Context 的简单示例
    【畅购商城】详情页模块之评论
  • 原文地址:https://blog.csdn.net/qq_36602071/article/details/128156153