Netty源码_AbstractByteBuf详解

上一章我们介绍了 ByteBuf 功能,这一章我们将讲解它重要的一个子类 AbstractByteBuf,它实现了 ByteBuf 中很多通用功能,这样子类就不需要各自实现了。

netty 中很多模块都是这样的,会有一个 Abstract 类实现模块的基础功能。

一. 重要属性

1.1 静态属性

1.1.1 checkAccessible

是否需要检查访问权限,默认是 true,是一个静态属性,所有 ByteBuf 实例一样。

    /**
     * Used internally by {@link AbstractByteBuf#ensureAccessible()} to try to guard
     * against using the buffer after it was released (best-effort).
     */
    boolean isAccessible() {
        return refCnt() != 0;
    }

    /**
     * Should be called by every method that tries to access the buffers content to check
     * if the buffer was released before.
     */
    protected final void ensureAccessible() {
        if (checkAccessible && !isAccessible()) {
            throw new IllegalReferenceCountException(0);
        }
    }

checkAccessibletrue 的时候,才会调用 isAccessible() 方法,判断访问权限。

可以通过 io.netty.buffer.checkAccessibleio.netty.buffer.bytebuf.checkAccessible 两个配置项设置。

    private static final String LEGACY_PROP_CHECK_ACCESSIBLE = "io.netty.buffer.bytebuf.checkAccessible";
    private static final String PROP_CHECK_ACCESSIBLE = "io.netty.buffer.checkAccessible";

    static final boolean checkAccessible; // accessed from CompositeByteBuf

    static {
        if (SystemPropertyUtil.contains(PROP_CHECK_ACCESSIBLE)) {
            checkAccessible = SystemPropertyUtil.getBoolean(PROP_CHECK_ACCESSIBLE, true);
        } else {
            checkAccessible = SystemPropertyUtil.getBoolean(LEGACY_PROP_CHECK_ACCESSIBLE, true);
        }
        ......
}

1.1.2 checkBounds

是否需要检查操作是否超出缓存区 ByteBuf 界限,默认是 true,是一个静态属性,所有 ByteBuf 实例一样。

可以通过 io.netty.buffer.checkBounds 配置项设置

    private static final boolean checkBounds;

    static {
        ......
        checkBounds = SystemPropertyUtil.getBoolean(PROP_CHECK_BOUNDS, true);
        ......
    }

1.1.3 leakDetector

检查缓存区 ByteBuf 是否存在内存泄露情况。是一个静态属性,所有 ByteBuf 实例共享。

    static final ResourceLeakDetector<ByteBuf> leakDetector =
        ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);

1.2 成员属性

  1. readerIndex: 读索引
  2. writerIndex: 写索引
  3. markedReaderIndex: 标记读索引
  4. markedWriterIndex: 标记写索引
  5. maxCapacity: 缓存区最大容量

你会发现,这里没有代表缓存区当前容量的属性,因为缓存区当前容量在不同类型缓存区实现中,表示形式是不一样的,所以这里就没有这个属性。

二. 构造方法

AbstractByteBuf 只有一个构造方法

    protected AbstractByteBuf(int maxCapacity) {
        checkPositiveOrZero(maxCapacity, "maxCapacity");
        this.maxCapacity = maxCapacity;
    }

也就是说缓存区必须设置它的最大容量 maxCapacity

三. 重要方法

