Netty 学习之旅:ByteBuf 篇之 ByteBuf 内部结构与 API 学习
1、原生 ByteBuffer、ByteBuf 内部结构设计
首先我们来回顾一下 java.nio.ByteBuffe r的内部结构设计。
ByteBuffer 内部持有一个 byte[] bt, 再加上 position、limit、capacity、mark 四个属性。
- position
当前可用操作的位置,调用 get() 方法,返回 bt[postion] 处的值,如果是调用 put 方法,将数据放入 bt[position] 的位置。 - limit
可读或可写的最大索引。 - capacity
最大容量,ByteBuffer 对象一旦创建,不能修改 capacity 属性。 - mark
用来标记当前位置,以便可用回退到该位置,默认为 undefind。
下面主要举例说明一下 ByteBuffer 的存储。
ByteBuffer bf = ByteBuffer.alloct(10);
bf.put( (byte)"h" );
bf.put( (byte)"e" );
bf.put( (byte)"l" );
bf.put( (byte)"l" );
bf.put( (byte)"o" );
将产生下图的结构:
那我们怎么从这个 ByteBuffer 中读取数据呢?如果调用 get 方法将会读出位置为5的数据,无法读取到 hello, 需要将 ByteBuffer 翻转,从写模式切换到读模式。
ByteBuffer 提供了一个方法 flip 方法,翻转缓存区,其如下如下:
bf.limit = bf.postion;
bf.positioin = 0;
关于 ByteBuffer 更多知识,建议下载一本电子书(《java NIO 中文版》)
为了简化缓存区的读、写的操作, Netty ByteBuf 引入了两个位置属性,readIndex,writeIndex,来取代position。这样就不需要在读、写之间切换了。其结构设计如下:
- discardable bytes
已读的内容。 - readable bytes
暂未读的内容。 - writable bytes
区域是可以写区域。
discardable bytes 区域是可以使用 discardReadBytes() 方法,将 readables bytes 丢弃,然后将 readable bytes 移动到 byte 数组的开始处,这也相当于 ByteBuffer的compact()方法(压缩),但这会产生缓存区内部元素的移动,会降低性能。
由于引入了 writerIndex, limit 属性不需要了。
上述简单介绍了ByteBuffer、ByteBuf 数据的内部存储,这对我们正确使用缓存区至关重要。
2、ByteBuf类图与常用API学习
2.1 ByteBuf 类图
现阶段主要描述一下主要类的作用。
- AbstractReferenceCountedByteBuf
不难理解,ByteBuf,一个最最重要的方面就是内存的管理,涉及到创建、重复利用、回收等方方面面,而AbstractReferenceCountedByteBuf 就是使用引用计数来管理内存的回收。 - CompositeByteBuf ByteBuf
组合ByteBuf,比如一个协议分为协议头,协议主体,这两部分都是一个单独的 ByteBuf, 在处理这两个ByteBuf 时,然后需要将这个数据在网络上传输。我们的常规做法,是需要创建一个更大的 ByteBuf, 然后将 head,body 的内容写入新创建的ByteBuf, CompositeByteBuf 就是用来解决这个,提供一个类似数据库视图这样的概念。 - PooledByteBuf
池化的 ByteBuf, ByteBuf 对象池. - UnpooledDirectByteBuf
非池化的直接内存(不占用 JVM 的堆内存,也叫堆外内存)。 - UnpooledHeapByteBuf
非池化的堆内存(占用JVM堆内存)。
2.2 ByteBuf 主要 API
顺序读写API(read、write) 相关API,会移动对应的读写指针,而随机访问API(get、put)相关API并不会改变读写指针。
2.2.1 顺序读API
/*** Gets a boolean at the current {@code readerIndex} and increases* the {@code readerIndex} by {@code 1} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 1}*/public abstract boolean readBoolean();/*** Gets a byte at the current {@code readerIndex} and increases* the {@code readerIndex} by {@code 1} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 1}*/public abstract byte readByte();/*** Gets an unsigned byte at the current {@code readerIndex} and increases* the {@code readerIndex} by {@code 1} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 1}*/public abstract short readUnsignedByte();/*** Gets a 16-bit short integer at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 2} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 2}*/public abstract short readShort();/*** Gets an unsigned 16-bit short integer at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 2} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 2}*/public abstract int readUnsignedShort();/*** Gets a 24-bit medium integer at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 3} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 3}*/public abstract int readMedium();/*** Gets an unsigned 24-bit medium integer at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 3} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 3}*/public abstract int readUnsignedMedium();/*** Gets a 32-bit integer at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 4} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 4}*/public abstract int readInt();/*** Gets an unsigned 32-bit integer at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 4} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 4}*/public abstract long readUnsignedInt();/*** Gets a 64-bit integer at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 8} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 8}*/public abstract long readLong();/*** Gets a 2-byte UTF-16 character at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 2} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 2}*/public abstract char readChar();/*** Gets a 32-bit floating point number at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 4} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 4}*/public abstract float readFloat();/*** Gets a 64-bit floating point number at the current {@code readerIndex}* and increases the {@code readerIndex} by {@code 8} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.readableBytes} is less than {@code 8}*/public abstract double readDouble();/*** Transfers this buffer's data to a newly created buffer starting at* the current {@code readerIndex} and increases the {@code readerIndex}* by the number of the transferred bytes (= {@code length}).* The returned buffer's {@code readerIndex} and {@code writerIndex} are* {@code 0} and {@code length} respectively.** @param length the number of bytes to transfer** @return the newly created buffer which contains the transferred bytes** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.readableBytes}*/public abstract ByteBuf readBytes(int length);/*** Returns a new slice of this buffer's sub-region starting at the current* {@code readerIndex} and increases the {@code readerIndex} by the size* of the new slice (= {@code length}).** @param length the size of the new slice** @return the newly created slice** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.readableBytes}*/public abstract ByteBuf readSlice(int length);/*** Transfers this buffer's data to the specified destination starting at* the current {@code readerIndex} until the destination becomes* non-writable, and increases the {@code readerIndex} by the number of the* transferred bytes. This method is basically same with* {@link #readBytes(ByteBuf, int, int)}, except that this method* increases the {@code writerIndex} of the destination by the number of* the transferred bytes while {@link #readBytes(ByteBuf, int, int)}* does not.** @throws IndexOutOfBoundsException* if {@code dst.writableBytes} is greater than* {@code this.readableBytes}*/public abstract ByteBuf readBytes(ByteBuf dst);/*** Transfers this buffer's data to the specified destination starting at* the current {@code readerIndex} and increases the {@code readerIndex}* by the number of the transferred bytes (= {@code length}). This method* is basically same with {@link #readBytes(ByteBuf, int, int)},* except that this method increases the {@code writerIndex} of the* destination by the number of the transferred bytes (= {@code length})* while {@link #readBytes(ByteBuf, int, int)} does not.** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.readableBytes} or* if {@code length} is greater than {@code dst.writableBytes}*/public abstract ByteBuf readBytes(ByteBuf dst, int length);/*** Transfers this buffer's data to the specified destination starting at* the current {@code readerIndex} and increases the {@code readerIndex}* by the number of the transferred bytes (= {@code length}).** @param dstIndex the first index of the destination* @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code dstIndex} is less than {@code 0},* if {@code length} is greater than {@code this.readableBytes}, or* if {@code dstIndex + length} is greater than* {@code dst.capacity}*/public abstract ByteBuf readBytes(ByteBuf dst, int dstIndex, int length);/*** Transfers this buffer's data to the specified destination starting at* the current {@code readerIndex} and increases the {@code readerIndex}* by the number of the transferred bytes (= {@code dst.length}).** @throws IndexOutOfBoundsException* if {@code dst.length} is greater than {@code this.readableBytes}*/public abstract ByteBuf readBytes(byte[] dst);/*** Transfers this buffer's data to the specified destination starting at* the current {@code readerIndex} and increases the {@code readerIndex}* by the number of the transferred bytes (= {@code length}).** @param dstIndex the first index of the destination* @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code dstIndex} is less than {@code 0},* if {@code length} is greater than {@code this.readableBytes}, or* if {@code dstIndex + length} is greater than {@code dst.length}*/public abstract ByteBuf readBytes(byte[] dst, int dstIndex, int length);/*** Transfers this buffer's data to the specified destination starting at* the current {@code readerIndex} until the destination's position* reaches its limit, and increases the {@code readerIndex} by the* number of the transferred bytes.** @throws IndexOutOfBoundsException* if {@code dst.remaining()} is greater than* {@code this.readableBytes}*/public abstract ByteBuf readBytes(ByteBuffer dst);/*** Transfers this buffer's data to the specified stream starting at the* current {@code readerIndex}.** @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.readableBytes}* @throws IOException* if the specified stream threw an exception during I/O*/public abstract ByteBuf readBytes(OutputStream out, int length) throws IOException;/*** Transfers this buffer's data to the specified stream starting at the* current {@code readerIndex}.** @param length the maximum number of bytes to transfer** @return the actual number of bytes written out to the specified channel** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.readableBytes}* @throws IOException* if the specified channel threw an exception during I/O*/public abstract int readBytes(GatheringByteChannel out, int length) throws IOException;
对于上面的方法,我挑如下三个进行重点说明一下,其他的类似:
- public abstract ByteBuf readBytes(byte[] dst)
从 ByteBuf 缓存区中读取 dst.length 个字节到 dst 目标数组中,返回当前的 ByteBuf, 这是级联设计风格,如果 ByteBuf 可剩余可读字节小于 dst.length,则抛出异常。 - public abstract ByteBuf readBytes(int length)
从 ByteBuf 缓存区读取 length 个字节到新创建的 ByteBuf, 注意返回的 ByteBuf 是先创建的,这不是级联设计风格。 - public abstract ByteBuf readBytes(ByteBuf dst)
从 ByteBuf 缓存区读取 ds t可写字节长度到 dst 中,返回的 ByteBuf 是级联风格设计,如果 dst 的可剩余写长度大于操作的缓存区时,将抛出异常。 - public abstract ByteBuf readSlice(int length)
从 ButeBuf 缓存区读取 length 个字节到 ByteBuf 中,这里的 ByteBuf 是原 ByteBuf 的视图,独立的 readIndex, writerIndex。
2.2.2 顺序写API
/*** Sets the specified boolean at the current {@code writerIndex}* and increases the {@code writerIndex} by {@code 1} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 1}*/public abstract ByteBuf writeBoolean(boolean value);/*** Sets the specified byte at the current {@code writerIndex}* and increases the {@code writerIndex} by {@code 1} in this buffer.* The 24 high-order bits of the specified value are ignored.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 1}*/public abstract ByteBuf writeByte(int value);/*** Sets the specified 16-bit short integer at the current* {@code writerIndex} and increases the {@code writerIndex} by {@code 2}* in this buffer. The 16 high-order bits of the specified value are ignored.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 2}*/public abstract ByteBuf writeShort(int value);/*** Sets the specified 24-bit medium integer at the current* {@code writerIndex} and increases the {@code writerIndex} by {@code 3}* in this buffer.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 3}*/public abstract ByteBuf writeMedium(int value);/*** Sets the specified 32-bit integer at the current {@code writerIndex}* and increases the {@code writerIndex} by {@code 4} in this buffer.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 4}*/public abstract ByteBuf writeInt(int value);/*** Sets the specified 64-bit long integer at the current* {@code writerIndex} and increases the {@code writerIndex} by {@code 8}* in this buffer.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 8}*/public abstract ByteBuf writeLong(long value);/*** Sets the specified 2-byte UTF-16 character at the current* {@code writerIndex} and increases the {@code writerIndex} by {@code 2}* in this buffer. The 16 high-order bits of the specified value are ignored.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 2}*/public abstract ByteBuf writeChar(int value);/*** Sets the specified 32-bit floating point number at the current* {@code writerIndex} and increases the {@code writerIndex} by {@code 4}* in this buffer.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 4}*/public abstract ByteBuf writeFloat(float value);/*** Sets the specified 64-bit floating point number at the current* {@code writerIndex} and increases the {@code writerIndex} by {@code 8}* in this buffer.** @throws IndexOutOfBoundsException* if {@code this.writableBytes} is less than {@code 8}*/public abstract ByteBuf writeDouble(double value);/*** Transfers the specified source buffer's data to this buffer starting at* the current {@code writerIndex} until the source buffer becomes* unreadable, and increases the {@code writerIndex} by the number of* the transferred bytes. This method is basically same with* {@link #writeBytes(ByteBuf, int, int)}, except that this method* increases the {@code readerIndex} of the source buffer by the number of* the transferred bytes while {@link #writeBytes(ByteBuf, int, int)}* does not.** @throws IndexOutOfBoundsException* if {@code src.readableBytes} is greater than* {@code this.writableBytes}*/public abstract ByteBuf writeBytes(ByteBuf src);/*** Transfers the specified source buffer's data to this buffer starting at* the current {@code writerIndex} and increases the {@code writerIndex}* by the number of the transferred bytes (= {@code length}). This method* is basically same with {@link #writeBytes(ByteBuf, int, int)},* except that this method increases the {@code readerIndex} of the source* buffer by the number of the transferred bytes (= {@code length}) while* {@link #writeBytes(ByteBuf, int, int)} does not.** @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.writableBytes} or* if {@code length} is greater then {@code src.readableBytes}*/public abstract ByteBuf writeBytes(ByteBuf src, int length);/*** Transfers the specified source buffer's data to this buffer starting at* the current {@code writerIndex} and increases the {@code writerIndex}* by the number of the transferred bytes (= {@code length}).** @param srcIndex the first index of the source* @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code srcIndex} is less than {@code 0},* if {@code srcIndex + length} is greater than* {@code src.capacity}, or* if {@code length} is greater than {@code this.writableBytes}*/public abstract ByteBuf writeBytes(ByteBuf src, int srcIndex, int length);/*** Transfers the specified source array's data to this buffer starting at* the current {@code writerIndex} and increases the {@code writerIndex}* by the number of the transferred bytes (= {@code src.length}).** @throws IndexOutOfBoundsException* if {@code src.length} is greater than {@code this.writableBytes}*/public abstract ByteBuf writeBytes(byte[] src);/*** Transfers the specified source array's data to this buffer starting at* the current {@code writerIndex} and increases the {@code writerIndex}* by the number of the transferred bytes (= {@code length}).** @param srcIndex the first index of the source* @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code srcIndex} is less than {@code 0},* if {@code srcIndex + length} is greater than* {@code src.length}, or* if {@code length} is greater than {@code this.writableBytes}*/public abstract ByteBuf writeBytes(byte[] src, int srcIndex, int length);/*** Transfers the specified source buffer's data to this buffer starting at* the current {@code writerIndex} until the source buffer's position* reaches its limit, and increases the {@code writerIndex} by the* number of the transferred bytes.** @throws IndexOutOfBoundsException* if {@code src.remaining()} is greater than* {@code this.writableBytes}*/public abstract ByteBuf writeBytes(ByteBuffer src);/*** Transfers the content of the specified stream to this buffer* starting at the current {@code writerIndex} and increases the* {@code writerIndex} by the number of the transferred bytes.** @param length the number of bytes to transfer** @return the actual number of bytes read in from the specified stream** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.writableBytes}* @throws IOException* if the specified stream threw an exception during I/O*/public abstract int writeBytes(InputStream in, int length) throws IOException;/*** Transfers the content of the specified channel to this buffer* starting at the current {@code writerIndex} and increases the* {@code writerIndex} by the number of the transferred bytes.** @param length the maximum number of bytes to transfer** @return the actual number of bytes read in from the specified channel** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.writableBytes}* @throws IOException* if the specified channel threw an exception during I/O*/public abstract int writeBytes(ScatteringByteChannel in, int length) throws IOException;/*** Fills this buffer with <tt>NUL (0x00)</tt> starting at the current* {@code writerIndex} and increases the {@code writerIndex} by the* specified {@code length}.** @param length the number of <tt>NUL</tt>s to write to the buffer** @throws IndexOutOfBoundsException* if {@code length} is greater than {@code this.writableBytes}*/public abstract ByteBuf writeZero(int length);
重点强调一下如下方法,其他的类似。
- public abstract ByteBuf writeBytes(byte[] src)
将字节数组 src 全部写入到缓存区中,如果 src.length 大于 ByteBuf 可写区域的话,会抛出异常。
2.2.3 随机读API
/*** Gets a boolean at the specified absolute (@code index) in this buffer.* This method does not modify the {@code readerIndex} or {@code writerIndex}* of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 1} is greater than {@code this.capacity}*/public abstract boolean getBoolean(int index);/*** Gets a byte at the specified absolute {@code index} in this buffer.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 1} is greater than {@code this.capacity}*/public abstract byte getByte(int index);/*** Gets an unsigned byte at the specified absolute {@code index} in this* buffer. This method does not modify {@code readerIndex} or* {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 1} is greater than {@code this.capacity}*/public abstract short getUnsignedByte(int index);/*** Gets a 16-bit short integer at the specified absolute {@code index} in* this buffer. This method does not modify {@code readerIndex} or* {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 2} is greater than {@code this.capacity}*/public abstract short getShort(int index);/*** Gets an unsigned 16-bit short integer at the specified absolute* {@code index} in this buffer. This method does not modify* {@code readerIndex} or {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 2} is greater than {@code this.capacity}*/public abstract int getUnsignedShort(int index);/*** Gets a 24-bit medium integer at the specified absolute {@code index} in* this buffer. This method does not modify {@code readerIndex} or* {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 3} is greater than {@code this.capacity}*/public abstract int getMedium(int index);/*** Gets an unsigned 24-bit medium integer at the specified absolute* {@code index} in this buffer. This method does not modify* {@code readerIndex} or {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 3} is greater than {@code this.capacity}*/public abstract int getUnsignedMedium(int index);/*** Gets a 32-bit integer at the specified absolute {@code index} in* this buffer. This method does not modify {@code readerIndex} or* {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 4} is greater than {@code this.capacity}*/public abstract int getInt(int index);/*** Gets an unsigned 32-bit integer at the specified absolute {@code index}* in this buffer. This method does not modify {@code readerIndex} or* {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 4} is greater than {@code this.capacity}*/public abstract long getUnsignedInt(int index);/*** Gets a 64-bit long integer at the specified absolute {@code index} in* this buffer. This method does not modify {@code readerIndex} or* {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 8} is greater than {@code this.capacity}*/public abstract long getLong(int index);/*** Gets a 2-byte UTF-16 character at the specified absolute* {@code index} in this buffer. This method does not modify* {@code readerIndex} or {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 2} is greater than {@code this.capacity}*/public abstract char getChar(int index);/*** Gets a 32-bit floating point number at the specified absolute* {@code index} in this buffer. This method does not modify* {@code readerIndex} or {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 4} is greater than {@code this.capacity}*/public abstract float getFloat(int index);/*** Gets a 64-bit floating point number at the specified absolute* {@code index} in this buffer. This method does not modify* {@code readerIndex} or {@code writerIndex} of this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 8} is greater than {@code this.capacity}*/public abstract double getDouble(int index);/*** Transfers this buffer's data to the specified destination starting at* the specified absolute {@code index} until the destination becomes* non-writable. This method is basically same with* {@link #getBytes(int, ByteBuf, int, int)}, except that this* method increases the {@code writerIndex} of the destination by the* number of the transferred bytes while* {@link #getBytes(int, ByteBuf, int, int)} does not.* This method does not modify {@code readerIndex} or {@code writerIndex} of* the source buffer (i.e. {@code this}).** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + dst.writableBytes} is greater than* {@code this.capacity}*/public abstract ByteBuf getBytes(int index, ByteBuf dst);/*** Transfers this buffer's data to the specified destination starting at* the specified absolute {@code index}. This method is basically same* with {@link #getBytes(int, ByteBuf, int, int)}, except that this* method increases the {@code writerIndex} of the destination by the* number of the transferred bytes while* {@link #getBytes(int, ByteBuf, int, int)} does not.* This method does not modify {@code readerIndex} or {@code writerIndex} of* the source buffer (i.e. {@code this}).** @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0},* if {@code index + length} is greater than* {@code this.capacity}, or* if {@code length} is greater than {@code dst.writableBytes}*/public abstract ByteBuf getBytes(int index, ByteBuf dst, int length);/*** Transfers this buffer's data to the specified destination starting at* the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex}* of both the source (i.e. {@code this}) and the destination.** @param dstIndex the first index of the destination* @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0},* if the specified {@code dstIndex} is less than {@code 0},* if {@code index + length} is greater than* {@code this.capacity}, or* if {@code dstIndex + length} is greater than* {@code dst.capacity}*/public abstract ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length);/*** Transfers this buffer's data to the specified destination starting at* the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + dst.length} is greater than* {@code this.capacity}*/public abstract ByteBuf getBytes(int index, byte[] dst);/*** Transfers this buffer's data to the specified destination starting at* the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex}* of this buffer.** @param dstIndex the first index of the destination* @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0},* if the specified {@code dstIndex} is less than {@code 0},* if {@code index + length} is greater than* {@code this.capacity}, or* if {@code dstIndex + length} is greater than* {@code dst.length}*/public abstract ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length);/*** Transfers this buffer's data to the specified destination starting at* the specified absolute {@code index} until the destination's position* reaches its limit.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer while the destination's {@code position} will be increased.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + dst.remaining()} is greater than* {@code this.capacity}*/public abstract ByteBuf getBytes(int index, ByteBuffer dst);/*** Transfers this buffer's data to the specified stream starting at the* specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + length} is greater than* {@code this.capacity}* @throws IOException* if the specified stream threw an exception during I/O*/public abstract ByteBuf getBytes(int index, OutputStream out, int length) throws IOException;/*** Transfers this buffer's data to the specified channel starting at the* specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @param length the maximum number of bytes to transfer** @return the actual number of bytes written out to the specified channel** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + length} is greater than* {@code this.capacity}* @throws IOException* if the specified channel threw an exception during I/O*/public abstract int getBytes(int index, GatheringByteChannel out, int length) throws IOException;
重点解读如下API,其他类似。
- public abstract ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length)
从源 ByteBuf 的 index 处开始读 length 个字节到目标 byte 中,从 dstIndex 开始。
2.2.4 随机写API
/*** Sets the specified boolean at the specified absolute {@code index} in this* buffer.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 1} is greater than {@code this.capacity}*/public abstract ByteBuf setBoolean(int index, boolean value);/*** Sets the specified byte at the specified absolute {@code index} in this* buffer. The 24 high-order bits of the specified value are ignored.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 1} is greater than {@code this.capacity}*/public abstract ByteBuf setByte(int index, int value);/*** Sets the specified 16-bit short integer at the specified absolute* {@code index} in this buffer. The 16 high-order bits of the specified* value are ignored.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 2} is greater than {@code this.capacity}*/public abstract ByteBuf setShort(int index, int value);/*** Sets the specified 24-bit medium integer at the specified absolute* {@code index} in this buffer. Please note that the most significant* byte is ignored in the specified value.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 3} is greater than {@code this.capacity}*/public abstract ByteBuf setMedium(int index, int value);/*** Sets the specified 32-bit integer at the specified absolute* {@code index} in this buffer.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 4} is greater than {@code this.capacity}*/public abstract ByteBuf setInt(int index, int value);/*** Sets the specified 64-bit long integer at the specified absolute* {@code index} in this buffer.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 8} is greater than {@code this.capacity}*/public abstract ByteBuf setLong(int index, long value);/*** Sets the specified 2-byte UTF-16 character at the specified absolute* {@code index} in this buffer.* The 16 high-order bits of the specified value are ignored.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 2} is greater than {@code this.capacity}*/public abstract ByteBuf setChar(int index, int value);/*** Sets the specified 32-bit floating-point number at the specified* absolute {@code index} in this buffer.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 4} is greater than {@code this.capacity}*/public abstract ByteBuf setFloat(int index, float value);/*** Sets the specified 64-bit floating-point number at the specified* absolute {@code index} in this buffer.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* {@code index + 8} is greater than {@code this.capacity}*/public abstract ByteBuf setDouble(int index, double value);/*** Transfers the specified source buffer's data to this buffer starting at* the specified absolute {@code index} until the source buffer becomes* unreadable. This method is basically same with* {@link #setBytes(int, ByteBuf, int, int)}, except that this* method increases the {@code readerIndex} of the source buffer by* the number of the transferred bytes while* {@link #setBytes(int, ByteBuf, int, int)} does not.* This method does not modify {@code readerIndex} or {@code writerIndex} of* the source buffer (i.e. {@code this}).** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + src.readableBytes} is greater than* {@code this.capacity}*/public abstract ByteBuf setBytes(int index, ByteBuf src);/*** Transfers the specified source buffer's data to this buffer starting at* the specified absolute {@code index}. This method is basically same* with {@link #setBytes(int, ByteBuf, int, int)}, except that this* method increases the {@code readerIndex} of the source buffer by* the number of the transferred bytes while* {@link #setBytes(int, ByteBuf, int, int)} does not.* This method does not modify {@code readerIndex} or {@code writerIndex} of* the source buffer (i.e. {@code this}).** @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0},* if {@code index + length} is greater than* {@code this.capacity}, or* if {@code length} is greater than {@code src.readableBytes}*/public abstract ByteBuf setBytes(int index, ByteBuf src, int length);/*** Transfers the specified source buffer's data to this buffer starting at* the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex}* of both the source (i.e. {@code this}) and the destination.** @param srcIndex the first index of the source* @param length the number of bytes to transfer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0},* if the specified {@code srcIndex} is less than {@code 0},* if {@code index + length} is greater than* {@code this.capacity}, or* if {@code srcIndex + length} is greater than* {@code src.capacity}*/public abstract ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length);/*** Transfers the specified source array's data to this buffer starting at* the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + src.length} is greater than* {@code this.capacity}*/public abstract ByteBuf setBytes(int index, byte[] src);/*** Transfers the specified source array's data to this buffer starting at* the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0},* if the specified {@code srcIndex} is less than {@code 0},* if {@code index + length} is greater than* {@code this.capacity}, or* if {@code srcIndex + length} is greater than {@code src.length}*/public abstract ByteBuf setBytes(int index, byte[] src, int srcIndex, int length);/*** Transfers the specified source buffer's data to this buffer starting at* the specified absolute {@code index} until the source buffer's position* reaches its limit.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + src.remaining()} is greater than* {@code this.capacity}*/public abstract ByteBuf setBytes(int index, ByteBuffer src);/*** Transfers the content of the specified source stream to this buffer* starting at the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @param length the number of bytes to transfer** @return the actual number of bytes read in from the specified channel.* {@code -1} if the specified channel is closed.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + length} is greater than {@code this.capacity}* @throws IOException* if the specified stream threw an exception during I/O*/public abstract int setBytes(int index, InputStream in, int length) throws IOException;/*** Transfers the content of the specified source channel to this buffer* starting at the specified absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @param length the maximum number of bytes to transfer** @return the actual number of bytes read in from the specified channel.* {@code -1} if the specified channel is closed.** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + length} is greater than {@code this.capacity}* @throws IOException* if the specified channel threw an exception during I/O*/public abstract int setBytes(int index, ScatteringByteChannel in, int length) throws IOException;/*** Fills this buffer with <tt>NUL (0x00)</tt> starting at the specified* absolute {@code index}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.** @param length the number of <tt>NUL</tt>s to write to the buffer** @throws IndexOutOfBoundsException* if the specified {@code index} is less than {@code 0} or* if {@code index + length} is greater than {@code this.capacity}*/public abstract ByteBuf setZero(int index, int length);1.2.2.5 ByteBuf复制相关API
/*** Returns a copy of this buffer's readable bytes. Modifying the content* of the returned buffer or this buffer does not affect each other at all.* This method is identical to {@code buf.copy(buf.readerIndex(), buf.readableBytes())}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.*/public abstract ByteBuf copy();/*** Returns a copy of this buffer's sub-region. Modifying the content of* the returned buffer or this buffer does not affect each other at all.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.*/public abstract ByteBuf copy(int index, int length);/*** Returns a slice of this buffer's readable bytes. Modifying the content* of the returned buffer or this buffer affects each other's content* while they maintain separate indexes and marks. This method is* identical to {@code buf.slice(buf.readerIndex(), buf.readableBytes())}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.*/public abstract ByteBuf slice();/*** Returns a slice of this buffer's sub-region. Modifying the content of* the returned buffer or this buffer affects each other's content while* they maintain separate indexes and marks.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.*/public abstract ByteBuf slice(int index, int length);/*** Returns a buffer which shares the whole region of this buffer.* Modifying the content of the returned buffer or this buffer affects* each other's content while they maintain separate indexes and marks.* This method is identical to {@code buf.slice(0, buf.capacity())}.* This method does not modify {@code readerIndex} or {@code writerIndex} of* this buffer.*/public abstract ByteBuf duplicate();
需要说明的是,slice、duplicate 方法,返回的 ByteBuf 与原共享数据结构,只是单独维护 readIndex,writerIndex。copy 方法会创建不同的 byte[] 数组,相互之间不会影响。
2.2.7 ByteBuf mark,reset,clear 的 API
/*** Sets the {@code readerIndex} and {@code writerIndex} of this buffer to* {@code 0}.* This method is identical to {@link #setIndex(int, int) setIndex(0, 0)}.* <p>* Please note that the behavior of this method is different* from that of NIO buffer, which sets the {@code limit} to* the {@code capacity} of the buffer.*/public abstract ByteBuf clear();/*** Marks the current {@code readerIndex} in this buffer. You can* reposition the current {@code readerIndex} to the marked* {@code readerIndex} by calling {@link #resetReaderIndex()}.* The initial value of the marked {@code readerIndex} is {@code 0}.*/public abstract ByteBuf markReaderIndex();/*** Repositions the current {@code readerIndex} to the marked* {@code readerIndex} in this buffer.** @throws IndexOutOfBoundsException* if the current {@code writerIndex} is less than the marked* {@code readerIndex}*/public abstract ByteBuf resetReaderIndex();/*** Marks the current {@code writerIndex} in this buffer. You can* reposition the current {@code writerIndex} to the marked* {@code writerIndex} by calling {@link #resetWriterIndex()}.* The initial value of the marked {@code writerIndex} is {@code 0}.*/public abstract ByteBuf markWriterIndex();/*** Repositions the current {@code writerIndex} to the marked* {@code writerIndex} in this buffer.** @throws IndexOutOfBoundsException* if the current {@code readerIndex} is greater than the marked* {@code writerIndex}*/public abstract ByteBuf resetWriterIndex();/*** Discards the bytes between the 0th index and {@code readerIndex}.* It moves the bytes between {@code readerIndex} and {@code writerIndex}* to the 0th index, and sets {@code readerIndex} and {@code writerIndex}* to {@code 0} and {@code oldWriterIndex - oldReaderIndex} respectively.* <p>* Please refer to the class documentation for more detailed explanation.*/public abstract ByteBuf discardReadBytes();/*** Similar to {@link ByteBuf#discardReadBytes()} except that this method might discard* some, all, or none of read bytes depending on its internal implementation to reduce* overall memory bandwidth consumption at the cost of potentially additional memory* consumption.*/public abstract ByteBuf discardSomeReadBytes();
2.2.8 ByteBuf 与 java nio 原生 ByteBuffer 转换
/*** Returns the maximum number of NIO {@link ByteBuffer}s that consist this buffer. Note that {@link #nioBuffers()}* or {@link #nioBuffers(int, int)} might return a less number of {@link ByteBuffer}s.** @return {@code -1} if this buffer has no underlying {@link ByteBuffer}.* the number of the underlying {@link ByteBuffer}s if this buffer has at least one underlying* {@link ByteBuffer}. Note that this method does not return {@code 0} to avoid confusion.** @see #nioBuffer()* @see #nioBuffer(int, int)* @see #nioBuffers()* @see #nioBuffers(int, int)*/public abstract int nioBufferCount();/*** Exposes this buffer's readable bytes as an NIO {@link ByteBuffer}. The returned buffer* shares the content with this buffer, while changing the position and limit of the returned* NIO buffer does not affect the indexes and marks of this buffer. This method is identical* to {@code buf.nioBuffer(buf.readerIndex(), buf.readableBytes())}. This method does not* modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the* returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic* buffer and it adjusted its capacity.** @throws UnsupportedOperationException* if this buffer cannot create a {@link ByteBuffer} that shares the content with itself** @see #nioBufferCount()* @see #nioBuffers()* @see #nioBuffers(int, int)*/public abstract ByteBuffer nioBuffer();/*** Exposes this buffer's sub-region as an NIO {@link ByteBuffer}. The returned buffer* shares the content with this buffer, while changing the position and limit of the returned* NIO buffer does not affect the indexes and marks of this buffer. This method does not* modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the* returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic* buffer and it adjusted its capacity.** @throws UnsupportedOperationException* if this buffer cannot create a {@link ByteBuffer} that shares the content with itself** @see #nioBufferCount()* @see #nioBuffers()* @see #nioBuffers(int, int)*/public abstract ByteBuffer nioBuffer(int index, int length);/*** Internal use only: Exposes the internal NIO buffer.*/public abstract ByteBuffer internalNioBuffer(int index, int length);/*** Exposes this buffer's readable bytes as an NIO {@link ByteBuffer}'s. The returned buffer* shares the content with this buffer, while changing the position and limit of the returned* NIO buffer does not affect the indexes and marks of this buffer. This method does not* modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the* returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic* buffer and it adjusted its capacity.*** @throws UnsupportedOperationException* if this buffer cannot create a {@link ByteBuffer} that shares the content with itself** @see #nioBufferCount()* @see #nioBuffer()* @see #nioBuffer(int, int)*/public abstract ByteBuffer[] nioBuffers();/*** Exposes this buffer's bytes as an NIO {@link ByteBuffer}'s for the specified index and length* The returned buffer shares the content with this buffer, while changing the position and limit* of the returned NIO buffer does not affect the indexes and marks of this buffer. This method does* not modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the* returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic* buffer and it adjusted its capacity.** @throws UnsupportedOperationException* if this buffer cannot create a {@link ByteBuffer} that shares the content with itself** @see #nioBufferCount()* @see #nioBuffer()* @see #nioBuffer(int, int)*/public abstract ByteBuffer[] nioBuffers(int index, int length);
看了这么多的分类的API,貌似没有看到创建相关的API,比如java.nio.ByteBuffer的 allocateDirect(int capacity) 等方法。原因是Netty 对 ByteBuf 的创建单独抽象成一个体系 ByteBufAllocator,包括了直接内存、堆内存,池化的 ByteBuf,这些将在源码分析ByteBuf 相关类时重点去学习。
欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
1、源码分析RocketMQ专栏(40篇+)
2、源码分析Sentinel专栏(12篇+)
3、源码分析Dubbo专栏(28篇+)
4、源码分析Mybatis专栏
5、源码分析Netty专栏(18篇+)
6、源码分析JUC专栏
7、源码分析Elasticjob专栏
8、Elasticsearch专栏
9、源码分析Mycat专栏
Netty 学习之旅:ByteBuf 篇之 ByteBuf 内部结构与 API 学习相关推荐
- iOS 学习之旅 - OC 篇
不知不觉来微信事业群差不多也快一年了,自己学习 iOS 也已有半年,在此记录一下自己的学习过程.很多同学可能想知道从 Android 转到 iOS 公司给了多久的缓冲?大概是一周的样子,那时我还在做 ...
- Python学习之旅 —— 基础篇(二)数据类型、运算、while循环
本篇要点: 常量定义 数据类型(重点是字符串的各种操作.列表和字典) 各种运算 while 循环 一.常量定义 虽然叫做常量,但是python中的常量是可修改的.(c语言中的常量不可修改.)常量的定义 ...
- 提权学习之旅——基础篇
首发于先知社区 https://xz.aliyun.com/t/8054 前言: 无论是CTF赛题还是渗透测试,有很多时候拿到WebShell的权限并不高,没有办法继续深入,所以需要进行提权操作,方便 ...
- JavaScript学习之旅-导言篇
为什么会学习JavaScript 我的学习方法 目前我所使用的资料 学习所用时间 为什么会学习JavaScript? 首先,我认为学习任何东西都是为了解决问题.无关语言好坏.而web应用发展的趋势使J ...
- grbl学习之旅---serial篇
serial.c和serial.h文件是实现了通过串行端口发送和接受字节的功能. 首先是serial.h中定义了基本函数和常量大小: #ifndef RX_BUFFER_SIZE#define RX_ ...
- 测试开发学习之旅------进阶篇
习题课 因果图法 自动饮料的售卖 机,可以输入1.5毛钱或者两块钱硬币,一瓶饮料的价钱1.5 可乐,雪碧,红茶,按哪一种饮料,出哪一种饮料,如果输入2块钱按相应的饮料,出饮料的同时会找出5毛硬币 (1 ...
- WCF学习之旅----基础篇之EnterpriseServices
2019独角兽企业重金招聘Python工程师标准>>> server using System; using System.Collections.Generic; using Sy ...
- grbl学习之旅---protocol篇
protocol.c和protocol.h是实现控制grbl的方法和程序执行协议.涉及到了system.h;stepper.h;print.h;report.h; system.h 是系统级命令和实时 ...
- grbl学习之旅---protocol篇(补充)
protocol.c和protocol.h是实现控制grbl的方法和程序执行协议.涉及到了system.h;stepper.h;print.h;report.h; system.h 是系统级命令和实时 ...
最新文章
- 崔巍 计算机考研怎么样,中国科学院大学研究生导师教师师资介绍简介-崔巍
- oracle中where中使用函数,Oracle 尽量避免在 SQL语句的WHERE子句中使用函数
- lt;备份gt;10月12日 内核编译与日志
- Flask 中的Jinja2模板引擎
- spring boot 使用 websocket tomcat刚启动就关闭到问题
- 关于为什么需要设置request.setCharacterEncoding以及适用范围问题
- 进击的UI---------------- UITextFieldUIButton
- PVE安装Linux提示:error: /vmlinuz has invalid signature.
- 【数学建模】五:MATLAB优化模型求解方法(1):标准模型
- Pygame下载教程
- SQL数据库置疑修复
- 免费的WinCC语音报警控件
- 省份简称匹配并分组统计
- android流量显示插件,安卓状态栏显示网速(安卓网速显示插件)
- Nginx正则表达式locationrewrite
- MySQL 5.7 服务端 错误码 (机翻)
- 大一大学计算机导论论文,大学计算机导论论文3500字_大学计算机导论毕业论文范文模板.doc...
- 对话汇医慧影联合创始人郭娜:人工智能是分级诊疗的必然抓手
- Linux ubuntu gtest googletest download compile apply TEST TEST_F TEST_P
- 两个程序悲催的进化旅程
热门文章
- C语言源代码系列-管理系统之单项选择题标准化考试系统设计
- 德卡D3读卡器C#开发中的填坑记录
- html代码中文乱码解决
- Android SearchView基本用法
- mysql longtext_MySql中LongText类型大字段查询优化
- bigdecimal转换为long_BigDecimal与Long、int之间的相互转换
- 彻底删除MySQL57服务
- 如何使用抽象类和抽象方法
- 全球与中国住院EHR系统市场现状及未来发展趋势(2022)
- Python中的几种乘法np.dot,np.multiply,*