zoukankan      html  css  js  c++  java
  • netty源码解析(4.0)-22 ByteBuf的I/O

        ByteBuf的I/O主要解决的问题有两个:

    • 管理readerIndex和writerIndex。这个在在AbstractByteBuf中解决。
    • 从内存中读写数据。ByteBuf的不同实现主要使用两种内存:堆内存表示为byte[];直接内,可能是DirectByteBuffer或者是一块裸内存。这个问题在HeapByteBufUtil, UnsafeByteBufUtil中解决。

    管理readerIndex和writerIndex

        AbstractByteBuf中实现了对readerIndex和writerIndex的管理。下面以int数据的读写为例说明这两个属性的管理机制。

    readerIndex

    @Override
    public int readInt() {
        checkReadableBytes0(4);
        int v = _getInt(readerIndex);
        readerIndex += 4;
        return v;
    }
    

    这个方法是从ByteBuf中读取一个int数据.

    • 第一步: checkReadableBytes0(4), 确保ByteBuf中有至少4个Byte的数据。
    • 第二步: _getInt从readerIndex指定的位置读取4个Byte的数据,并反序列化成一个int。
    • 第三步: readerIndex的值增加4。

        还有一个getInt方法,它也能从ByteBuf读出一个int数据,不同的是这个方法需要指定读取位置,也不会影响readerIndex的值。
        如果有需要,还能使用readerIndex(int readerIndex)方法,设置readerIndex的值。
        可以调用markedReaderIndex()把当前的readerIndex值保存到markedReaderIndex属性。在有需要的时候可以调用resetReaderIndex()把markedReaderIndex属性的值恢复到readerIndex。

    writerIndex

    @Override
    public ByteBuf writeInt(int value) {
        ensureWritable0(4);
        _setInt(writerIndex, value);
        writerIndex += 4;
        return this;
    }
    

         这个方法是向ByteBuf中写入一个int数据。

    • 第一步: ensureWritable0(4), 检查确保ByteBuf中有4个Byte的可写空间。
    • 第二步: _setInt把int数据写入到writerIndex指定的位置,写之前会把int数据方序列化成4个Byte的数据。
    • 第三步: writerIndex值增加4。

         setInt方法向ByteBuf写入一个int数据,不影响writerIndex。
         markWriterIndex和resetWriterIndex分别用来标记和恢复writerIndex。

         不同数据类型对readerIndex和writerIndex影响取决于数据的长度。每种数据类型的长度在上一章中有详细描述。 Bytes数据相对要复杂一些,后面会详述。

    AbstractByteBuf定义的抽象的接口

        AbstractByteBuf这个抽象类主要实现对readerIndex和writerIndex的管理。它把真正的I/O操实现留给子类。
        以int类型的数据读写为例,它定义了两个抽象接口分别从指定位置读写数据: _getInt(int index), _setInt(int index, int value)。此外还有另外4组抽象方法,用来实现不同类型数据的读写:

    • _getByte, setByte
    • _getShort, setShort
    • _getUnsignedMedium, _setMedium
    • _getLong, _getLong

    浮点数据的I/O

        浮点数是IEEE定义的标准内存内存结构,较整数要复杂一些。AbstractByteBuf使用int的I/O方法处理float数据,使用long的I/O方法处理double数据。下面是writeDouble的实现:

     @Override
     public double readDouble() {
         return Double.longBitsToDouble(readLong());
     }
    @Override
    public ByteBuf writeDouble(double value) {
        writeLong(Double.doubleToRawLongBits(value));
        return this;
    }
    

        读的时候是先读出一个long数据,然后调用longBitsToDouble把long转换成double。写的时候先调用doubletoRawLongBits把double转换成long, 然后写入long数据。
        对float数据的处理类似,这里就不再赘述。

    Bytes数据的I/O

         ByteBuf定义了5中不同类型的Bytes: byte[], ByteBuffer, InputStream, OutputStream, ScatteringByteChannel。其中byte[]和ByteBuffer有最大长度限制,读写的时候可以指定长度,也可以不指定。另外3种长度不确定,必须要指定长度,下面以byte[]为例看一下Bytes I/O的实现。

    Bytes读

    @Override
    public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
        checkReadableBytes(length);
        getBytes(readerIndex, dst, dstIndex, length);
        readerIndex += length;
        return this;
    }
    
    @Override
    public ByteBuf readBytes(byte[] dst) {
        readBytes(dst, 0, dst.length);
        return this;
    }
    

    readBytes(dst)方法没有指定长度默认会读取dst最大长度的的数据。
    readBytes(dst, dstIndex, length)方法指定的dst的起始位置和长度,把读取的数据放到dst的dst[dstIndex]到dst[dstIndex+length]区间内。同时他还负责管理readerIndex。
    getBytes(index, dst, dstIndex, length)方法是功能最强大的方法,留给子类实现。

    Bytes写

    @Override
    public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
        ensureWritable(length);
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }
    
    @Override
    public ByteBuf writeBytes(byte[] src) {
        writeBytes(src, 0, src.length);
        return this;
    }
    

    writeBytes(src)写入整个src的数据。
    writeBytes(src, srcIndex, length)指定src的起始位置和长度,把src中src[srcIndex]到src[srcIndex+length]区间的数据写入到ByteBuf。同时它还负责管理writerIndex。
    setBytes(index, src, srcIndex, length)是功能最强大的写方法,留给子类实现。

    不同内存对I/O操作的影响

    前面讲到ByteBuf主要使用两种内存。堆内存可以表示为byte[]对象,直接内存可以表示为内存的首地址address, 和内存大小size, 内存的范围是(address, addrress+size]。本质上它们都是一维数组,不同的是,java语言提供了对byte[]类型的支持,而直接内可以通过DirectBuffer访问,如果支持Unsafe还可以通过sun.misc.Unsafe提供的方法访问。如果要读取byte[4]的数据,byte[]可以这样:

    //假设src是一个byte[], 长度大于4
    byte[] dst = byte[4];
    for(int i = 0; i < 4; ++ i) dst[i] = src[i];
    

    而直接内存是这样

    //假设src是DirectByteBuffer
    byte[] dst = byte[4];
    for(int i = 0; i < 4; ++ i) dst[i] = src.get(i);
    
    //如果使用Unsafe,假设address是内存的首地址,size > 4. unsafe是sun.misc.Unsafe对象
    byte[] dst = byte[4];
    for(int i = 0; i < 4; ++ i) dest[i] = unsafe.getByte(address+i);
    
    

    针对不同内存的I/O操作工具

        Netty提供了一个堆内存I/O操作的工具类: HeapByteBufUtil。一个使用 sun.misc.Unsafe的直接内存I/O操作的工具类: UnsafeByteBufUtil。
        下面以long类型数据的I/O为例,比较两者内存操作方式的不同之处。
        堆内存:

    static long getLong(byte[] memory, int index) {
            return  ((long) memory[index]     & 0xff) << 56 |
                    ((long) memory[index + 1] & 0xff) << 48 |
                    ((long) memory[index + 2] & 0xff) << 40 |
                    ((long) memory[index + 3] & 0xff) << 32 |
                    ((long) memory[index + 4] & 0xff) << 24 |
                    ((long) memory[index + 5] & 0xff) << 16 |
                    ((long) memory[index + 6] & 0xff) <<  8 |
                    (long) memory[index + 7] & 0xff;
    }
    
    static void setLong(byte[] memory, int index, long value) {
            memory[index]     = (byte) (value >>> 56);
            memory[index + 1] = (byte) (value >>> 48);
            memory[index + 2] = (byte) (value >>> 40);
            memory[index + 3] = (byte) (value >>> 32);
            memory[index + 4] = (byte) (value >>> 24);
            memory[index + 5] = (byte) (value >>> 16);
            memory[index + 6] = (byte) (value >>> 8);
            memory[index + 7] = (byte) value;
       }
    
    

    这个是BIG_ENDIAN字节序的算法。再看一下直接内存的的代码:

    static long getLong(long address){
    				PlatformDependent.getByte(address)) << 56 |
                   (PlatformDependent.getByte(address + 1) & 0xffL) << 48 |
                   (PlatformDependent.getByte(address + 2) & 0xffL) << 40 |
                   (PlatformDependent.getByte(address + 3) & 0xffL) << 32 |
                   (PlatformDependent.getByte(address + 4) & 0xffL) << 24 |
                   (PlatformDependent.getByte(address + 5) & 0xffL) << 16 |
                   (PlatformDependent.getByte(address + 6) & 0xffL) <<  8 |
                   (PlatformDependent.getByte(address + 7)) & 0xffL;
        }
    static void setLong(long address, long value) {
                PlatformDependent.putByte(address, (byte) (value >>> 56));
                PlatformDependent.putByte(address + 1, (byte) (value >>> 48));
                PlatformDependent.putByte(address + 2, (byte) (value >>> 40));
                PlatformDependent.putByte(address + 3, (byte) (value >>> 32));
                PlatformDependent.putByte(address + 4, (byte) (value >>> 24));
                PlatformDependent.putByte(address + 5, (byte) (value >>> 16));
                PlatformDependent.putByte(address + 6, (byte) (value >>> 8));
                PlatformDependent.putByte(address + 7, (byte) value);
        }
    

    这两段代码在BIG_ENDIAN的字节序编码算法上并无区别。他们的区别在于读写byte数据方式上,一个使用[]运算符,一个使用getByte和putByte方法。

  • 相关阅读:
    住建部第一批城市更新试点名单
    新城建试点城市
    日常笔记
    简单的C++配置模块
    C++ 异常 OR 错误码
    数独的暴力破解法
    MySQL语法数据库操作 Test
    Python中的staticmethod和classmethod Test
    Python中的__init__()、__new__()、__del__()方法 Test
    MySQL语法数据库表操作 Test
  • 原文地址:https://www.cnblogs.com/brandonli/p/11578233.html
Copyright © 2011-2022 走看看