3.1 索引和标记索引相关

  1. checkIndexBounds: 检查是否超出缓存区界限

    private static void checkIndexBounds(final int readerIndex, final int writerIndex, final int capacity) {
        if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity) {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",
                    readerIndex, writerIndex, capacity));
        }
    }
    
  2. readerIndex()readerIndex(int readerIndex): 获取和设置读索引 readerIndex

    @Override
    public int readerIndex() {
        return readerIndex;
    }
    
    @Override
    public ByteBuf readerIndex(int readerIndex) {
        if (checkBounds) {
            checkIndexBounds(readerIndex, writerIndex, capacity());
        }
        this.readerIndex = readerIndex;
        return this;
    }
    
  3. writerIndex()writerIndex(int writerIndex): 获取和设置写索引 writerIndex

    @Override
    public int writerIndex() {
        return writerIndex;
    }
    
    @Override
    public ByteBuf writerIndex(int writerIndex) {
        if (checkBounds) {
            checkIndexBounds(readerIndex, writerIndex, capacity());
        }
        this.writerIndex = writerIndex;
        return this;
    }
    
  4. setIndex(int readerIndex, int writerIndex): 同时设置读索引 readerIndex和写索引 writerIndex

    @Override
    public ByteBuf setIndex(int readerIndex, int writerIndex) {
        if (checkBounds) {
            checkIndexBounds(readerIndex, writerIndex, capacity());
        }
        setIndex0(readerIndex, writerIndex);
        return this;
    }
    
  5. clear(): 清除读索引 readerIndex和写索引 writerIndex,相当于 setIndex(0, 0)

    @Override
    public ByteBuf clear() {
        readerIndex = writerIndex = 0;
        return this;
    }
    
  6. markReaderIndex()resetReaderIndex(): 标记和重置读索引 readerIndex

    @Override
    public ByteBuf markReaderIndex() {
        markedReaderIndex = readerIndex;
        return this;
    }
    @Override
    public ByteBuf resetReaderIndex() {
        readerIndex(markedReaderIndex);
        return this;
    }
    
  7. markWriterIndex()resetWriterIndex(): 标记和重置写索引 writerIndex

    @Override
    public ByteBuf markWriterIndex() {
        markedWriterIndex = writerIndex;
        return this;
    }
    @Override
    public ByteBuf resetWriterIndex() {
        writerIndex(markedWriterIndex);
        return this;
    }
    
  8. discardReadBytes(): 这个方法会将可读区域 readable bytes 数据移动到开头,让可写区域writable bytes 变得更多,它也会改变读写索引。

    @Override
    public ByteBuf discardReadBytes() {
      // 如果读索引是0,那么就不需要移动,直接返回
      if (readerIndex == 0) {
          ensureAccessible();
          return this;
      }
    
      if (readerIndex != writerIndex) {
          // readerIndex != writerIndex,说明还有可读数据,那么就需要将这些可读数据移动到开头。
          // 这里取巧了,直接调用 ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) 方法,源缓存区就是自己
          setBytes(0, this, readerIndex, writerIndex - readerIndex);
          // 移动后写索引的值
          writerIndex -= readerIndex;
          // 但是要调整一下标记索引的大小
          adjustMarkers(readerIndex);
          // 读索引的值设置为 0
          readerIndex = 0;
      } else {
          // 如果 readerIndex == writerIndex,说明没有可读内容了,就不涉及数据移动了。
          // 那么直接将 readerIndex 和 writerIndex 设置为 0
          ensureAccessible();
          // 但是要调整一下标记索引的大小
          adjustMarkers(readerIndex);
          writerIndex = readerIndex = 0;
      }
      return this;
    }
    

    分为三种情况:

    • readerIndex == 0 说明可读数据就在开头,不需要移动,直接返回。
    • readerIndex == writerIndex 说明没有可读数据了,那么也不需要移动数据了,直接将读写索引都设置为 0,顺便修改一下标记索引的大小。
    • readerIndex != writerIndex 说明还有可读数据,那么就需要将这些可读数据移动到开头,通过调用 setBytes(int index, ByteBuf src, int srcIndex, int length) 方法来移动数据,然后将读索引设置为0,写索引设置为 writerIndex - readerIndex, 顺便修改一下标记索引的大小。
  9. discardSomeReadBytes() :这个方法不像 discardReadBytes() 方法,必须将可读区域 readable bytes 移动到开头,它会判断读索引的位置,来决定是否将可读区域移动到开头。所以它也有可能改变读写索引。

    @Override
    public ByteBuf discardSomeReadBytes() {
       // 只有读索引大于 0,才有可能需要移动
       if (readerIndex > 0) {
           // 当 readerIndex == writerIndex,说明没有可读内容了,不需要数据移动
           // 那么直接将 readerIndex 和 writerIndex 设置为 0
           if (readerIndex == writerIndex) {
               ensureAccessible();
               // 要调整一下标记索引的大小
               adjustMarkers(readerIndex);
               writerIndex = readerIndex = 0;
               return this;
           }
    
           // 当读索引的值大于或者等于 缓存区容量的一半时,才进行数据移动
           if (readerIndex >= capacity() >>> 1) {
               // 直接调用 ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) 方法,源缓存区就是自己
               setBytes(0, this, readerIndex, writerIndex - readerIndex);
               // 移动后写索引的值
               writerIndex -= readerIndex;
               // 调整一下标记索引的大小
               adjustMarkers(readerIndex);
               // 读索引的值设置为 0
               readerIndex = 0;
               return this;
           }
       }
       ensureAccessible();
       return this;
    }
    

    这个方法与discardReadBytes() 方法相比较,你会发现,唯一的不同就是它只会当读索引的值大于或者等于缓存区容量的一半时,才进行数据移动。

