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

netty源码解解析(4.0)-22 ByteBuf的I/O的更多相关文章

  1. netty源码解解析(4.0)-24 ByteBuf基于内存池的内存管理

    io.netty.buffer.PooledByteBuf<T>使用内存池中的一块内存作为自己的数据内存,这个块内存是PoolChunk<T>的一部分.PooledByteBu ...

  2. netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放

    ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机. 内存分配分两个阶段: 第一阶段,初始化时分配内存.第二阶段: 内存不够用时分配新的内存.ByteBuf抽象层没有定义 ...

  3. netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk

    PoolArena实现了用于高效分配和释放内存,并尽可能减少内存碎片的内存池,这个内存管理实现使用PageRun/PoolSubpage算法.分析代码之前,先熟悉一些重要的概念: page: 页,一个 ...

  4. netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架

    编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...

  5. netty源码解解析(4.0)-11 Channel NIO实现-概览

      结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...

  6. netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理

    事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...

  7. netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现

    io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...

  8. netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端

    本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...

  9. netty源码解解析(4.0)-15 Channel NIO实现:写数据

    写数据是NIO Channel实现的另一个比较复杂的功能.每一个channel都有一个outboundBuffer,这是一个输出缓冲区.当调用channel的write方法写数据时,这个数据被一系列C ...

随机推荐

  1. sql server中的cte

    从SQL Server 2005开始,提供了CTE(Common Table Expression,公用表表达式)的语法支持. CTE是定义在SELECT.INSERT.UPDATE或DELETE语句 ...

  2. 分布式CAP理论

    分布式CAP理论 来自wiki: 在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下 ...

  3. 关于Function和Object之间先后问题的相关研究

    文章说明,博主是一个前端小白,本片文章是博主在学习的过程中碰到的疑惑,根据查找的资料,之后得出的个人结论,文中如果出现错误,欢迎指正. -------路漫漫其修远兮吾将上下而求索,与诸君共勉----- ...

  4. docker安装Ubuntu以及ssh连接

    一.简述 环境: Windows10 docker:2.1.0.1 二.开始安装 Windows的docker安装就不再多说了,网上有很多教程 在docker的hub仓库中,有专门的ubuntu系统. ...

  5. .net core 单元测试之 JustMock第二篇

    JustMock标记方法 上篇文章在举例子的时候使用了returns的标记方法,JustMock还有很多标记方法: CallOriginal 跟Behaviors里的CallOriginal差不多意思 ...

  6. com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect

    com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: con ...

  7. Springboot源码分析之事务拦截和管理

    摘要: 在springboot的自动装配事务里面,InfrastructureAdvisorAutoProxyCreator ,TransactionInterceptor,PlatformTrans ...

  8. Leetcode之回溯法专题-77. 组合(Combinations)

    Leetcode之回溯法专题-77. 组合(Combinations)   给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合. 示例: 输入: n = 4, k = 2 输 ...

  9. hdu 1182 A Bug's Life(简单种类并查集)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1829 题意:就是给你m条关系a与b有性关系,问这些关系中是否有同性恋 这是一道简单的种类并查集,而且也 ...

  10. Python——最美丽的编程语言

    之前总听别人说Python有多厉害,有多好,我是不以为然的.但是当我开始接触Python的时候,我就知道它被誉为最美编程语言不是没有道理的.简短的代码,清晰明了的逻辑,方便快捷的编程工具让它注定会大放 ...