缓冲区是包在一个对象内的基本数据元素数组,Buffer类相比一个简单的数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。
Buffer的属性
容量(capacity):
缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变
上界(limit):
缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数
位置(position):
下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新
标记(mark):
下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新一个备忘位置。调用 mark( )来设定 mark = postion。调用 reset( )设定 position =mark。标记在设定前是未定义的(undefined)。这四个属性之间总是遵循以下关系:0 <= mark <= position <= limit <= capacity
例如:
// mark=position=0;limit=capacity=256
ByteBuffer buf = ByteBuffer.allocate(256);
Buffer源码分析
package com.swk.nio;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.InvalidMarkException;
import java.nio.ReadOnlyBufferException;
public abstract class Buffer {
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
long address;
Buffer(int mark, int pos, int lim, int cap) {
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
* 返回Buffer的容量
* @author fuyuwei
* 2017年6月19日 下午9:23:19
* @return
public final int capacity() {
return capacity;
* 返回Buffer的位置
* @author fuyuwei
* 2017年6月19日 下午9:23:33
* @return
public final int position() {
return position;
* 设置Buffer的position,如果mark>position,则mark废弃
* @author fuyuwei
* 2017年6月19日 下午9:23:56
* @param newPosition
* @return
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
* 返回Buffer的上界
* @author fuyuwei
* 2017年6月19日 下午9:25:10
* @return
public final int limit() {
return limit;
* 设置Buffer的上界,如果position>limit,则把position设置为新的limit,如果mark>newLimit则废弃mark
* @author fuyuwei
* 2017年6月19日 下午9:25:26
* @param newLimit
* @return
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
* 将mark设置为position
* @author fuyuwei
* 2017年6月19日 下午9:28:02
* @return
public final Buffer mark() {
mark = position;
return this;
* 将buffer的position重置为先前的mark
* @author fuyuwei
* 2017年6月19日 下午9:28:32
* @return
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
* 用于写模式,清空buffer,将position置为0,limit置为capacity,丢弃mark
* 需要注意的此时buffer中仍有数据,当相同或不同线程再访问时可以直接从内存中获取
* @author fuyuwei
* 2017年6月19日 下午9:29:29
* @return
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
* 将写模式转变为读模式;翻转此buffer,首先对当前位置设置限制,然后将该位置设置为零。如果已定义了标记,则丢弃该标记
* @author fuyuwei
* 2017年6月19日 下午9:31:44
* @return
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
* rewind()在读写模式下都可用,它单纯的将当前位置置0,同时取消mark标记
* @author fuyuwei
* 2017年6月19日 下午9:34:48
* @return
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
* 获取当前位置到上限之间元素个数
* @author fuyuwei
* 2017年6月19日 下午9:35:12
* @return
public final int remaining() {
return limit - position;
* 是否还有剩余元素
* @author fuyuwei
* 2017年6月19日 下午9:35:51
* @return
public final boolean hasRemaining() {
return position < limit;
* 当前buffer是否是只读
* @author fuyuwei
* 2017年6月19日 下午9:36:16
* @return
public abstract boolean isReadOnly();
* 判断这个缓冲区是否被一个可访问数组所支持
* @author fuyuwei
* 2017年6月19日 下午9:38:38
* @return
public abstract boolean hasArray();
* 返回支持的数组
* 这种方法的目的是让数组支持的缓冲区更有效地传递给本地代码。具体的子类为这个方法提供了更强类型的返回值
* 对该缓冲区内容的修改将导致返回的数组内容被修改,反之亦然。
* 在调用这个方法之前,调用@link hasArray hasArray方法,以确保该缓冲区拥有一个可访问的支持数组。
* @author fuyuwei
* 2017年6月19日 下午9:39:11
* @return
public abstract Object array();
* 在缓冲区的第一个元素的支持数组中返回偏移量
* 如果这个缓冲区得到一个数组的支持那么缓冲位置对应于数组索引
* 在调用这个方法之前调用@link hasArray hasArray方法,以确保该缓冲区具有可访问的支持数组
* @author fuyuwei
* 2017年6月19日 下午9:40:52
* @return
public abstract int arrayOffset();
* Tells whether or not this buffer is
* <a href="ByteBuffer.html#direct"><i>direct</i></a>. </p>
* @return <tt>true</tt> if, and only if, this buffer is direct
* @since 1.6
public abstract boolean isDirect();
* 检查当前位置与限制,如果它不小于限制,则抛出一个@link BufferUnderflowException,否则增加该位置
* @author fuyuwei
* 2017年6月19日 下午9:43:00
* @return
final int nextGetIndex() {
if (position >= limit)
throw new BufferUnderflowException();
return position++;
* 增加指定值的position
* @author fuyuwei
* 2017年6月19日 下午9:44:05
* @param nb
* @return
final int nextGetIndex(int nb) {
if (limit - position < nb)
throw new BufferUnderflowException();
int p = position;
position += nb;
return p;
* Checks the current position against the limit, throwing a {@link
* BufferOverflowException} if it is not smaller than the limit, and then
* increments the position. </p>
* @return The current position value, before it is incremented
final int nextPutIndex() {
if (position >= limit)
throw new BufferOverflowException();
return position++;
final int nextPutIndex(int nb) {
if (limit - position < nb)
throw new BufferOverflowException();
int p = position;
position += nb;
return p;
* 检查给定索引的限制,如果它不小于limit或小于0,则抛出一个@link IndexOutOfBoundsException。
* @author fuyuwei
* 2017年6月19日 下午9:45:34
* @param i
* @return
final int checkIndex(int i) {
if ((i < 0) || (i >= limit))
throw new IndexOutOfBoundsException();
return i;
final int checkIndex(int i, int nb) {
if ((i < 0) || (nb > limit - i))
throw new IndexOutOfBoundsException();
return i;
final int markValue() {
return mark;
final void truncate() {
mark = -1;
position = 0;
limit = 0;
capacity = 0;
final void discardMark() {
mark = -1;
static void checkBounds(int off, int len, int size) {
if ((off | len | (off + len) | (size - (off + len))) < 0)
throw new IndexOutOfBoundsException();
Buffer的存取
缓冲区管理着固定数目的数据元素,在我们想清空缓冲区之前,我们可能只使用了缓冲区的一部分。这时,我们需要能够追踪添加到缓冲区内的数据元素的数量,放入下一个元素的位置等等的方法。位置属性做到了这一点。它在调用 put()时指出了下一个数据元素应该被插入的位置,或者当 get()被调用时指出下一个元素应从何处检索。这里我们以ByteBuffer为例介绍下这两个方法。
public abstract class ByteBuffer {
* 读取该缓冲区当前位置上的字节,然后增加位置。
* @author fuyuwei
* 2017年6月19日 下午9:54:42
* @return
public abstract byte get();
* 读取给定索引中的字节
* @author fuyuwei
* 2017年6月19日 下午9:56:17
* @param index
* @return
public abstract byte get(int index);
* 将给定的字节写入当前位置的缓冲区中,然后增加位置
* @author fuyuwei
* 2017年6月19日 下午9:56:54
* @param b
* @return
public abstract ByteBuffer put(byte b);
* 在给定索引中将给定字节写入该缓冲区
* @author fuyuwei
* 2017年6月19日 下午9:57:53
* @param index
* @param b
* @return
public abstract ByteBuffer put(int index, byte b);
Buffer的填充
我们将代表“Hello”字符串的 ASCII 码载入一个名为 buffer 的ByteBuffer 对象中。
public static void main(String[] args) {
ByteBuffer buffer=ByteBuffer.allocate(256);
buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
此时byteBuffer的position为5(从0开始),注意本例中的每个字符都必须被强制转换为 byte。我们不能不经强制转换而这样操做:
buffer.put('H')
因为我们存放的是字节而不是字符。记住在 java 中,字符在内部以 Unicode 码表示,每个 Unicode 字符占 16 位。通过将char 强制转换为 byte,我们删除了前八位来建立一个八位字节值。既然我们已经在 buffer 中存放了一些数据,如果我们想在不丢失位置的情况下通过put进行修改。假设我们想将缓冲区中的内容从“Hello”的 ASCII 码更改为“ Mellow”。我们可以这样实现:
buffer.put(0,(byte)'M').put((byte)'w');
第0个position的H被替换为了M,而第二个put不会修改第1个position他会从之前记住的position(第5个)开始往buffer里放数据
Buffer的翻转
对于已经写满了缓冲区,如果将缓冲区内容传递给一个通道,以使内容能被全部写出。但如果通道现在在缓冲区上执行get(),那么它将从我们刚刚插入的有用数据之外取出未定义数据。如果我们通过翻转将位置值重新设为 0,通道就会从正确位置开始获取。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
Flip()函数将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态
例如我们定义了一个容量是10的buffer,并填入hello,如下图所示
翻转后如下图所示
Rewind()函数与 flip()相似,但不影响上界属性。它只是将位置值设回 0。您可以使用 rewind()后退,重读已经被翻转的缓冲区中的数据。
翻转两次把上界设为位置的值,并把位置设为 0。上界和位置都变成 0,get()操作会导致 BufferUnderflowException 异常。而 put()则会导致 BufferOverflowException 异常。
Buffer的释放
如果一个填满的缓冲区在读之前要对其进行翻转,hashRemaining会在释放缓冲区时告诉我们是否已达到缓冲区的上界。缓冲区并不是线程安全的,多线程环境下在存取缓冲区之前要进行同步处理。一旦缓冲区对象完成填充并释放,它就可以被重新使用了,clear()将缓冲区重置为空。他并不改变缓冲区的数据,仅仅是将上界设为容量值,并把位置设置为0,这使得缓冲区可以重新被填入。
填充和释放缓冲区代码例如:
public class NIOTest {
private static int index = 0;
private static String [] strings = {
"A random string value",
"The product of an infinite number of monkeys",
"Hey hey we're the Monkees",
"Opening act for the Monkees: Jimi Hendrix",
"'Scuse me while I kiss this fly",
"Help Me! Help Me!",
public static void main(String[] args) {
CharBuffer buf = CharBuffer.allocate(128);
while(fillBuffer(buf)){
buf.flip();
drainBuffer(buf);
buf.clear();
private static void drainBuffer(CharBuffer buffer){
while(buffer.hasRemaining()){
System.out.println(buffer.get());
private static boolean fillBuffer(CharBuffer buffer){
if(index >= strings.length){
return false;
String string = strings[index++];
for(int i=0;i<strings.length;i++){
buffer.put(string.charAt(i));
return true;
Buffer的压缩
public abstract ByteBuffer compact();
如果我们只想从缓冲区中释放一部分数据,而不是全部,然后重新填充。为了实现这一点,未读的数据元素需要下移以使第一个元素索引为 0。尽管重复这样做会效率低下,但这有时非常必要,而 API 对此为您提供了一个 compact()函数。这一缓冲区工具在复制数据时要比您使用 get()和 put()函数高效得多。
压缩后变成
元素2-4被复制了0-2。位置3,4不受影响,3和4可以被之后的put覆盖,postion变成了被复制元素的个数的位置,limit=capacity,此时缓冲区可以再次被填满。调用 compact()的作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。
Buffer的标记
标记,使缓冲区能够记住一个位置并在之后将其返回。缓冲区的标记在 mark( )函数被调用之前是未定义的,调用时标记被设为当前位置的值。reset( )函数将位置设为当前的标记值。如果标记值未定义,调用 reset( )将导致 InvalidMarkException 异常。一些缓冲区函数会抛弃已经设定的标记( rewind( ), clear( ),以及 flip( )总是抛弃标记)。如果新设定的值比当前的标记小,调用limit( )或 position( )带有索引参数的版本会抛弃标记。这里需要注意的是clear( )函数将清空缓冲区,而 reset( )位置返回到一个先前设定的标记。
如果这个缓冲区现在被传递给一个通道,两个字节(“ lo”)将会被发送,而位置会前进到 5。如果我们此时调用 reset( ),位置将会被设为标记,如下图所示。再次将缓冲区传递给通道将导致四个字节(“ello”)被发送。

Buffer的比较
equals( )
返回true的条件:
1、两个对象类型相同。包含不同数据类型的 buffer 永远不会相等,而且 buffer绝不会等于非 buffer 对象。
2、两个对象都剩余同样数量的元素。 Buffer 的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从位置到上界)必须相同。
3、在每个缓冲区中应被 Get()函数返回的剩余数据元素序列必须一致
compareTo( )
compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:
1、第一个不相等的元素小于另一个Buffer中对应的元素 。
2、所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。
Buffer的批量移动
缓冲区的涉及目的就是为了能够高效传输数据。一次移动一个数据元素效率并不高,Buffer的API给我们提供了批量移动的方法。批量移动总是具有指定的长度。也就是说,您总是要求移动固定数量的数据元素
* 这个方法将所有字节从这个缓冲区传输到给定的目标数组中
* @author fuyuwei
* 2017年6月20日 下午9:21:41
* @param dst
* @return
public ByteBuffer get(byte[] dst) {
return get(dst, 0, dst.length);
* 这个方法将字节从这个缓冲区传输到给定的目标数组。
* @author fuyuwei
* 2017年6月20日 下午9:23:01
* @param dst
* @param offset
* @param length
* @return
public ByteBuffer get(byte[] dst, int offset, int length) {
checkBounds(offset, length, dst.length);
if (length > remaining())
throw new BufferUnderflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
dst[i] = get();
return this;
* 这个方法将给定源字节数组的整个内容传输到这个缓冲区中。
* @author fuyuwei
* 2017年6月20日 下午9:26:33
* @param src
* @return
public final ByteBuffer put(byte[] src) {
return put(src, 0, src.length);
* 这个方法将字节从给定的源数组中转移到这个缓冲区中
* @author fuyuwei
* 2017年6月20日 下午9:30:52
* @param src
* @param offset
* @param length
* @return
public ByteBuffer put(byte[] src, int offset, int length) {
checkBounds(offset, length, src.length);
if (length > remaining())
throw new BufferOverflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
this.put(src[i]);
return this;
缓冲区是包在一个对象内的基本数据元素数组,Buffer类相比一个简单的数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer的属性容量(capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变 上界(limit):缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数 位置(position):下一个要被读或写的元素
写缓冲区
HBase每次的put操作,都是一次rpc操作,如果某个应用程序每秒钟有1000+的put操作,那显然是不合适的。
HBase的客户端API中配置了一个写缓冲区,缓冲区负责收集put操作,达到一定条件后,调用一次rpc操作,将全部的缓冲数据发送到服务器端。
void setAutoFlush(boolean autoFlush)
boolean isAutoFlu
堆外内存是相对于堆内内存的一个概念。堆内内存是由JVM所管控的Java进程内存,我们平时在Java中创建的对象都处于堆内内存中,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理它们的内存。那么堆外内存就是存在于JVM管控之外的一块内存区域,因此它是不受JVM的管控。
在讲解DirectByteBuffer之前,需要先简单了解两个知识点。
java引用类
缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I/O)的数据作临时存储,这部分预留的内存空间就叫做缓冲区:
使用缓冲区有这么两个好处:
1、减少实际的物理读写次数
2、缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数
举个简单的例子,比如A地有1w块砖要搬到B地
由于没有工具(缓冲区),我们一次只能搬一本,那么就要搬1w次(
(1)capacity
作为一个内存块,Buffer有一个固定的大小值,也叫"capacity".你只能往里写capacity个byte,long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
(2...
缓冲区:
在NIO库中,所有的数据都是用缓冲区处理的,在读取数据时,它是直接读到缓冲区的,在写入数据时,它是写入到缓冲区的,任何时候访问NIO中的数据,你都是将它放到缓冲区中。
缓冲区实质上是一个数组,通常它是一个字节数组,但是也可以使用其他种类的数组,但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读写进程
缓冲区类型:
最常用的缓冲区类型是ByteBuffer。一个ByteBuffer可以在其底层字节数组上进行get/set操作(即字节的获取和设置)。Byte
1. 熟悉 Nio 技术:在学习 Netty 之前,需要先掌握 Nio 技术的基本知识,包括缓冲区、通道、选择器等。
2. 了解 Netty 的基本架构:Netty 的整个架构由多个模块组成,包括 Channel、EventLoop、ChannelPipeline 等,需要了解它们之间的关系和作用。
3. 学习 Netty 的核心组件:Netty 的核心组件包括 Channel、EventLoop、ChannelHandler 等,需要深入了解它们的作用和使用方法。
4. 实践 Netty 的案例:通过实践 Netty 的案例,例如编写简单的 Echo 服务器和客户端,可以更好地理解和掌握 Netty 的使用方法和技巧。
5. 深入研究 Netty 的源码:通过深入研究 Netty 的源码,可以更好地了解它的实现原理和内部机制,从而更好地应用和扩展 Netty。
总之,学习 Netty 需要有良好的 Nio 基础,并且需要结合实际案例进行实践,同时深入研究 Netty 的源码可以更好地提高技术水平。