3.2 容量相关

  1. maxCapacity()maxCapacity(int maxCapacity): 获取和设置缓存区最大容量maxCapacity

  2. isReadable()isReadable(int numBytes): 缓存区是否有足够可读区域readable bytes

    @Override
    public boolean isReadable() {
        return writerIndex > readerIndex;
    }
    
    @Override
    public boolean isReadable(int numBytes) {
        return writerIndex - readerIndex >= numBytes;
    }
    
  3. isWritable()isWritable(int numBytes): 缓存区是否有足够可写区域writable bytes

    @Override
    public boolean isWritable() {
        return capacity() > writerIndex;
    }
    
    @Override
    public boolean isWritable(int numBytes) {
        return capacity() - writerIndex >= numBytes;
    }
    
  4. readableBytes(): 缓存区可读区域readable bytes 的大小。

    @Override
    public int readableBytes() {
        return writerIndex - readerIndex;
    }
    
  5. writableBytes(): 缓存区可写区域writable bytes 的大小。

     @Override
     public int writableBytes() {
         return capacity() - writerIndex;
     }
    
  6. maxWritableBytes(): 缓存区最大可写区域writable bytes 的大小。

     @Override
     public int maxWritableBytes() {
         return maxCapacity() - writerIndex;
     }
    
  7. ensureWritable(int minWritableBytes): 扩展缓存区容量。

    @Override
    public ByteBuf ensureWritable(int minWritableBytes) {
        ensureWritable0(checkPositiveOrZero(minWritableBytes, "minWritableBytes"));
        return this;
    }
    
    final void ensureWritable0(int minWritableBytes) {
        final int writerIndex = writerIndex();
        // 目标容量
        final int targetCapacity = writerIndex + minWritableBytes;
        // using non-short-circuit & to reduce branching - this is a hot path and targetCapacity should rarely overflow
        // 如果目标容量小于当前容量,那么不需要扩展,直接返回。
        // targetCapacity >= 0 是为了防止目标容量溢出了,超过 int 类型最大值
        if (targetCapacity >= 0 & targetCapacity <= capacity()) {
            ensureAccessible();
            return;
        }
        if (checkBounds && (targetCapacity < 0 || targetCapacity > maxCapacity)) {
            ensureAccessible();
            throw new IndexOutOfBoundsException(String.format(
                    "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                    writerIndex, minWritableBytes, maxCapacity, this));
        }
    
        // Normalize the target capacity to the power of 2.
        // 最快能够获取的最大可写区域大小,它的大小在 writableBytes() <= maxFastWritableBytes() <= markWriterIndex()
        final int fastWritable = maxFastWritableBytes();
        // 如果 fastWritable 大小足够,那么新容量就是 writerIndex + fastWritable;
        // 否则就是用 ByteBufAllocator 的 calculateNewCapacity 计算出新容量。
        // 特别注意,新的容量一定是 2的幂。
        int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
                : alloc().calculateNewCapacity(targetCapacity, maxCapacity);
    
        // 设置缓存区新容量
        capacity(newCapacity);
    }
    

    注意一下几点:

    • 当目标容量targetCapacity 小于或者等于当前缓存区容量 capacity(),那就直接返回。
    • 当目标容量targetCapacity超出缓存区最大容量 maxCapacity,那么新容量newCapacity 的值就是maxCapacity,这是alloc().calculateNewCapacity(targetCapacity, maxCapacity) 方法的实现。
    • 新容量newCapacity 一定是 2 的幂数。
  8. int ensureWritable(int minWritableBytes, boolean force): 也是扩展缓存区容量。

    @Override
    public int ensureWritable(int minWritableBytes, boolean force) {
        ensureAccessible();
        checkPositiveOrZero(minWritableBytes, "minWritableBytes");
    
        // minWritableBytes 在当前缓存区可写区域内,直接返回
        if (minWritableBytes <= writableBytes()) {
            return 0;
        }
    
        // 最大容量
        final int maxCapacity = maxCapacity();
        // 读索引
        final int writerIndex = writerIndex();
        // 待写字节大小 minWritableBytes,超出缓存区最大可写区域 `maxCapacity - writerIndex`
        if (minWritableBytes > maxCapacity - writerIndex) {
            // 非强制或者当前容量就是最大容量
            if (!force || capacity() == maxCapacity) {
                // 返回 1
                return 1;
            }
    
            // 将缓存区容量设置成最大容量maxCapacity
            capacity(maxCapacity);
            return 3;
        }
    
        // 扩展缓存区大小
        int fastWritable = maxFastWritableBytes();
        int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
                : alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
    
        // Adjust to the new capacity.
        capacity(newCapacity);
        return 2;
    }
    

    扩展缓存区只会分为四种情况:

    • 待写字节 minWritableBytes 小于或者等于缓存区可写区域大小 writableBytes(),不需要扩展容量,直接返回 0
    • 待写字节 minWritableBytes 小于或者等于缓存区最大可写区域大小 maxCapacity - writerIndex,能够正常扩展容量,返回 2
    • 待写字节 minWritableBytes 超出缓存区最大可写区域 maxCapacity - writerIndex, 不能正常扩展容量,那么分为两种情况:如果非强制(!force)或者当前容量就是最大容量时返回1,否则就将缓存区容量设置成最大容量并返回3。也就是 13 的区别,1 没有改变缓存区容量,而 3 重新设置的缓存区容量。

3.3 get 系列方法

我们知道 get 系列方法都从还钱指定索引位置读取数据。

3.3.1 获取基本数据类型的方法

首先我们讲解一下有符号和无符号类型的区别,因为 netty 提供了很多获取无符号基本数据类型的方法。

  • 对于有符号数据类型来说,它的最高位表示正负,即0 表示正数,1 表示负数。而对于无符号数据类型来说,它只表示正数,最高位也是数据内容。例如对于 byte 类型数据,有符号byte范围就是 -128 -->127,而无符号byte范围就是 0 --> 255
  • 所以将有符号数据变成无符号数据也很简单,就是将自身字节数据全部保留。例如获取无符号byte类型数据,就是(short) (getByte(index) & 0xFF)
  • 注意: 0xFF 是一个 int 类型数据,那么 getByte(index) 的值也要由byte 提升到 int 类型,然后才能计算,而类型提升时,如果是正数,那么高位全部补零,如果是负数,那么高位全部补1。而0xFF正好是高三个字节全是0,地位一个字节数据全是1,因此就正好保存了getByte(index) byte字节中全部数据。
  1. 获取 byte 类型数据

    @Override
    public byte getByte(int index) {
        checkIndex(index);
        return _getByte(index);
    }
    protected abstract byte _getByte(int index);
    @Override
    public boolean getBoolean(int index) {
        return getByte(index) != 0;
    }
    @Override
    public short getUnsignedByte(int index) {
        return (short) (getByte(index) & 0xFF);
    }
    
    • 通过 getBoolean(int index) ,我们知道 boolean 类型在缓存区存储的是一个 byte 字节,0 表示false, 非零就是 true
    • _getByte(int index) 方法是由子类实现的,不同类型的ByteBuf 实现不一样。
    • 必须通过 checkIndex(index) 方法,检查是否越界。
    • 通过 (getByte(index) & 0xFF) 获取无符号byte 值。
  2. 获取 short 类型数据

    @Override
    public short getShort(int index) {
        checkIndex(index, 2);
        return _getShort(index);
    }
    
    protected abstract short _getShort(int index);
    
    @Override
    public short getShortLE(int index) {
        checkIndex(index, 2);
        return _getShortLE(index);
    }
    
    protected abstract short _getShortLE(int index);
    
    @Override
    public int getUnsignedShort(int index) {
        return getShort(index) & 0xFFFF;
    }
    
    @Override
    public int getUnsignedShortLE(int index) {
        return getShortLE(index) & 0xFFFF;
    }
    
    • 有两个需要子类复写的方法 _getShort(int index)_getShortLE(int index)
    • 必须通过 checkIndex(index, 2) 方法,检查是否越界。
    • 通过 & 0xFFFF 获取无符号的 short 类型。
  3. 获取 Medium类型数据
    这是一个三个字节的整形数据,java 中没有对应类型,只能用 int 类型表示。

    @Override
    public int getUnsignedMedium(int index) {
        checkIndex(index, 3);
        return _getUnsignedMedium(index);
    }
    
    protected abstract int _getUnsignedMedium(int index);
    
    @Override
    public int getUnsignedMediumLE(int index) {
        checkIndex(index, 3);
        return _getUnsignedMediumLE(index);
    }
    
    protected abstract int _getUnsignedMediumLE(int index);
    
    @Override
    public int getMedium(int index) {
        int value = getUnsignedMedium(index);
         // 三字节最高位是不是 `1`
        if ((value & 0x800000) != 0) {
             // 通过 `0xff000000` 将 value 值转换成负数
            value |= 0xff000000;
        }
        return value;
    }
    
    @Override
    public int getMediumLE(int index) {
        int value = getUnsignedMediumLE(index);
        // 三字节最高位是不是 `1`
        if ((value & 0x800000) != 0) {
            // 通过 `0xff000000` 将 value 值转换成负数
            value |= 0xff000000;
        }
        return value;
    }
    
    • 有两个需要子类复写的方法 _getUnsignedMedium(int index)_getUnsignedMediumLE(int index)
    • 必须通过 checkIndex(index, 3) 方法,检查是否越界。
    • 你会发现不一样的点,Medium 类型数据,直接获取的是无符号Medium类型,有符号Medium类型反而要做特殊处理。即判断三字节最高位是不是 1 (即(value & 0x800000) != 0), 然后转成负数 value |= 0xff000000。主要是因为 java 是没有三个字节的整形数据的。
  4. 获取 int类型数据

    @Override
     public int getInt(int index) {
         checkIndex(index, 4);
         return _getInt(index);
     }
     protected abstract int _getInt(int index);
     @Override
     public int getIntLE(int index) {
         checkIndex(index, 4);
         return _getIntLE(index);
     }
     protected abstract int _getIntLE(int index);
     @Override
     public long getUnsignedInt(int index) {
         return getInt(index) & 0xFFFFFFFFL;
     }
     @Override
     public long getUnsignedIntLE(int index) {
         return getIntLE(index) & 0xFFFFFFFFL;
     }
    
    • 有两个需要子类复写的方法 _getInt(int index)_getIntLE(int index)
    • 必须通过 checkIndex(index, 4) 方法,检查是否越界。
    • 通过 & 0xFFFFFFFFL 获取无符号的 short 类型。注意 0xFFFFFFFFL 是一个 long 类型。
  5. 获取 long类型数据

     @Override
     public long getLong(int index) {
         checkIndex(index, 8);
         return _getLong(index);
     }
     protected abstract long _getLong(int index);
     @Override
     public long getLongLE(int index) {
         checkIndex(index, 8);
         return _getLongLE(index);
     }
     protected abstract long _getLongLE(int index);
    
    • 有两个需要子类复写的方法 _getLong(int index)_getLongLE(int index)
    • 必须通过 checkIndex(index, 8) 方法,检查是否越界。
    • long 类型不需要无符号long 类型了
  6. 其他基础类型方法

     @Override
     public char getChar(int index) {
         return (char) getShort(index);
     }
     @Override
     public float getFloat(int index) {
         return Float.intBitsToFloat(getInt(index));
     }
     public float getFloatLE(int index) {
         return Float.intBitsToFloat(getIntLE(index));
     }
     @Override
     public double getDouble(int index) {
         return Double.longBitsToDouble(getLong(index));
     }
     public double getDoubleLE(int index) {
         return Double.longBitsToDouble(getLongLE(index));
     }
    
    • 你会发现单浮点型float 数据是通过 int 类型存储的,双浮点型 double 数据是通过 long 类型存储的。

3.3.2 其他方法

  1. getBytes(int index, byte[] dst)
    @Override
    public ByteBuf getBytes(int index, byte[] dst) {
        getBytes(index, dst, 0, dst.length);
        return this;
    }
    
  2. getBytes(int index, ByteBuf dst)getBytes(int index, ByteBuf dst, int length)
     @Override
     public ByteBuf getBytes(int index, ByteBuf dst) {
         getBytes(index, dst, dst.writableBytes());
         return this;
     }
    
     @Override
     public ByteBuf getBytes(int index, ByteBuf dst, int length) {
         getBytes(index, dst, dst.writerIndex(), length);
         dst.writerIndex(dst.writerIndex() + length);
         return this;
     }
    

    会改变目标缓存区 dst 的写索引 writerIndex 的值。

  3. getCharSequence(int index, int length, Charset charset)
     @Override
     public CharSequence getCharSequence(int index, int length, Charset charset) {
         if (CharsetUtil.US_ASCII.equals(charset) || CharsetUtil.ISO_8859_1.equals(charset)) {
             // ByteBufUtil.getBytes(...) will return a new copy which the AsciiString uses directly
             return new AsciiString(ByteBufUtil.getBytes(this, index, length, true), false);
         }
         return toString(index, length, charset);
     }
    

3.4 set 系列方法

3.4.1 设置基本数据类型的方法

  1. 设置 byte 类型数据
     @Override
     public ByteBuf setByte(int index, int value) {
         checkIndex(index);
         _setByte(index, value);
         return this;
     }
     protected abstract void _setByte(int index, int value);
     @Override
     public ByteBuf setBoolean(int index, boolean value) {
         setByte(index, value? 1 : 0);
         return this;
     }
    
    • 需要子类复写 _setByte(int index, int value) 方法
    • 注意,虽然设置byte 类型数据,但是传递的值 value 却是 int 类型。
  2. 设置 short 类型数据
     @Override
     public ByteBuf setShort(int index, int value) {
         checkIndex(index, 2);
         _setShort(index, value);
         return this;
     }
     protected abstract void _setShort(int index, int value);
     @Override
     public ByteBuf setShortLE(int index, int value) {
         checkIndex(index, 2);
         _setShortLE(index, value);
         return this;
     }
     protected abstract void _setShortLE(int index, int value);
     @Override
     public ByteBuf setChar(int index, int value) {
         setShort(index, value);
         return this;
     }
    
    • 需要子类复写 _setShort(int index, int value)_setShortLE(int index, int value)方法
    • 注意,虽然设置short 类型数据,但是传递的值 value 却是 int 类型。
    • char 类型也是用short 类型数据存储的。
  3. 设置 medium 类型数据
     @Override
     public ByteBuf setMedium(int index, int value) {
         checkIndex(index, 3);
         _setMedium(index, value);
         return this;
     }
     protected abstract void _setMedium(int index, int value);
     @Override
     public ByteBuf setMediumLE(int index, int value) {
         checkIndex(index, 3);
         _setMediumLE(index, value);
         return this;
     }
     protected abstract void _setMediumLE(int index, int value);
    
    • 需要子类复写 _setMedium(int index, int value)_setMediumLE(int index, int value)方法
    • 注意,虽然设置medium 类型数据,但是传递的值 value 却是 int 类型。
  4. 设置 int 类型数据
     @Override
     public ByteBuf setInt(int index, int value) {
         checkIndex(index, 4);
         _setInt(index, value);
         return this;
     }
     protected abstract void _setInt(int index, int value);
     @Override
     public ByteBuf setIntLE(int index, int value) {
         checkIndex(index, 4);
         _setIntLE(index, value);
         return this;
     }
     protected abstract void _setIntLE(int index, int value);
     @Override
     public ByteBuf setFloat(int index, float value) {
         setInt(index, Float.floatToRawIntBits(value));
         return this;
     }
    
    • 需要子类复写 _setInt(int index, int value)_setIntLE(int index, int value)方法
    • 单精度浮点型float数据使用 int类型存储的。
  5. 设置 long 类型数据
    @Override
     public ByteBuf setLong(int index, long value) {
         checkIndex(index, 8);
         _setLong(index, value);
         return this;
     }
     protected abstract void _setLong(int index, long value);
     @Override
     public ByteBuf setLongLE(int index, long value) {
         checkIndex(index, 8);
         _setLongLE(index, value);
         return this;
     }
     protected abstract void _setLongLE(int index, long value);
     @Override
     public ByteBuf setDouble(int index, double value) {
         setLong(index, Double.doubleToRawLongBits(value));
         return this;
     }
    
    • 需要子类复写 _setLong(int index, long value)_setLongLE(int index, long value)方法
    • 双精度浮点型double数据使用 long类型存储的。

3.4.2 其他方法

  1. setBytes(int index, byte[] src)
     @Override
     public ByteBuf setBytes(int index, byte[] src) {
         setBytes(index, src, 0, src.length);
         return this;
     }
    
  2. setBytes(int index, ByteBuf src)setBytes(int index, ByteBuf src, int length)
     @Override
     public ByteBuf setBytes(int index, ByteBuf src) {
         setBytes(index, src, src.readableBytes());
         return this;
     }
     @Override
     public ByteBuf setBytes(int index, ByteBuf src, int length) {
         checkIndex(index, length);
         ObjectUtil.checkNotNull(src, "src");
         if (checkBounds) {
             checkReadableBounds(src, length);
         }
    
         setBytes(index, src, src.readerIndex(), length);
         src.readerIndex(src.readerIndex() + length);
         return this;
     }
    
    • 会改变源缓存区src 的读索引readerIndex的值。
  3. setZero(int index, int length)
     @Override
     public ByteBuf setZero(int index, int length) {
         if (length == 0) {
             return this;
         }
    
         // 检查边界
         checkIndex(index, length);
    
         // 计算 length/8 的值,因为 long 类型是8个字节,优先使用 long 设置
         int nLong = length >>> 3;
         // 不足 8 的值
         int nBytes = length & 7;
         // 先使用 long 来设置
         for (int i = nLong; i > 0; i --) {
             _setLong(index, 0);
             index += 8;
         }
         // 如果正好等于4,那么直接用一个 int 类型就可以了
         if (nBytes == 4) {
             _setInt(index, 0);
             // Not need to update the index as we not will use it after this.
         } else if (nBytes < 4) {
             // 不足 4 个字节,那么就直接使用 byte 字节设置,这里就不用 short再来一层了
             for (int i = nBytes; i > 0; i --) {
                 _setByte(index, (byte) 0);
                 index ++;
             }
         } else {
             _setInt(index, 0);
             index += 4;
             // 不足 4 个字节,那么就直接使用 byte 字节设置,这里就不用 short再来一层了
             for (int i = nBytes - 4; i > 0; i --) {
                 _setByte(index, (byte) 0);
                 index ++;
             }
         }
         return this;
     }
    

    就是将缓存区从 indexlength 长度的区域的值都设置成 0,这路使用了技巧,优先使用 long 类型设置,不足 8个字节的部分,使用int类型 和 byte 类型设置。

3.5 read 系列方法

3.5.1 读取基本数据类型的方法

  1. 读取 byte 类型数据
     private void checkReadableBytes0(int minimumReadableBytes) {
         ensureAccessible();
         if (checkBounds && readerIndex > writerIndex - minimumReadableBytes) {
             throw new IndexOutOfBoundsException(String.format(
                     "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
                     readerIndex, minimumReadableBytes, writerIndex, this));
         }
     }
    
     @Override
     public byte readByte() {
         checkReadableBytes0(1);
         int i = readerIndex;
         byte b = _getByte(i);
         readerIndex = i + 1;
         return b;
     }
     @Override
     public boolean readBoolean() {
         return readByte() != 0;
     }
     @Override
     public short readUnsignedByte() {
         return (short) (readByte() & 0xFF);
     }
    
    • 通过 checkReadableBytes0(1) 方法检查是否越界。
    • 获取完数据之后,将读索引readerIndex值增加1
  2. 读取 short 类型数据
     @Override
     public short readShort() {
         checkReadableBytes0(2);
         short v = _getShort(readerIndex);
         readerIndex += 2;
         return v;
     }
     @Override
     public short readShortLE() {
         checkReadableBytes0(2);
         short v = _getShortLE(readerIndex);
         readerIndex += 2;
         return v;
     }
     @Override
     public int readUnsignedShort() {
         return readShort() & 0xFFFF;
     }
     @Override
     public int readUnsignedShortLE() {
         return readShortLE() & 0xFFFF;
     }
     @Override
     public char readChar() {
         return (char) readShort();
     }
    
    • 通过 checkReadableBytes0(2) 方法检查是否越界。
    • 获取完数据之后,将读索引readerIndex值增加2
    • 字符char类型是通过 short 类型读取的。
  3. 读取 medium 类型数据
     @Override
     public int readMedium() {
         int value = readUnsignedMedium();
         if ((value & 0x800000) != 0) {
             value |= 0xff000000;
         }
         return value;
     }
     @Override
     public int readMediumLE() {
         int value = readUnsignedMediumLE();
         if ((value & 0x800000) != 0) {
             value |= 0xff000000;
         }
         return value;
     }
     @Override
     public int readUnsignedMedium() {
         checkReadableBytes0(3);
         int v = _getUnsignedMedium(readerIndex);
         readerIndex += 3;
         return v;
     }
     @Override
     public int readUnsignedMediumLE() {
         checkReadableBytes0(3);
         int v = _getUnsignedMediumLE(readerIndex);
         readerIndex += 3;
         return v;
     }
    
    • 通过 checkReadableBytes0(3) 方法检查是否越界。
    • 获取完数据之后,将读索引readerIndex值增加3
  4. 读取 int 类型数据
     @Override
     public int readInt() {
         checkReadableBytes0(4);
         int v = _getInt(readerIndex);
         readerIndex += 4;
         return v;
     }
     @Override
     public int readIntLE() {
         checkReadableBytes0(4);
         int v = _getIntLE(readerIndex);
         readerIndex += 4;
         return v;
     }
     @Override
     public long readUnsignedInt() {
         return readInt() & 0xFFFFFFFFL;
     }
     @Override
     public long readUnsignedIntLE() {
         return readIntLE() & 0xFFFFFFFFL;
     }
     @Override
     public float readFloat() {
         return Float.intBitsToFloat(readInt());
     }
    
    • 通过 checkReadableBytes0(4) 方法检查是否越界。
    • 获取完数据之后,将读索引readerIndex值增加4
    • 单精度浮点数 float 数据,是通过 int 类型转换的。
  5. 读取 long 类型数据
     @Override
     public long readLong() {
         checkReadableBytes0(8);
         long v = _getLong(readerIndex);
         readerIndex += 8;
         return v;
     }
    
     @Override
     public long readLongLE() {
         checkReadableBytes0(8);
         long v = _getLongLE(readerIndex);
         readerIndex += 8;
         return v;
     }
     @Override
     public double readDouble() {
         return Double.longBitsToDouble(readLong());
     }
    
    • 通过 checkReadableBytes0(8) 方法检查是否越界。
    • 获取完数据之后,将读索引readerIndex值增加8
    • 双精度浮点数 double 数据,是通过 long 类型转换的。

3.5.2 读取一小段缓存区

  1. readBytes(int length)
     @Override
     public ByteBuf readBytes(int length) {
         checkReadableBytes(length);
         if (length == 0) {
             return Unpooled.EMPTY_BUFFER;
         }
    
         // 创建新的缓存区,初始容量initialCapacity就是 length,最大容量是maxCapacity
         ByteBuf buf = alloc().buffer(length, maxCapacity);
         // 通过 writeBytes 方法将本缓存区从readerIndex 起 length长度的数据写入到新创建的缓存区buf
         buf.writeBytes(this, readerIndex, length);
         // 增加读索引的值
         readerIndex += length;
         // 返回新创建的缓存区
         return buf;
     }
    

    这个将本缓存区的一段内容复制到新创建的缓存区中,它们不共享数据,不会互相影响。

  2. readSlice(int length)readRetainedSlice(int length)
     @Override
     public ByteBuf readSlice(int length) {
         checkReadableBytes(length);
         ByteBuf slice = slice(readerIndex, length);
         readerIndex += length;
         return slice;
     }
    
     @Override
     public ByteBuf readRetainedSlice(int length) {
         checkReadableBytes(length);
         ByteBuf slice = retainedSlice(readerIndex, length);
         readerIndex += length;
         return slice;
     }
    

    通过 sliceretainedSlice 方法,获取本缓存区一段内容的新缓存区,但是这个新缓存区与本缓存区共享内存,也就说更改本缓冲区对应区域的内容,那么新缓存区的内容也会改变。

3.5.3 读取数据到字节数组

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

最终还是通过 getBytes(int index, byte[] dst, int dstIndex, int length) 方法实现。不过它增加了读索引 readerIndex 的值。

3.5.4 读取数据到其他缓存区ByteBuf

    @Override
    public ByteBuf readBytes(ByteBuf dst) {
        readBytes(dst, dst.writableBytes());
        return this;
    }
    @Override
    public ByteBuf readBytes(ByteBuf dst, int length) {
        if (checkBounds) {
            if (length > dst.writableBytes()) {
                throw new IndexOutOfBoundsException(String.format(
                        "length(%d) exceeds dst.writableBytes(%d) where dst is: %s", length, dst.writableBytes(), dst));
            }
        }
        readBytes(dst, dst.writerIndex(), length);
        dst.writerIndex(dst.writerIndex() + length);
        return this;
    }
    @Override
    public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
        checkReadableBytes(length);
        getBytes(readerIndex, dst, dstIndex, length);
        readerIndex += length;
        return this;
    }
  • 最后通过调用 getBytes(int index, ByteBuf dst, int dstIndex, int length) 方法实现
  • 如果从目标缓存区dst 的写索引writerIndex处,那么就增加目标缓存区dst 的写索引值,因此前两个方法会增加目标缓存区dst 的写索引值。
  • 它们都增加本缓存区读索引值。

3.5.5 读取数据到 NIO缓存区ByteBuffer

    @Override
    public ByteBuf readBytes(ByteBuffer dst) {
        int length = dst.remaining();
        checkReadableBytes(length);
        getBytes(readerIndex, dst);
        readerIndex += length;
        return this;
    }

3.5.6 读取数据到IO

    @Override
    public int readBytes(GatheringByteChannel out, int length)
            throws IOException {
        checkReadableBytes(length);
        int readBytes = getBytes(readerIndex, out, length);
        readerIndex += readBytes;
        return readBytes;
    }

    @Override
    public int readBytes(FileChannel out, long position, int length)
            throws IOException {
        checkReadableBytes(length);
        int readBytes = getBytes(readerIndex, out, position, length);
        readerIndex += readBytes;
        return readBytes;
    }

    @Override
    public ByteBuf readBytes(OutputStream out, int length) throws IOException {
        checkReadableBytes(length);
        getBytes(readerIndex, out, length);
        readerIndex += length;
        return this;
    }

3.5.7 跳过一段区域

    @Override
    public ByteBuf skipBytes(int length) {
        checkReadableBytes(length);
        readerIndex += length;
        return this;
    }

3.6 write 系列方法

所有 write 方法都通过 ensureWritable0 方法来确保有足够的写入空间
,并且写入之后都会增加写索引 writerIndex 的值。

3.6.1 写入基本数据类型的方法

  1. 写入 byte 类型数据
 @Override
 public ByteBuf writeBoolean(boolean value) {
     writeByte(value ? 1 : 0);
     return this;
 }

 @Override
 public ByteBuf writeByte(int value) {
     ensureWritable0(1);
     _setByte(writerIndex++, value);
     return this;
 }
  1. 写入 short 类型数据
     @Override
     public ByteBuf writeShort(int value) {
         ensureWritable0(2);
         _setShort(writerIndex, value);
         writerIndex += 2;
         return this;
     }
     @Override
     public ByteBuf writeShortLE(int value) {
         ensureWritable0(2);
         _setShortLE(writerIndex, value);
         writerIndex += 2;
         return this;
     }
     @Override
     public ByteBuf writeChar(int value) {
         writeShort(value);
         return this;
     }
    
  2. 写入 medium 类型数据
     @Override
     public ByteBuf writeMedium(int value) {
         ensureWritable0(3);
         _setMedium(writerIndex, value);
         writerIndex += 3;
         return this;
     }
    
     @Override
     public ByteBuf writeMediumLE(int value) {
         ensureWritable0(3);
         _setMediumLE(writerIndex, value);
         writerIndex += 3;
         return this;
     }
    
  3. 写入 int 类型数据
     @Override
     public ByteBuf writeInt(int value) {
         ensureWritable0(4);
         _setInt(writerIndex, value);
         writerIndex += 4;
         return this;
     }
    
     @Override
     public ByteBuf writeIntLE(int value) {
         ensureWritable0(4);
         _setIntLE(writerIndex, value);
         writerIndex += 4;
         return this;
     }
     @Override
     public ByteBuf writeFloat(float value) {
         writeInt(Float.floatToRawIntBits(value));
         return this;
     }
    
  4. 写入 long 类型数据
     @Override
     public ByteBuf writeLong(long value) {
         ensureWritable0(8);
         _setLong(writerIndex, value);
         writerIndex += 8;
         return this;
     }
    
     @Override
     public ByteBuf writeLongLE(long value) {
         ensureWritable0(8);
         _setLongLE(writerIndex, value);
         writerIndex += 8;
         return this;
     }
     @Override
     public ByteBuf writeDouble(double value) {
         writeLong(Double.doubleToRawLongBits(value));
         return this;
     }
    

3.6.2 将字节数组的数据写入到缓存区

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

3.6.3 将其他缓存区ByteBuf数据写入到缓存区

    @Override
    public ByteBuf writeBytes(ByteBuf src) {
        writeBytes(src, src.readableBytes());
        return this;
    }
    @Override
    public ByteBuf writeBytes(ByteBuf src, int length) {
        if (checkBounds) {
            checkReadableBounds(src, length);
        }
        writeBytes(src, src.readerIndex(), length);
        src.readerIndex(src.readerIndex() + length);
        return this;
    }
    @Override
    public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
        ensureWritable(length);
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }

