目录
本文须知
本文内容来自于B站孙帅suns的netty应用视频,由本人根据视频学习整理而成。然而,由于个人理解和整理的差异,可能存在部分地方的误差或讲解不够明确的情况。
为了获得更详细的课程内容和准确的解释,请您移步至B站,搜索孙帅suns获取详细课程视频内容,以便深入了解和学习。孙帅suns的视频将为您提供更全面、详细的知识和解释,帮助您更好地理解netty应用的相关内容。
在阅读本文时,请注意本文的目的是为了提供一个概述和个人整理的观点,而不是取代原始视频课程的内容。如有任何疑问或不明之处,建议您参考原始视频课程以获取更准确的信息。
感谢您的理解和支持!
在此附上B站孙帅suns个人主页链接:孙帅suns个人主页
前言
上一篇我和大家一起了解了Channel和Buffer。我们知道,Buffer当中存在很多种类,但是大多都是相通的,所以这一篇我们通过ByteBuffer来了解一下Buffer的获取和实现,以及它的一些核心API。
ByteBuffer的实现
ByteBuffer是抽象类,他的主要实现类有
- HeapByteBuffer
- MappedByteBuffer(DirectByteBuffer)
HeapByteBuffer和DirectByteBuffer的区别:
- HeapByteffer使用的是JVM内的内存
- 优点:
- 创建成本低;
- 缺点:
- 读写效率低;
- 会受到gc的影响;
- 优点:
- DirectByteBuffer使用的是OS的内存
- 优点:
- 读写效率高,不会受到gc的影响,不会受到Java堆内存大小的限制;
- 缺点:
- 需要主动的进行析构(资源回收),否则会造成内存的泄露;
- 优点:
内存溢出和内存泄漏
内存溢出:
- 指的是可提供的内存容量不足以满足程序需求申请的内存大小。
内存溢出通常由程序或者算法问题引起,如:
- 无限递归;
- 创建过多对象;
注意:内存溢出通常发生在堆栈内存上。
内存泄漏:
- 指的是因为部分原因,导致可提供的内存大小并不是真实的内容容量。比如有一部分内存被程序申请使用之后没有进行正确的释放或回收。
内存泄漏通常由于程序当中的错误或疏忽引起,如:
- 忘记释放动态分配的内存;
- 循环引用;
注意:内存泄漏通常发生在堆内存上。
ByteBuffer的获取
获取方式
- ByteBuffer.allocate(10);
- 一旦分配内存空间,无法进行动态的调整。
- 这里在netty当中提供的ByteBuffer可以动态的调整。原始的NIO当中的ByteBuffer无法进行调整。
- encode()
ByteBuffer的核心结构
三个主要状态
ByteBuffer是一个类似数组的结构,整个结构当中包含三个主要的状态:
- Capacity:
- buffer的容量,类似于数组的size
- Position:
- buffer当前缓存的下标,在读取操作时记录读到了哪个位置,在写操作时记录写到了哪个位置。从0开始,每读取一次,下标+1
- 注意:每次进行读写的时候需要检查和控制position的位置,确保不会越界访问缓冲区当中的数据。
- Limit:
- 读写限制,在读操作时,设置你能够读取多少字节数据;在写操作时,设置你还能写多少字节数据。
//测试刚开始写操作时
@Test
public void testState1() {
ByteBuffer buffer = ByteBuffer.allocate(10);
//10 0 10
System.out.println("buffer.capacity() = " + buffer.capacity());
System.out.println("buffer.position() = " + buffer.position());
System.out.println("buffer.limit() = " + buffer.limit());
}
//测试写进三位之后的变化
@Test
public void testState2() {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a', 'b', 'c'});
//10 3 10
System.out.println("buffer.capacity() = " + buffer.capacity());
System.out.println("buffer.position() = " + buffer.position());
System.out.println("buffer.limit() = " + buffer.limit());
//追加测试,再写两位
System.out.println("==================================");
buffer.put(new byte[]{'1', '2'});
//10 5 10
System.out.println("buffer.capacity() = " + buffer.capacity());
System.out.println("buffer.position() = " + buffer.position());
System.out.println("buffer.limit() = " + buffer.limit());
}
//测试刚开始读操作时的变化
@Test
public void testState3() {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.flip();
//10 0 0
System.out.println("buffer.capacity() = " + buffer.capacity());
System.out.println("buffer.position() = " + buffer.position());
System.out.println("buffer.limit() = " + buffer.limit());
}
//测试写进三个数然后读状态下的变化
@Test
public void testState4() {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a', 'b', 'c'});
buffer.flip();
//10 0 3
System.out.println("buffer.capacity() = " + buffer.capacity());
System.out.println("buffer.position() = " + buffer.position());
System.out.println("buffer.limit() = " + buffer.limit());
}
数据的读写
在NIO当中,写入Buffer之前要设置写模式:
- 新创建的buffer是自动是写模式;
- 调用clear、compact方法可以切换成写模式;
在NIO当中,读取Buffer之前要设置读模式:
- 调用flib方法;
clear和compact方法的区别:
- clear:清空缓冲区,将缓冲区设置成写模式;
- compact:切换到写模式,同时将未读取的数据移动到缓冲区的开头。
测试compact方法:
/** 测试compact方法
* 作用:将未读取的数据移动到缓冲区的开头,之前已读取的数据所占用的空间就变得可以重新使用,从而减少了内存的浪费。
*/
@Test
public void testStatic5() {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a', 'b', 'c', 'd'});
//设置读模式,同时取出两位
buffer.flip();
System.out.println("buffer.get() = " + (char)buffer.get()); //a
System.out.println("buffer.get() = " + (char)buffer.get()); //b
//测试取出两位之后的状态:10 2 4
System.out.println("buffer.capacity() = " + buffer.capacity());
System.out.println("buffer.position() = " + buffer.position());
System.out.println("buffer.limit() = " + buffer.limit());
//通过compact设置成写模式之后再查看状态:10 2 10
System.out.println("=====================");
buffer.compact();
System.out.println("buffer.capacity() = " + buffer.capacity());
System.out.println("buffer.position() = " + buffer.position());
System.out.println("buffer.limit() = " + buffer.limit());
}
核心API
Buffer当中写数据:
- channel的read方法:
- channel.read(buffer)
- buffer的put方法
- buffer.put(byte)
- byffer.put(byte[])
Buffer当中读数据:
- channel的write方法
- buffer的get方法
- 每调用一次get方法,会影响position的位置
- rewind方法
- 可以将position重置成0,用于复读数据
- mark&reset方法
- 通过mark方法进行标记position
- 通过reset方法跳回标记,重新执行
- get(i)方法:
- 获取特定position上的数据,但是不会对position的位置产生影响
- 同时get(i)方法不受读写模式的影响
下面是关于上述一些方法的测试:
//测试mark和reset
@Test
public void TestAPI1() {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a', 'b', 'c', 'd'});
buffer.flip();
System.out.println("buffer.get() = " + (char)buffer.get()); //a
System.out.println("buffer.get() = " + (char)buffer.get()); //b
buffer.mark(); //标记
System.out.println("buffer.get() = " + (char)buffer.get()); //c
System.out.println("buffer.get() = " + (char)buffer.get()); //d
buffer.reset();
System.out.println("==============");
System.out.println("buffer.get() = " + (char)buffer.get());
System.out.println("buffer.get() = " + (char)buffer.get());
}
//测试get(i)方法
@Test
public void TestAPI2() {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a', 'b', 'c', 'd'});
buffer.flip();
System.out.println("buffer.get(2) = " + (char)buffer.get(2));
}
总结
在Buffer当中,其实最重要的就是三个状态,对Buffer的操作基本上都跟这三个状态有关,所以,只需要我们能够熟练掌握这三个状态,基本上就能够熟练的运用Buffer了。