本文正在参加「金石计划 . 瓜分6万现金大奖」
前言
咱就说,基础不牢地动山摇,以前欠下的债迟早都要补回来,开始安排 Netty, 从 NIO 开始学起, 学习嘛, 肯定是先从案例开始学起
今日任务:
- 初步学习 NIO 中的 ByteBuffer
ByteBuffer
文件读取案例
一个小小的案例, 读取 test.txt 文件的内容, 四步走:
- 获取文件流
- 准备缓冲区
- 写入缓冲区
- 遍历读取
代码展示
private static void getFileContent(){
// 输入输出流
try(FileChannel channel = new FileInputStream("C:\Users\My\Desktop\learn\demo\demo\test.txt").getChannel()){
// 准备缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
// 循环遍历
while(true){
// 从 channel 读取数据, 向 buffer 写入
int len = channel.read(buffer);
if (len == -1){
System.out.println("没有内容了");
break;
}
// 打印 buffer 内容
// 切换至 读模式
buffer.flip();
// 遍历查看是否还有剩余未读数据
while(buffer.hasRemaining()){
byte b = buffer.get();
System.out.println(b);
}
// 切换为写模式
buffer.clear();
}
}catch (Exception e){
System.out.println("文件未找到");
}
}
打印结果
test.txt文件内容如下所示
接下来执行上述代码, 结果如下
可以看到打印出来的结果是 ASCII 编码表的值,所以我们把输出转为 char 类型, 结果如下
Buffer
Buffer继承自Object类,是基本类型元素的线性有限序列(容器)。除内容外,Buffer的基本属性有:Limit—限制,Capacity—容量,Position—位置。对着三个变量操作,可以完成几乎所有对Buffer的代码操作。
今天开始学习 Netty ,那就绕不过 NIO, 在 NIO 包下的 Buffer 有以下七种实现:
- ByteBuffer
- ShortBuffer
- FloatBuffer
- CharBuffer
- DoubleBuffer
- IntBuffer
- LongBuffer
实际上就是八种数据类型的相关 Buffer, 但是我只听说过 ByteBuffer, 也是使用最广泛的, 至于为什么我也不知道, 希望以后有机会能过来填坑, 有大佬知道的话也可以评论区说一下
ByteBuffer常用方法
在 java 中 ByteBuffer 的常用方法有以下七种:
- put: 写入
- get: 读数据
- flip: 切换读模式
- rewind: 重新重头开始读
- mark: 记录当前下标
- reset: 回到 mark 位置
- clear: 切换写模式
- compact: 切换写模式, 同时将未读数据移动到首部
工具方法 selectAll()
为了方便观察当前 ByteBuffer 所处的位置, 容量等信息, 写了下面这个方法
private static void selectAll(ByteBuffer buffer){
System.out.println();
int limit = buffer.limit();
System.out.print("pos = " + buffer.position() + " " + "lim = " + buffer.limit() + " " + "cap = " + buffer.capacity());
System.out.println();
for (int i = 0; i < limit; i++) {
if (i < 10){
System.out.print(" | ");
}else{
System.out.print(" | ");
}
System.out.print(i);
}
System.out.println();
System.out.println("--------------------------------------------------------------------------");
for (int i = 0; i < limit; i++) {
byte b = buffer.get(i);
System.out.print(" | ");
System.out.print((char) b);
if (b == 0){
System.out.print(0);
}
}
System.out.println();
}
打印结果如下:
根据打印结果我们可以很直观的看到当前 ByteBuffer 每个下标上的元素是什么, 同时也能看到当前指针所处的位置(Position)
初始化
以下测试用例的 ByteBuffer 采用同一个 ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
buffer.put()
put 方法就是往 ByteBuffer 中写入数据, 具体操作如下述代码所示
private static void bufferPut(ByteBuffer buffer){
buffer.put(new byte[]{'a', 'b', 'c', 'd'});
selectAll(buffer);
buffer.put(new byte[]{'1'});
selectAll(buffer);
}
执行结果如下:
图中和代码一样, 是分两次打印当前 ByteBuffer 的内容, pos 就是下一次写入的下标
buffer.get()
get 方法是获取元素的, 但是我们如果在刚写入之后就去读取的话, 是什么都读取不到的, 具体如下所示
可以看到, 我们读取出来当前的元素为 0, 我们进入源码看一下 get 方法
如图所示, 我们通过 get 方法获取到的元素都是当前 pos 的下一个坐标元素, 在之前的 put 方法中, 我们看到最后 pos 是指向了 5 是下一个 写入下标
我们有两种方法可以获取到元素:
- 通过下标获取
- 将写入模式更改为读模式
通过下标获取: 直接输入下标就可以获取到了
buffer.flip()
flip 方法: 将读模式转换为写模式
还是刚才的例子, 使用方法 buffer.flip() 之后, 可以看到 pos 变成了 1, lim 变成了 5 , cap 不变
这里 pos 代表的是下一个 get 的下标, lim 代表的是当前的长度
注意: 在调用 flip 进入读模式之后,后续如果调用
get()
导致position
大于等于了limit
的值,程序会抛出BufferUnderflowException
异常。这点从之前get
的源码也可以看出来。
buffer.rewind()
rewind 方法可以理解为下标归零, 也可以理解为重新开始, 重头开始. 代码展示如下所示
我们点进源代码也可以看到, 它实际上就是将 pos 赋值了 0, 将 mark 赋值为 -1
buffer.mark() & buffer.reset()
这里将 mark 方法和 reset 方法一起讲, 他俩是相辅相成的一个关系, 二者缺一不可
mark 方法标记当前下标, reset 回到 mark 标记的下标
可以看到, 当我们执行 mark 方法的时候记录了当前的下标, 执行 reset 方法的时候 pos 又更改为了 1
buffer.clear() & buffer.compact()
clear 方法和 compact 方法都是对当前 ByteBuffer 写入 , clear 方法是从头开始写入, compact 方法是将未读内容移至头部然后再写入
可以看到, 在执行完 clear 方法之后, 新增的元素是从下标 0 开始写入的
可以看到, 在执行完 compact 方法之后, 它先是将没有读取到的数据放置头部, 在接下来 put 的时候对后面的内容进行了一个覆盖
全部代码
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Author ningxuan
* @Date 2022/10/31 21:28
*/
public class ByteBufferTest {
public static void main(String[] args) {
// 读文件案例
// getFileContent();
// 初始化 ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
// 写测试
bufferPut(buffer); // a , b , c , d, 1
bufferGet(buffer);
buffer.flip();
// System.out.println((char) buffer.get()); // a pos == 0
// System.out.println((char)buffer.get()); // b pos == 1
// buffer.rewind();
// System.out.println((char)buffer.get()); // c pos == 0
// System.out.println((char)buffer.get()); // d pos == 1
// bufferMarkTest(buffer);
bufferCompact(buffer);
}
private static void bufferCompact(ByteBuffer buffer){
selectAll(buffer);
buffer.get();
buffer.get();
selectAll(buffer);
buffer.compact();
selectAll(buffer);
buffer.put(new byte[]{'e', 'h'});
selectAll(buffer);
}
private static void bufferClear(ByteBuffer buffer){
selectAll(buffer);
System.out.println();
buffer.clear();
buffer.put(new byte[]{'e', 'h'});
selectAll(buffer);
}
private static void bufferMarkTest(ByteBuffer buffer){
byte b;
b = buffer.get(); // a
System.out.println((char) b);
buffer.mark();
System.out.println("position = " + buffer.position());
b = buffer.get(); // b
System.out.println((char) b);
b = buffer.get(); // c
System.out.println((char) b);
buffer.reset();
System.out.println("position = " + buffer.position());
b = buffer.get(); // b
System.out.println((char) b);
b = buffer.get(); // c
System.out.println((char) b);
}
private static void bufferGet(ByteBuffer buffer){
byte b = buffer.get(0);
// System.out.println((char)b);
b = buffer.get(1);
// System.out.println((char)b);
}
private static void bufferPut(ByteBuffer buffer){
buffer.put(new byte[]{'a', 'b', 'c', 'd'});
// selectAll(buffer);
buffer.put(new byte[]{'1'});
// selectAll(buffer);
}
private static void selectAll(ByteBuffer buffer){
System.out.println();
int limit = buffer.limit();
System.out.print("pos = " + buffer.position() + " " + "lim = " + buffer.limit() + " " + "cap = " + buffer.capacity());
System.out.println();
for (int i = 0; i < limit; i++) {
if (i < 10){
System.out.print(" | ");
}else{
System.out.print(" | ");
}
System.out.print(i);
}
System.out.println();
System.out.println("--------------------------------------------------------------------------");
for (int i = 0; i < limit; i++) {
byte b = buffer.get(i);
System.out.print(" | ");
System.out.print((char) b);
if (b == 0){
System.out.print(0);
}
}
System.out.println();
}
private static void getFileContent(){
// 输入输出流
try(FileChannel channel = new FileInputStream("C:\Users\My\Desktop\learn\demo\demo\test.txt").getChannel()){
// 准备缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
// 循环遍历
while(true){
// 从 channel 读取数据, 向 buffer 写入
int len = channel.read(buffer);
if (len == -1){
System.out.println("没有内容了");
break;
}
// 打印 buffer 内容
// 切换至 读模式
buffer.flip();
// 遍历查看是否还有剩余未读数据
while(buffer.hasRemaining()){
byte b = buffer.get();
System.out.println((char) b);
}
// 切换为写模式
buffer.clear();
}
}catch (Exception e){
System.out.println("文件未找到");
}
}
}
总结
今天学习了 NIO 下的 ByteBuffer 的几个常用方法, 重新学习第一天, 奥利给
本文内容到此结束了
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位大佬指出。
我是 宁轩 , 我们下次再见