前两个方法会增加源缓存区src 读索引readerIndex 的值。

3.6.4 将NIO 缓存区ByteBuffer的数据写入到缓存区

    @Override
    public ByteBuf writeBytes(ByteBuffer src) {
        int length = src.remaining();
        ensureWritable0(length);
        setBytes(writerIndex, src);
        writerIndex += length;
        return this;
    }

3.6.5 从输入流中读取数据写入到缓存区

    @Override
    public int writeBytes(InputStream in, int length)
            throws IOException {
        ensureWritable(length);
        int writtenBytes = setBytes(writerIndex, in, length);
        if (writtenBytes > 0) {
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }

    @Override
    public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
        ensureWritable(length);
        int writtenBytes = setBytes(writerIndex, in, length);
        if (writtenBytes > 0) {
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }
    @Override
    public int writeBytes(FileChannel in, long position, int length) throws IOException {
        ensureWritable(length);
        int writtenBytes = setBytes(writerIndex, in, position, length);
        if (writtenBytes > 0) {
            writerIndex += writtenBytes;
        }
        return writtenBytes;
    }

返回值表示写入缓存区的数据大小。

3.7 搜索

3.7.1 简单搜索

  1. indexOf(int fromIndex, int toIndex, byte value)
     @Override
     public int indexOf(int fromIndex, int toIndex, byte value) {
         if (fromIndex <= toIndex) {
             return ByteBufUtil.firstIndexOf(this, fromIndex, toIndex, value);
         }
         return ByteBufUtil.lastIndexOf(this, fromIndex, toIndex, value);
     }
    

    fromIndex <= toIndex 是升序搜索,反之就是降序搜索。
    搜索范围,如果是升序,那么就是 fromIndex(包括)到toIndex(排除);如果是降序,那么就是fromIndex(排除)到toIndex(包括),因为这个时候fromIndex的值比 toIndex 大。因此就是小的值会被包括,大的值会被排除。

  2. bytesBefore() 系列方法
     @Override
     public int bytesBefore(byte value) {
         return bytesBefore(readerIndex(), readableBytes(), value);
     }
    
     @Override
     public int bytesBefore(int length, byte value) {
         checkReadableBytes(length);
         return bytesBefore(readerIndex(), length, value);
     }
    
     @Override
     public int bytesBefore(int index, int length, byte value) {
         int endIndex = indexOf(index, index + length, value);
         if (endIndex < 0) {
             return -1;
         }
         return endIndex - index;
     }
    
    • 最终通过 indexOf 方法来实现的。
    • 它只能升序查找。

3.7.2 复杂搜索

  1. 升序复杂搜索

     @Override
     public int forEachByte(ByteProcessor processor) {
         ensureAccessible();
         try {
             return forEachByteAsc0(readerIndex, writerIndex, processor);
         } catch (Exception e) {
             PlatformDependent.throwException(e);
             return -1;
         }
     }
     @Override
     public int forEachByte(int index, int length, ByteProcessor processor) {
         checkIndex(index, length);
         try {
             return forEachByteAsc0(index, index + length, processor);
         } catch (Exception e) {
             PlatformDependent.throwException(e);
             return -1;
         }
     }
     int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception {
         // 升序遍历
         for (; start < end; ++start) {
             // 当 processor.process 返回false,表示查找到了,返回查找到的位置
             if (!processor.process(_getByte(start))) {
                 return start;
             }
         }
    
         // -1 表示查找不到
         return -1;
     }
    

    注意当 processor.process 方法返回false,表示查找到了。

  2. 降序复杂搜索

     @Override
     public int forEachByteDesc(ByteProcessor processor) {
         ensureAccessible();
         try {
             return forEachByteDesc0(writerIndex - 1, readerIndex, processor);
         } catch (Exception e) {
             PlatformDependent.throwException(e);
             return -1;
         }
     }
     @Override
     public int forEachByteDesc(int index, int length, ByteProcessor processor) {
         checkIndex(index, length);
         try {
             return forEachByteDesc0(index + length - 1, index, processor);
         } catch (Exception e) {
             PlatformDependent.throwException(e);
             return -1;
         }
     }
     int forEachByteDesc0(int rStart, final int rEnd, ByteProcessor processor) throws Exception {
         // 降序遍历
         for (; rStart >= rEnd; --rStart) {
             // 当 processor.process 返回false,表示查找到了,返回查找到的位置
             if (!processor.process(_getByte(rStart))) {
                 return rStart;
             }
         }
         // -1 表示查找不到
         return -1;
     }
    

3.8 复制缓存区

    @Override
    public ByteBuf copy() {
        return copy(readerIndex, readableBytes());
    }

最后调用 copy(readerIndex, readableBytes()) 方法,交给子类实现。

3.9 派生的缓冲区

 @Override
    public ByteBuf duplicate() {
        ensureAccessible();
        return new UnpooledDuplicatedByteBuf(this);
    }

    @Override
    public ByteBuf retainedDuplicate() {
        return duplicate().retain();
    }

    @Override
    public ByteBuf slice() {
        return slice(readerIndex, readableBytes());
    }

    @Override
    public ByteBuf retainedSlice() {
        return slice().retain();
    }

    @Override
    public ByteBuf slice(int index, int length) {
        ensureAccessible();
        return new UnpooledSlicedByteBuf(this, index, length);
    }

    @Override
    public ByteBuf retainedSlice(int index, int length) {
        return slice(index, length).retain();
    }

3.10 转换成 ByteBuffer

    @Override
    public ByteBuffer nioBuffer() {
        return nioBuffer(readerIndex, readableBytes());
    }

    @Override
    public ByteBuffer[] nioBuffers() {
        return nioBuffers(readerIndex, readableBytes());
    }

这实现了这两个方法,剩余方法交给具体子类实现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容