NIO 下的 ByteBuffer简单学习

443 阅读4分钟

本文正在参加「金石计划 . 瓜分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文件内容如下所示

image.png

接下来执行上述代码, 结果如下

image.png

可以看到打印出来的结果是 ASCII 编码表的值,所以我们把输出转为 char 类型, 结果如下

image.png

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();
}

打印结果如下:

image.png

根据打印结果我们可以很直观的看到当前 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);
}

执行结果如下:

image.png

图中和代码一样, 是分两次打印当前 ByteBuffer 的内容, pos 就是下一次写入的下标

buffer.get()

get 方法是获取元素的, 但是我们如果在刚写入之后就去读取的话, 是什么都读取不到的, 具体如下所示

image.png

可以看到, 我们读取出来当前的元素为 0, 我们进入源码看一下 get 方法

image.png

如图所示, 我们通过 get 方法获取到的元素都是当前 pos 的下一个坐标元素, 在之前的 put 方法中, 我们看到最后 pos 是指向了 5 是下一个 写入下标

我们有两种方法可以获取到元素:

  • 通过下标获取
  • 将写入模式更改为读模式

通过下标获取: 直接输入下标就可以获取到了

image.png

buffer.flip()

flip 方法: 将读模式转换为写模式

image.png

还是刚才的例子, 使用方法 buffer.flip() 之后, 可以看到 pos 变成了 1, lim 变成了 5 , cap 不变

这里 pos 代表的是下一个 get 的下标, lim 代表的是当前的长度

注意: 在调用 flip 进入读模式之后,后续如果调用 get() 导致 position 大于等于了 limit 的值,程序会抛出 BufferUnderflowException 异常。这点从之前 get 的源码也可以看出来。

buffer.rewind()

rewind 方法可以理解为下标归零, 也可以理解为重新开始, 重头开始. 代码展示如下所示

image.png

我们点进源代码也可以看到, 它实际上就是将 pos 赋值了 0, 将 mark 赋值为 -1

image.png

buffer.mark() & buffer.reset()

这里将 mark 方法和 reset 方法一起讲, 他俩是相辅相成的一个关系, 二者缺一不可
mark 方法标记当前下标, reset 回到 mark 标记的下标

image.png

可以看到, 当我们执行 mark 方法的时候记录了当前的下标, 执行 reset 方法的时候 pos 又更改为了 1

buffer.clear() & buffer.compact()

clear 方法和 compact 方法都是对当前 ByteBuffer 写入 , clear 方法是从头开始写入, compact 方法是将未读内容移至头部然后再写入

image.png

可以看到, 在执行完 clear 方法之后, 新增的元素是从下标 0 开始写入的

image.png

可以看到, 在执行完 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 的几个常用方法, 重新学习第一天, 奥利给




本文内容到此结束了

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

我是 宁轩 , 我们下次再见