1. 程式人生 > >netty原始碼解解析(4.0)-22 ByteBuf的I/O

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方法