NIO 下的 ByteBuffer简单学习

简介: NIO 下的 ByteBuffer简单学习

前言

咱就说,基础不牢地动山摇,以前欠下的债迟早都要补回来,开始安排 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 的几个常用方法, 重新学习第一天, 奥利给



目录
相关文章
|
Java Maven
java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat(IIILjava/nio/ByteBuffer;)J [duplicate]
java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat(IIILjava/nio/ByteBuffer;)J [duplicate]
NIO同步非阻塞模型学习使用
NIO同步非阻塞模型学习使用
NIO同步非阻塞模型学习使用
|
缓存 网络协议 Java
Java NIO学习(二):Channel通道
Java NIO 的通道类似流,但又有些不同:
183 0
Java NIO学习(二):Channel通道
|
设计模式 缓存 网络协议
Java NIO学习(一):Java NIO概述
IO 的操作方式通常分为几种:同步阻塞 BIO、同步非阻塞 NIO、异步非阻塞 AIO。
163 0
Java NIO学习(一):Java NIO概述
|
NoSQL 应用服务中间件 Redis
NIO学习四-Selector
前面我们已经简单的学习了channel,知道channel作为通道,可以在通道中进行读写操作,同时知道ByteChannel是双向的。对于NIO的优势在于多路复用选择器上,在Nginx、Redis、Netty中都有多路复用的体现。因此学习Selector是有必要的。
96 0
NIO学习四-Selector
|
Java API
NIO学习三-Channel
在学习NIO时,ByteBuffer、Channel、Selector三个组件是必须了解的。前面我们说到ByteBuffer是作为缓冲区进行数据的存放或者获取。通常我们需要进行flip翻转操作,但是这个在Netty中,有一个更为强大的类可以替代ByteBuf,其不需要进行翻转,也可以进行读写的双向操作。要将数据打包到缓冲区中,通常需要使用通道,而通道作为传输数据的载体,也即它可以使数据从一端到另一端,因此就必须进行了解。 Channel中,我们也看到其子类有很多,通常都是用于读写操作的。其中ByteChannel可以进行读写操作,也即可以进行双向操作。 操作过程:首先创建流对象,有了流对象获取
90 0
NIO学习三-Channel
NIO学习二-ByteBuffer
前面我们已经了解到Buffer中,0<=mark<=postion<=limit<=capacity。其中mark是标记,如果为-1时丢弃。postion是当前位置,limit是限制,也即上界。capacity是容量。同时了解了直接缓冲区与缓冲区的底层实现是不同的,缓冲区是基于数组的,而直接缓冲区是基于内存的。同时可以基于反射,拿到cleaner,进而拿到clean进行清理。同时clear是还原缓冲区的状态,flip是反转缓冲区的,rewind重绕缓冲区,标记清除。remianing对剩余元素的个数记录。offset获取偏移量。 ByteBuffer是Buffer的子类,可以在缓冲区以字节为单
103 0
NIO学习二-ByteBuffer
|
存储 索引
NIO学习一
NIO相比普通IO提供了功能更为强大、处理数据更快的解决方案。 常用于高性能服务器上。NIO实现高性能处理的原理是使用较少的线程来处理更多的任务 常规io使用的byte[]、char[]进行封装,而NIO采用ByteBuffer类来操作数据,再结合 针对File或socket技术的channel,采用同步非阻塞技术来实现高性能处理,而Netty 正是采用ByteBuffer(缓冲区)、Channel(通道)、Selector(选择器)进行封装的。 因此我们需要先了解NIO相关的知识。
105 0
NIO学习一
|
存储 缓存 Java
【NIO】NIO三剑客之一ByteBuffer介绍与使用
【NIO】NIO三剑客之一ByteBuffer介绍与使用
【NIO】NIO三剑客之一ByteBuffer介绍与使用